> This is part 1 of 2 of the full documentation (pages 1–100 of 105).
> The content is paginated: fetch every part to see all of it.
> Next part: https://docs.17movement.net/llms-full.txt/1
> Page index: https://docs.17movement.net/llms.txt

# Homepage

Hello and welcome to our technical documentation! We are a dedicated team specializing in creating unique and high-quality FiveM scripts for the GTA V community. Our goal is to provide you with exceptional products that elevate your gaming experience and make your server stand out.

## What We Do

We specialize in crafting custom scripts for FiveM, offering an extensive range of products designed to enhance the gameplay experience on your server. From immersive role-playing jobs to performance optimizations and innovative features, our scripts cater to various needs and preferences. We take pride in our exceptional customer support and strive to ensure a seamless integration of our products into your server.

## Customizable Solutions

We understand that each server is unique, and our products are designed with customization in mind. Our scripts come with extensive configuration options, allowing you to tailor them to your specific requirements and preferences. This flexibility ensures that our products are a perfect fit for your server, regardless of its theme or gameplay style.

{% hint style="info" %}
Always make sure to backup your scripts before installing or updating our scripts. This simple precaution can save you time and effort in case of unexpected issues or conflicts during the installation process.
{% endhint %}

## Comprehensive Documentation

Our technical documentation provides in-depth information on the installation, configuration, and usage of our products. Explore the detailed guides for each script to ensure a smooth and successful integration into your server. Additionally, our documentation offers tips, best practices, and troubleshooting advice to help you make the most of your purchase.<br>

**Note on Reselling and Profit Generation:**

Any reselling, redistribution, or other means of profiting from our products is strictly prohibited. Engaging in such activities is in violation of our terms and conditions and may result in the immediate revocation of your license. We reserve the right to take further legal action if necessary.


# Support

We understand that sometimes our documentation might not provide all the answers you need, or you may need further clarification. Don't worry! Our team is always here to help and make sure you get the assistance you require.

## Contact us on Discord

The best way to reach us for immediate help is through our Discord server. Our friendly support team is available to assist you with any questions or concerns you may have regarding our products. Join our Discord server today and don't hesitate to ask for help\
\
[Discord](https://discord.17movement.net)


# 🚀 Getting Started

### Installation

Place the `17mov_Interface` resource inside your server's `resources` folder.

{% hint style="warning" %}
The resource folder must be named exactly `17mov_Interface`. Renaming it may break exports, integrations and installation files.
{% endhint %}

Make sure the resource starts **after your framework** and any optional integrations you are using.

```
ensure qb-core          # or es_extended / qbx_core
ensure ox_lib           # optional
ensure ox_target        # optional
ensure 17mov_Interface
```

If you are not using `ox_lib` or a target system, you do not need to include them.

### HUD Editor

Players can open the HUD editor using:

```
/hud
```

The default keybind is:

```
F7
```

The HUD editor allows players to customize their interface, including positions, sizes, styles and other available settings.

### Admin HUD Editor

Administrators can open the server-wide HUD editor using:

```
/hudadmin
```

The admin editor allows you to configure the default HUD layout for the entire server.


# 💻 API

The Complete Interface System provides a full API for interacting with the interface from your own resources.

This section contains all available client and server exports, events, and HUD values.

### API Reference

* [Client Exports](https://docs.17movement.net/complete-interface-system/api/client-exports)\
  All exports available from client-side scripts.
* [Server Exports](https://docs.17movement.net/complete-interface-system/api/server-exports)\
  Exports available from server-side scripts.
* [Events](https://docs.17movement.net/complete-interface-system/api/events)\
  Client, server and integration events exposed by the system.
* [HUD Values](https://docs.17movement.net/complete-interface-system/api/hud-values)\
  Available keys used with `getValue` and `setValue`.


# Client Exports

All client exports are available through the `17mov_Interface` resource.

```lua
local interface = exports['17mov_Interface']
```

Optional arguments and fields are marked with `?`.

\
All exports listed on this page are also fully demonstrated in:

```
client/examples.lua
```

You can use that file as a reference for usage examples.

***

### Notifications

#### `NotificationData`

* `id?` `string` - Reusing the same ID updates the existing notification.
* `type?` `string` - `success`, `info`, `warning`, `error` or `message`. Defaults to `info`.
* `title?` `string` - Notification title.
* `description?` `string` - Notification description.
* `duration?` `number` - Duration in milliseconds.
* `icon?` `string` - Lucide icon name.
* `iconColor?` `string` - Hex icon color.
* `group?` `string` - Group key used for duplicate stacking.

#### `showNotification(data)`

Displays a notification.

**Arguments**

* `data` `NotificationData | string`

When a string is provided, it is used as the notification title.

```lua
interface:showNotification({
    type = 'success',
    title = 'Payment received',
    description = 'You received $500',
    duration = 4000,
    icon = 'banknote'
})
```

***

### Text UI

#### `TextUIOptions`

* `type?` `string` - `accent`, `info`, `success`, `warning` or `error`.
* `icon?` `string` - Lucide icon name.
* `iconColor?` `string` - Hex icon color.
* `duration?` `number` - Automatically hides the Text UI after the specified time.

#### `showTextUI(text, options?)`

Displays a Text UI element.

**Arguments**

* `text` `string` - Text to display.
* `options?` `TextUIOptions`

Key hints such as `[E]` are rendered as key elements.

```lua
interface:showTextUI('[E] Open the door', {
    type = 'info',
    icon = 'door-open'
})
```

#### `hideTextUI()`

Hides the currently displayed Text UI.

```lua
interface:hideTextUI()
```

#### `isTextUIOpen()`

**Returns**

```lua
boolean isOpen, string|nil currentText
```

***

### Progress Bar

#### `ProgressAnimation`

Animation played while the progress bar is active.

* `dict?` `string` - Animation dictionary.
* `clip?` `string` - Animation clip.
* `flag?` `number` - Animation flag. Defaults to `49`.
* `blendIn?` `number`
* `blendOut?` `number`
* `duration?` `number`
* `playbackRate?` `number`
* `lockX?` `boolean`
* `lockY?` `boolean`
* `lockZ?` `boolean`
* `scenario?` `string` - Can be used instead of a dictionary and clip.
* `playExitAnim?` `boolean`

Example:

```lua
{
    dict = 'mini@repair',
    clip = 'fixing_a_ped',
    flag = 49
}
```

A scenario can also be used:

```lua
{
    scenario = 'PROP_HUMAN_BUM_BIN'
}
```

#### `ProgressProp`

Prop attached to the player while the progress bar is active.

* `model` `string | number` - Prop model.
* `bone?` `number` - Bone ID. Defaults to `60309`.
* `pos?` `vector3` - Position offset.
* `rot?` `vector3` - Rotation offset.
* `rotOrder?` `number` - Rotation order.

Example:

```lua
{
    model = 'prop_tool_wrench',
    bone = 60309,
    pos = vec3(0.0, 0.0, 0.0),
    rot = vec3(0.0, 0.0, 0.0)
}
```

#### `ProgressDisable`

Controls disabled while the progress bar is active.

* `move?` `boolean`
* `car?` `boolean`
* `combat?` `boolean`
* `sprint?` `boolean`
* `mouse?` `boolean`

Example:

```lua
{
    move = true,
    car = true,
    combat = true,
    sprint = true
}
```

#### `ProgressData`

* `duration` `number` - Duration in milliseconds.
* `label?` `string` - Main progress label.
* `description?` `string` - Additional description.
* `canCancel?` `boolean` - Allows the player to cancel the progress.
* `useWhileDead?` `boolean`
* `allowRagdoll?` `boolean`
* `allowCuffed?` `boolean`
* `allowFalling?` `boolean`
* `allowSwimming?` `boolean`
* `anim?` `ProgressAnimation`
* `prop?` `ProgressProp | ProgressProp[]`
* `propTwo?` `ProgressProp`
* `disable?` `ProgressDisable`

#### `progressBar(data)`

Starts a progress bar and waits until it finishes or gets cancelled.

**Arguments**

* `data` `ProgressData`

**Returns**

`true` when completed and `false` when cancelled or interrupted.

```lua
boolean
```

**Example**

```lua
local completed = interface:progressBar({
    duration = 5000,
    label = 'Repairing engine',
    description = 'Hold still',
    canCancel = true,

    anim = {
        dict = 'mini@repair',
        clip = 'fixing_a_ped',
        flag = 49
    },

    prop = {
        model = 'prop_tool_wrench',
        bone = 60309,
        pos = vec3(0.0, 0.0, 0.0),
        rot = vec3(0.0, 0.0, 0.0)
    },

    disable = {
        move = true,
        car = true,
        combat = true
    }
})
```

#### `progressCircle(data)`

Alias of `progressBar`.

**Arguments**

* `data` `ProgressData`

**Returns**

```lua
boolean
```

#### `startProgress(data, onStart?, onTick?, onFinish?)`

Starts a non-blocking progress bar.

**Arguments**

* `data` `ProgressData`
* `onStart?` `function`
* `onTick?` `function`
* `onFinish?` `function(cancelled: boolean)`

**Returns**

```lua
boolean started
```

#### `cancelProgress()`

Cancels the active progress bar.

```lua
interface:cancelProgress()
```

#### `stopProgress()`

Stops the active progress bar and marks it as completed.

```lua
interface:stopProgress()
```

#### `progressActive()`

Returns whether a progress bar is currently active.

**Returns**

```lua
boolean
```

***

### Input Dialog

#### `InputDialogHeader`

* `title?` `string`
* `description?` `string`
* `icon?` `string`

#### `InputDialogOptions`

* `allowCancel?` `boolean` - Displays a cancel button.

#### Common Input Fields

These fields are available for every input type:

* `type?` `string`
* `label?` `string`
* `description?` `string`
* `placeholder?` `string`
* `icon?` `string`
* `default?` `any`
* `required?` `boolean`
* `disabled?` `boolean`

#### `TextInput`

* `type` `"text" | "input"`
* `min?` `number` - Minimum length.
* `max?` `number` - Maximum length.
* `password?` `boolean`

Returns `string`.

#### `NumberInput`

* `type` `"number"`
* `min?` `number`
* `max?` `number`
* `step?` `number`
* `decimals?` `number`
* `negative?` `boolean`

Returns `number`.

#### `CheckboxInput`

* `type` `"checkbox"`
* `text?` `string`
* `checked?` `boolean`

Returns `boolean`.

#### `SelectOption`

* `value` `any`
* `label` `string`
* `disabled?` `boolean`

#### `SelectInput`

* `type` `"select" | "multi-select"`
* `options` `SelectOption[]`

Returns the selected value.

#### `SliderInput`

* `type` `"slider"`
* `min?` `number`
* `max?` `number`
* `step?` `number`
* `unit?` `string`

Returns `number`.

#### `ColorInput`

* `type` `"color"`
* `presets?` `string[]`

Returns a hex color string.

#### `DateInput`

* `type` `"date"`
* `format?` `string`
* `returnString?` `boolean`
* `clearable?` `boolean`

Returns a timestamp or formatted string.

#### `DateRangeInput`

* `type` `"date-range"`
* `format?` `string`
* `returnString?` `boolean`
* `clearable?` `boolean`

Returns:

```lua
{
    from = any,
    to = any
}
```

#### `DateTimeInput`

* `type` `"date-time"`
* `format?` `string`
* `returnString?` `boolean`
* `clearable?` `boolean`
* `minuteStep?` `number`

Returns a timestamp or formatted string.

#### `TimeInput`

* `type` `"time"`
* `minuteStep?` `number`
* `clearable?` `boolean`

Returns:

```
HH:mm
```

#### `TextareaInput`

* `type` `"textarea"`
* `rows?` `number`
* `min?` `number`
* `max?` `number`

Returns `string`.

#### `InputRow`

An input row can be any of the following types:

```
TextInput
NumberInput
CheckboxInput
SelectInput
SliderInput
ColorInput
DateInput
DateRangeInput
DateTimeInput
TimeInput
TextareaInput
```

#### `inputDialog(header, rows, options?)`

Displays an input dialog and waits for the player's response.

**Arguments**

* `header` `string | InputDialogHeader`
* `rows` `InputRow[]`
* `options?` `InputDialogOptions`

**Returns**

A table containing values in the same order as the provided rows, or `nil` when cancelled.

```lua
table|nil
```

**Example**

```lua
local input = interface:inputDialog({
    title = 'Vehicle registration',
    icon = 'file-text'
}, {
    {
        type = 'text',
        label = 'Plate',
        required = true,
        min = 2,
        max = 8
    },
    {
        type = 'number',
        label = 'Price',
        min = 0
    },
    {
        type = 'checkbox',
        label = 'Insurance'
    }
}, {
    allowCancel = true
})

if not input then return end

print(input[1], input[2], input[3])
```

#### `closeInputDialog()`

Closes the currently displayed input dialog.

```lua
interface:closeInputDialog()
```

***

### Alert Dialog

#### `AlertDialogHeader`

* `title?` `string`
* `description?` `string`
* `icon?` `string`

#### `AlertDialogLabels`

* `confirm?` `string`
* `cancel?` `string`

#### `AlertDialogOptions`

* `allowCancel?` `boolean`
* `labels?` `AlertDialogLabels`

#### `AlertDialogData`

* `header` `string | AlertDialogHeader`
* `content` `string` - Markdown is supported.
* `options?` `AlertDialogOptions`

#### `alertDialog(data, timeout?)`

Displays an alert dialog and waits for the player's response.

**Arguments**

* `data` `AlertDialogData`
* `timeout?` `number` - Timeout in milliseconds.

**Returns**

```lua
'confirm' | 'cancel' | nil
```

```lua
local result = interface:alertDialog({
    header = {
        title = 'Sell the vehicle?',
        icon = 'car'
    },

    content = 'You will receive **$4,500**.',

    options = {
        allowCancel = true,
        labels = {
            confirm = 'Sell',
            cancel = 'Keep'
        }
    }
})
```

When using `timeout`, the call is rejected with `timeout` after the specified time.

#### `closeAlertDialog(reason?)`

Closes the currently displayed alert dialog.

**Arguments**

* `reason?` `string`

```lua
interface:closeAlertDialog()
```

***

### Context Menu

#### `ContextMetadata`

* `label` `string`
* `value?` `any`
* `progress?` `number`

#### `ContextOption`

* `title?` `string`
* `description?` `string`
* `icon?` `string`
* `iconColor?` `string`
* `image?` `string`
* `progress?` `number`
* `colorScheme?` `string`
* `arrow?` `boolean`
* `disabled?` `boolean`
* `readOnly?` `boolean`
* `metadata?` `ContextMetadata[] | table`
* `menu?` `string`
* `onSelect?` `function(args)`
* `event?` `string`
* `serverEvent?` `string`
* `args?` `any`

#### `ContextMenu`

* `id` `string`
* `title` `string`
* `description?` `string`
* `menu?` `string` - Parent context ID.
* `canClose?` `boolean`
* `options` `ContextOption[] | table`
* `onExit?` `function`
* `onBack?` `function`

#### `registerContext(context)`

Registers one or multiple context menus.

**Arguments**

* `context` `ContextMenu | ContextMenu[]`

```lua
interface:registerContext({
    id = 'garage',
    title = 'Garage',

    options = {
        {
            title = 'Sultan RS',
            icon = 'car',
            progress = 87
        },
        {
            title = 'Sell vehicle',
            icon = 'banknote',
            serverEvent = 'garage:server:sell'
        }
    }
})
```

#### `showContext(id)`

Opens a registered context menu.

**Arguments**

* `id` `string`

```lua
interface:showContext('garage')
```

#### `hideContext(onExit?)`

Closes the currently displayed context menu.

**Arguments**

* `onExit?` `boolean`

#### `getOpenContext()`

**Returns**

```lua
string|nil
```

***

### List Menu

#### `ListMenuValue`

* `label` `string`
* `description?` `string`

A plain string can also be used instead.

#### `ListMenuOption`

* `label?` `string`
* `description?` `string`
* `icon?` `string`
* `iconColor?` `string`
* `values?` `string[] | ListMenuValue[]`
* `defaultIndex?` `number`
* `checked?` `boolean`
* `progress?` `number`
* `colorScheme?` `string`
* `close?` `boolean`
* `disabled?` `boolean`
* `args?` `any`

#### `ListMenu`

* `id` `string`
* `title` `string`
* `position?` `string` - `top-left`, `top-right`, `bottom-left` or `bottom-right`.
* `options` `ListMenuOption[]`
* `menu?` `string`
* `canClose?` `boolean`
* `cursor?` `boolean`
* `disableInput?` `boolean`
* `onClose?` `function(keyPressed)`
* `onSelected?` `function(index, secondary, args)`
* `onSideScroll?` `function(index, scrollIndex, args)`
* `onCheck?` `function(index, checked, args)`
* `cb?` `function(index, scrollIndex, args, checked)`

#### `registerMenu(data, cb?)`

Registers a list menu.

**Arguments**

* `data` `ListMenu`
* `cb?` `function(index, scrollIndex, args, checked)`

```lua
interface:registerMenu({
    id = 'settings',
    title = 'Settings',
    position = 'top-left',

    options = {
        {
            label = 'Radio volume',
            values = { 'Low', 'Mid', 'High' },
            defaultIndex = 2,
            close = false
        },
        {
            label = 'Dark mode',
            checked = true
        }
    }
}, function(selected, scrollIndex, args, checked)
    print(selected, scrollIndex, checked)
end)
```

#### `setMenuOptions(id, options, index?)`

Updates all options or one specific option.

**Arguments**

* `id` `string`
* `options` `ListMenuOption[] | ListMenuOption`
* `index?` `number`

#### `showMenu(id, startIndex?)`

Opens a registered menu.

**Arguments**

* `id` `string`
* `startIndex?` `number`

#### `hideMenu(onExit?)`

Closes the active menu.

**Arguments**

* `onExit?` `boolean`

#### `getOpenMenu()`

**Returns**

```lua
string|nil
```

***

### Radial Menu

#### `RadialItem`

* `id?` `string`
* `label` `string`
* `icon?` `string`
* `onSelect?` `function(menuId, index, args) | string`
* `event?` `string`
* `serverEvent?` `string`
* `args?` `any`
* `menu?` `string`
* `condition?` `function`
* `disabled?` `boolean`
* `keepOpen?` `boolean`

#### `RadialMenu`

* `id` `string`
* `items` `RadialItem[]`

#### `registerRadial(menu)`

Registers a radial submenu.

**Arguments**

* `menu` `RadialMenu`

```lua
interface:registerRadial({
    id = 'police_menu',

    items = {
        {
            id = 'search',
            label = 'Search',
            icon = 'search',
            serverEvent = 'police:server:search'
        }
    }
})
```

#### `addRadialItem(items, parentMenuId?)`

Adds one or multiple radial items.

**Arguments**

* `items` `RadialItem | RadialItem[]`
* `parentMenuId?` `string`

#### `removeRadialItem(id, parentMenuId?)`

**Arguments**

* `id` `string`
* `parentMenuId?` `string`

#### `clearRadialItems()`

Removes all root radial items.

#### `disableRadial(state)`

**Arguments**

* `state` `boolean`

#### `openRadial(id?)`

**Arguments**

* `id?` `string`

#### `closeRadial()`

Closes the radial menu.

#### `toggleRadial(id?)`

**Arguments**

* `id?` `string`

#### `isRadialOpen()`

**Returns**

```lua
boolean
```

#### `getCurrentRadial()`

**Returns**

```lua
string|nil
```

***

### NPC Dialog

#### `DialogCamera`

* `distance?` `number`
* `height?` `number`
* `side?` `number`
* `fov?` `number`
* `blend?` `number`

#### `DialogPed`

* `model` `string | number`
* `coords` `vector3 | vector4`
* `heading?` `number`
* `distance?` `number` - Spawn distance.
* `interact?` `number` - Interaction distance.
* `label?` `string`
* `icon?` `string`
* `prompt?` `string`
* `helpText?` `string`
* `scenario?` `string`
* `freeze?` `boolean`
* `canInteract?` `function(entity, distance)`
* `canInteractInterval?` `number`

#### `DialogOption`

* `label` `string`
* `description?` `string`
* `icon?` `string`
* `disabled?` `boolean`
* `condition?` `function`
* `next?` `string`
* `close?` `boolean`
* `value?` `any`
* `args?` `any`
* `onClick?` `function(args, ped, result) | string`
* `onSelect?` `function(args, ped, result) | string`
* `event?` `string`
* `serverEvent?` `string`

#### `DialogNode`

* `speaker?` `string`
* `text` `string | string[]`
* `options?` `DialogOption[]`
* `onOpen?` `function(nodeId, ped)`

#### `Dialog`

* `id?` `string`
* `title?` `string`
* `start` `string`
* `nodes` `table<string, DialogNode>`
* `radius?` `number`
* `camera?` `DialogCamera`
* `ped?` `DialogPed`
* `hideHud?` `boolean`
* `hidePlayer?` `boolean`
* `faceCamera?` `boolean`
* `facialAnim?` `boolean`
* `canClose?` `boolean`
* `canInteract?` `function(entity, distance)`
* `onStart?` `function(ped)`
* `onSelect?` `function(result, ped)`
* `onEnd?` `function(ped, result)`
* `onResult?` `function(result, ped)`

#### `DialogResult`

* `dialog` `string`
* `node` `string`
* `index` `number`
* `label` `string`
* `value?` `any`
* `args?` `any`
* `path` `string[]`

#### `registerDialog(dialog)`

Registers one or multiple dialogs.

**Arguments**

* `dialog` `Dialog | table<string, Dialog>`

**Returns**

The generated or provided dialog ID when a single dialog is registered.

```lua
string
```

#### `startDialog(dialogOrId, ped?, customSettings?)`

Starts a dialog and waits for the player's response.

**Arguments**

* `dialogOrId` `Dialog | string`
* `ped?` `number`
* `customSettings?` `table`

**Returns**

```lua
DialogResult|nil
```

#### `stopDialog(silent?)`

Stops the active dialog.

**Arguments**

* `silent?` `boolean`

#### `removeDialog(id)`

Removes a registered dialog and its interaction.

**Arguments**

* `id` `string`

#### `isDialogOpen()`

**Returns**

```lua
string|false openDialogId, string|false currentNodeId
```

***

### Sounds

#### `playSound(name, volume?, exclusive?)`

Plays a registered sound.

**Arguments**

* `name` `string`
* `volume?` `number`
* `exclusive?` `boolean`

```lua
interface:playSound('notify:success')
```

#### `stopSound(name?)`

Stops a specific sound.

**Arguments**

* `name?` `string`

When no sound is specified, all sounds are stopped.

***

### HUD

#### `toggleDisplay(state)`

Shows or hides the HUD.

**Arguments**

* `state` `boolean`

#### `openEditor()`

Opens the player's HUD editor.

#### `setValue(key, value)`

Sets a HUD value.

**Arguments**

* `key` `string`
* `value` `any`

#### `getValue(key)`

Returns a stored HUD value.

**Arguments**

* `key` `string`

**Returns**

```lua
any
```

#### `getStatus()`

Returns the current status values.

**Returns**

```lua
{
    hunger = number,
    thirst = number,
    stress = number
}
```

***

### Info Panel

#### `InfoItemValue`

* `value` `any`
* `sub?` `any`
* `badge?` `any`

#### `setInfoItem(id, value, sub?, badge?)`

Updates an info panel item.

**Arguments**

* `id` `string`
* `value` `any | InfoItemValue`
* `sub?` `any`
* `badge?` `any`

```lua
interface:setInfoItem('radio', '245.5 MHz', 'Dispatch', 'LIVE')
```

Or:

```lua
interface:setInfoItem('job', {
    value = 'Mechanic',
    sub = 'Boss',
    badge = 'ON DUTY'
})
```

#### `refreshInfoItems()`

Refreshes all function-driven info panel items.

***

### Radar / Minimap

#### `hideRadar(state)`

Temporarily hides or shows the minimap.

**Arguments**

* `state` `boolean`

#### `setMapLoop(state)`

Enables or disables minimap management.

**Arguments**

* `state` `boolean`

***

### Seatbelt

#### `updateBeltsState(state)`

Updates the seatbelt state without playing the toggle sound.

**Arguments**

* `state` `boolean`

#### `getBeltsState(cb?)`

**Arguments**

* `cb?` `function(state)`

**Returns**

```lua
boolean
```

***

### Cinematic Mode

#### `enableCinematic()`

Enables cinematic mode.

#### `disableCinematic()`

Disables cinematic mode.

#### `toggleCinematic()`

Toggles cinematic mode.

#### `isCinematic()`

**Returns**

```lua
boolean
```

***

### Stress

#### `addStress(amount)`

Adds stress.

**Arguments**

* `amount` `number`

#### `removeStress(amount)`

Removes stress.

**Arguments**

* `amount` `number`

#### `getStress()`

**Returns**

```lua
number
```

#### `setStressEnabled(state)`

Enables or disables the stress system.

**Arguments**

* `state` `boolean`

#### `isStressEnabled()`

**Returns**

```lua
boolean
```

#### `setStressEffects(state)`

Enables or disables stress effects.

**Arguments**

* `state` `boolean`

***

### Stress Sources

#### `StressSourceOptions`

* `enabled?` `boolean`
* `check?` `function(service, now, source)`
* `poll?` `boolean`
* `interval?` `number`
* `gain?` `number`
* `cooldown?` `number`
* `notify?` `boolean`

`check` is required unless `poll = false`.

#### `addStressSource(id, options)`

Registers a custom stress source.

**Arguments**

* `id` `string`
* `options` `StressSourceOptions`

```lua
interface:addStressSource('lowhealth', {
    gain = 3,
    interval = 2000,
    cooldown = 5000,

    check = function()
        return GetEntityHealth(PlayerPedId()) < 150
    end
})
```

#### `removeStressSource(id)`

**Arguments**

* `id` `string`

#### `setStressSource(id, state)`

**Arguments**

* `id` `string`
* `state` `boolean`

**Returns**

```lua
boolean
```

#### `setStressSourceGain(id, amount)`

**Arguments**

* `id` `string`
* `amount` `number`

#### `isStressSource(id)`

**Arguments**

* `id` `string`

**Returns**

```lua
boolean
```

#### `getStressSources()`

Returns all registered stress sources.

#### `triggerStressSource(id)`

Triggers a manually controlled stress source.

**Arguments**

* `id` `string`


# Server Exports

All server exports are available through the `17mov_Interface` resource.

```lua
local interface = exports['17mov_Interface']
```

***

### `showNotification(source, data)`

Displays a notification for a player.

**Arguments**

* `source` `number` - Player server ID.
* `data` `NotificationData | string` - Uses the same notification data as the client export.

**Example**

```lua
interface:showNotification(source, {
    type = 'success',
    title = 'Payment received',
    description = 'You received $500',
    duration = 4000,
    icon = 'banknote'
})
```

***

### `startProgress(source, data)`

Starts a non-blocking progress bar for a player.

**Arguments**

* `source` `number` - Player server ID.
* `data` `ProgressData` - Uses the same progress data as the client export.

**Example**

```lua
interface:startProgress(source, {
    duration = 5000,
    label = 'Repairing vehicle',
    canCancel = true
})
```

***

### `stopProgress(source)`

Stops the active progress bar for a player and marks it as completed.

**Arguments**

* `source` `number` - Player server ID.

```lua
interface:stopProgress(source)
```

***

### `toggleDisplay(source, state)`

Shows or hides the HUD for a player.

**Arguments**

* `source` `number` - Player server ID.
* `state` `boolean` - `true` to show the HUD, `false` to hide it.

```lua
interface:toggleDisplay(source, false)
```

***

### `addStress(source, amount)`

Adds stress to a player.

**Arguments**

* `source` `number` - Player server ID.
* `amount` `number` - Amount of stress to add.

```lua
interface:addStress(source, 10)
```

***

### `removeStress(source, amount)`

Removes stress from a player.

**Arguments**

* `source` `number` - Player server ID.
* `amount` `number` - Amount of stress to remove.

```lua
interface:removeStress(source, 10)
```

***

### `getDefaultPositions()`

Returns the default positions configured for HUD elements.

**Returns**

```lua
table
```

**Example**

```lua
local positions = interface:getDefaultPositions()
```

***

### `setDefaultPositions(positions)`

Updates the default HUD element positions and applies them to all players.

**Arguments**

* `positions` `table` - New default positions.

**Returns**

```lua
boolean
```

**Example**

```lua
local success = interface:setDefaultPositions(positions)
```

***

### `getDefaultLayout()`

Returns the default HUD layout saved by the admin editor.

**Returns**

```lua
string|nil
```

**Example**

```lua
local layout = interface:getDefaultLayout()
```

***

### `setDefaultLayout(code)`

Sets or clears the default HUD layout and applies the change to all players.

**Arguments**

* `code` `string | nil` - Layout code. Pass `nil` to clear the current default layout.

**Returns**

```lua
boolean
```

**Example**

```lua
local success = interface:setDefaultLayout(layoutCode)
```

To clear the current layout:

```lua
interface:setDefaultLayout(nil)
```

***

### `SoundFile`

Represents a sound available in the sound library.

* `file` `string` - Sound file name.
* `title` `string` - Display name.
* `type` `string` - Sound file type.

### `getSoundLibrary()`

Returns all currently available sounds.

**Returns**

```lua
SoundFile[]
```

**Example**

```lua
local sounds = interface:getSoundLibrary()

for _, sound in ipairs(sounds) do
    print(sound.file, sound.title, sound.type)
end
```

***

### `refreshSoundLibrary()`

Re-scans the sounds directory and returns the updated sound library.

**Returns**

```lua
SoundFile[]
```

**Example**

```lua
local sounds = interface:refreshSoundLibrary()
```


# Events

The following events can be triggered from your own resources to interact with the interface.

From the client:

```lua
TriggerEvent('eventName', ...)
```

From the server:

```lua
TriggerClientEvent('eventName', source, ...)
```

Optional arguments are marked with `?`.

***

### Notifications

#### `17mov_Interface:Notification:Client:Show`

Displays a notification.

**Arguments**

* `data` `NotificationData | string`

```lua
TriggerEvent('17mov_Interface:Notification:Client:Show', {
    type = 'success',
    title = 'Payment received',
    description = 'You received $500'
})
```

***

### Text UI

#### `17mov_Interface:TextUI:Client:Show`

Displays Text UI.

**Arguments**

* `text` `string`
* `options?` `TextUIOptions`

```lua
TriggerEvent(
    '17mov_Interface:TextUI:Client:Show',
    '[E] Open the door',
    {
        type = 'info',
        icon = 'door-open'
    }
)
```

#### `17mov_Interface:TextUI:Client:Hide`

Hides the active Text UI.

```lua
TriggerEvent('17mov_Interface:TextUI:Client:Hide')
```

***

### Progress Bar

#### `17mov_Interface:Progress:Client:ProgressBar`

Starts a progress bar.

**Arguments**

* `data` `ProgressData`

```lua
TriggerEvent('17mov_Interface:Progress:Client:ProgressBar', {
    duration = 5000,
    label = 'Repairing vehicle',
    canCancel = true
})
```

#### `17mov_Interface:Progress:Client:ProgressCircle`

Starts a progress circle.

**Arguments**

* `data` `ProgressData`

#### `17mov_Interface:Progress:Client:StartProgress`

Starts a non-blocking progress bar.

**Arguments**

* `data` `ProgressData`

#### `17mov_Interface:Progress:Client:Cancel`

Cancels the active progress bar.

```lua
TriggerEvent('17mov_Interface:Progress:Client:Cancel')
```

#### `17mov_Interface:Progress:Client:Stop`

Stops the active progress bar and marks it as completed.

```lua
TriggerEvent('17mov_Interface:Progress:Client:Stop')
```

***

### Input Dialog

#### `17mov_Interface:InputDialog:Client:Show`

Displays an input dialog.

**Arguments**

* `header` `string | InputDialogHeader`
* `rows` `InputRow[]`
* `options?` `InputDialogOptions`

#### `17mov_Interface:InputDialog:Client:Hide`

Closes the active input dialog.

```lua
TriggerEvent('17mov_Interface:InputDialog:Client:Hide')
```

***

### Alert Dialog

#### `17mov_Interface:AlertDialog:Client:Show`

Displays an alert dialog.

**Arguments**

* `data` `AlertDialogData`
* `timeout?` `number`

#### `17mov_Interface:AlertDialog:Client:Hide`

Closes the active alert dialog.

**Arguments**

* `reason?` `string`

***

### Context Menu

#### `17mov_Interface:ContextMenu:Client:Register`

Registers one or multiple context menus.

**Arguments**

* `context` `ContextMenu | ContextMenu[]`

#### `17mov_Interface:ContextMenu:Client:ShowContext`

Opens a registered context menu.

**Arguments**

* `id` `string`

```lua
TriggerEvent(
    '17mov_Interface:ContextMenu:Client:ShowContext',
    'garage'
)
```

#### `17mov_Interface:ContextMenu:Client:HideContext`

Closes the active context menu.

**Arguments**

* `onExit?` `boolean`

***

### List Menu

#### `17mov_Interface:Menu:Client:Register`

Registers a list menu.

**Arguments**

* `data` `ListMenu`
* `cb?` `function`

#### `17mov_Interface:Menu:Client:SetOptions`

Updates menu options.

**Arguments**

* `id` `string`
* `options` `ListMenuOption[] | ListMenuOption`
* `index?` `number`

#### `17mov_Interface:Menu:Client:Show`

Opens a registered list menu.

**Arguments**

* `id` `string`
* `startIndex?` `number`

#### `17mov_Interface:Menu:Client:Hide`

Closes the active list menu.

**Arguments**

* `onExit?` `boolean`

***

### Radial Menu

#### `17mov_Interface:RadialMenu:Client:Register`

Registers a radial menu.

**Arguments**

* `menu` `RadialMenu`

#### `17mov_Interface:RadialMenu:Client:AddItem`

Adds one or multiple radial items.

**Arguments**

* `items` `RadialItem | RadialItem[]`
* `parentMenuId?` `string`

#### `17mov_Interface:RadialMenu:Client:RemoveItem`

Removes a radial item.

**Arguments**

* `id` `string`
* `parentMenuId?` `string`

#### `17mov_Interface:RadialMenu:Client:ClearItems`

Removes all root radial items.

#### `17mov_Interface:RadialMenu:Client:Disable`

Enables or disables the radial menu.

**Arguments**

* `state` `boolean`

#### `17mov_Interface:RadialMenu:Client:Open`

Opens the radial menu.

**Arguments**

* `id?` `string`

#### `17mov_Interface:RadialMenu:Client:Close`

Closes the radial menu.

#### `17mov_Interface:RadialMenu:Client:Toggle`

Toggles the radial menu.

**Arguments**

* `id?` `string`

***

### NPC Dialog

#### `17mov_Interface:NpcDialog:Client:Register`

Registers one or multiple NPC dialogs.

**Arguments**

* `dialog` `Dialog | table<string, Dialog>`

#### `17mov_Interface:NpcDialog:Client:Show`

Starts a dialog.

**Arguments**

* `dialogOrId` `Dialog | string`
* `ped?` `number`
* `customSettings?` `table`

#### `17mov_Interface:NpcDialog:Client:Hide`

Stops the active dialog.

**Arguments**

* `silent?` `boolean`

#### `17mov_Interface:NpcDialog:Client:Remove`

Removes a registered dialog.

**Arguments**

* `id` `string`

***

### Sounds

#### `17mov_Interface:Sound:Client:Play`

Plays a registered sound.

**Arguments**

* `name` `string`
* `volume?` `number`
* `exclusive?` `boolean`

```lua
TriggerEvent(
    '17mov_Interface:Sound:Client:Play',
    'notify:success'
)
```

#### `17mov_Interface:Sound:Client:Stop`

Stops a sound.

**Arguments**

* `name?` `string`

***

### HUD

#### `17mov_Interface:Hud:Client:ToggleDisplay`

Shows or hides the HUD.

**Arguments**

* `state` `boolean`

```lua
TriggerEvent(
    '17mov_Interface:Hud:Client:ToggleDisplay',
    false
)
```

#### `17mov_Interface:Editor:Client:Open`

Opens the player's HUD editor.

```lua
TriggerEvent('17mov_Interface:Editor:Client:Open')
```

#### `17mov_Interface:Hud:Client:SetValue`

Sets a HUD value.

**Arguments**

* `key` `string`
* `value` `any`

***

### Info Panel

#### `17mov_Interface:Info:Client:SetItem`

Updates an info panel item.

**Arguments**

* `id` `string`
* `value` `any`
* `sub?` `any`
* `badge?` `any`

```lua
TriggerEvent(
    '17mov_Interface:Info:Client:SetItem',
    'radio',
    '245.5 MHz',
    'Dispatch',
    'LIVE'
)
```

#### `17mov_Interface:Info:Client:Refresh`

Refreshes all function-driven info panel items.

***

### Radar / Minimap

#### `17mov_Interface:Radar:Client:Hide`

Temporarily hides or shows the minimap.

**Arguments**

* `state` `boolean`

#### `17mov_Interface:Radar:Client:SetLoop`

Enables or disables minimap management.

**Arguments**

* `state` `boolean`

***

### Seatbelt

#### `17mov_Interface:Seatbelt:Client:UpdateState`

Updates the seatbelt state.

**Arguments**

* `state` `boolean`

***

### Stress

#### `17mov_Interface:Stress:Client:AddAmount`

Adds stress.

**Arguments**

* `amount` `number`

#### `17mov_Interface:Stress:Client:RemoveAmount`

Removes stress.

**Arguments**

* `amount` `number`

#### `17mov_Interface:Stress:Client:SetEnabled`

Enables or disables the stress system.

**Arguments**

* `state` `boolean`

#### `17mov_Interface:Stress:Client:SetEffects`

Enables or disables stress effects.

**Arguments**

* `state` `boolean`

#### `17mov_Interface:Stress:Client:SetSource`

Enables or disables a registered stress source.

**Arguments**

* `id` `string`
* `state` `boolean`

#### `17mov_Interface:Stress:Client:TriggerSource`

Triggers a manually controlled stress source.

**Arguments**

* `id` `string`

***

### Status

#### `17mov_Interface:Bridge:Client:UpdateStatus`

Updates one or multiple status values.

**Arguments**

* `status` `StatusData`

#### `StatusData`

* `hunger?` `number`
* `thirst?` `number`
* `stress?` `number`

```lua
TriggerEvent('17mov_Interface:Bridge:Client:UpdateStatus', {
    hunger = 75,
    thirst = 60,
    stress = 10
})
```


# Hud Values

HUD values can be read and updated using:

```lua
local interface = exports['17mov_Interface']

local value = interface:getValue('key')
interface:setValue('key', value)
```

Most values are updated automatically by the script. You should only overwrite values that are controlled by your own resource.

When a value is empty, it is stored as `false`.

***

### Status

Values from `0` to `100`.

* `health`
* `armor`
* `hunger`
* `thirst`
* `stress`
* `oxygen`
* `stamina`
* `voice`

Example:

```lua
local hunger = interface:getValue('hunger')

interface:setValue('hunger', 75)
```

***

### Voice

* `talking` `boolean`
* `voiceMode` `number`
* `voiceModeMax` `number`
* `voiceRange` `string`

Example value:

```
2/3
```

***

### Player

* `serverId`
* `job`
* `jobGrade`
* `cash`
* `bank`
* `black`
* `societyMoney`
* `weapon`
* `ammoClip`
* `ammoTotal`
* `radio`

Money values are stored as formatted strings.

Example:

```lua
local job = interface:getValue('job')
local cash = interface:getValue('cash')
```

***

### World

* `street`
* `crossing`
* `zone`
* `heading`
* `compass`
* `time`

`compass` contains values such as:

```
N
NE
E
SE
S
SW
W
NW
```

`time` uses the following format:

```
HH:MM
```

***

### Vehicle

* `inVehicle` `boolean`
* `vehicleKind` `string`
* `speed` `number`
* `speedPercent` `number`
* `rpm` `number`
* `gear`
* `maxGear`
* `fuel`
* `engineHealth`
* `engineOn`
* `lights`
* `highBeams`
* `doorsLocked`
* `mileage`
* `seatbelt`

#### `vehicleKind`

Possible values:

```
land
air
water
```

#### `speed`

Raw vehicle speed.

#### `rpm`

Normalized RPM value from `0` to `100`.

#### `mileage`

Vehicle mileage in kilometers.

***

### Aircraft

* `altitude`
* `altitudeSea`
* `verticalSpeed`
* `airspeed`
* `pitch`
* `roll`
* `airHeading`
* `landingGear`
* `hasLandingGear`

`airspeed` is stored in knots.

***

### Boat

* `knots`
* `depth`
* `inWater`

***

### Info Panel

Custom info items use dynamic keys based on their ID.

```
info:<id>
info:<id>:sub
info:<id>:badge
```

For example, an info item with the ID `phone` uses:

```
info:phone
info:phone:sub
info:phone:badge
```

These values can also be updated using `setInfoItem`.


# 🔗 Integrations

Our script can replace commonly used interface functions from popular frameworks and libraries without requiring changes to every resource that uses them.

After installing the appropriate integration, existing notifications, progress bars, menus, dialogs and other interface elements can be displayed through our interface.

### Available Integrations

* [ESX](https://docs.17movement.net/complete-interface-system/integrations/esx)
* [QB-Core](https://docs.17movement.net/complete-interface-system/integrations/qb-core)
* [ox\_lib & QBOX](https://docs.17movement.net/complete-interface-system/integrations/ox_lib-and-qbox)


# ESX

### Installation

Copy:

```
installation/es_extended/17mov_functions.lua
```

into the root directory of your `es_extended` resource.

Then open:

```
es_extended/fxmanifest.lua
```

and add `17mov_functions.lua` at the end of the `client_scripts` section:

```lua
client_scripts {
    'client/main.lua',
    'client/functions.lua',
    'client/compat.lua',
    'client/modules/wrapper.lua',
    'client/modules/callback.lua',
    'client/modules/adjustments.lua',

    'client/modules/events.lua',

    'client/modules/actions.lua',
    'client/modules/death.lua',
    'client/modules/npwd.lua',

    '17mov_functions.lua',
}
```

Restart the server after making the changes.

***

### Supported Functions

The integration replaces the following ESX functions:

* `ESX.ShowNotification`
* `ESX.ShowAdvancedNotification`
* `ESX.ShowHelpNotification`
* `ESX.TextUI`
* `ESX.HideUI`
* `ESX.UI.Menu.Open`
* `ESX.UI.Menu.Close`
* `ESX.UI.Menu.CloseAll`

Default ESX menus are displayed using our list menu.

ESX `dialog` menus are displayed using our input dialog


# QB-Core

### qb-core

Copy:

```
installation/qb-core/17mov_functions.lua
```

into the root directory of your `qb-core` resource.

Then open:

```
qb-core/fxmanifest.lua
```

and add `17mov_functions.lua` at the end of the `client_scripts` section:

```lua
client_scripts {
    'client/functions.lua',
    'client/loops.lua',
    'client/events.lua',
    'client/drawtext.lua',

    '17mov_functions.lua',
}
```

Restart the server after making the changes.

#### Supported Functions

The integration replaces:

* `QBCore.Functions.Notify`
* `QBCore.Functions.Progressbar`

It also redirects the standard QB Text UI functions to our Text UI:

* `DrawText`
* `ChangeText`
* `HideText`

***

### qb-menu

Copy:

```
installation/qb-menu/main.lua
```

and replace:

```
qb-menu/client/main.lua
```

with the provided file.

The integration keeps the existing qb-menu exports:

* `openMenu`
* `closeMenu`
* `showHeader`

It also keeps the existing events:

```
qb-menu:client:openMenu
qb-menu:client:closeMenu
```

Existing resources using qb-menu can continue using the same exports and events.

***

### qb-input

Copy:

```
installation/qb-input/main.lua
```

and replace:

```
qb-input/client/main.lua
```

with the provided file.

The integration keeps the existing input exports:

* `ShowInput`
* `showInput`

Existing resources using qb-input can continue using the same calls.


# ox\_lib & QBOX

This integration allows resources using `ox_lib` interface functions to display their UI through our interface without requiring changes in those resources.

QBox servers use the same integration through `ox_lib`, so no separate installation is required.

### Installation

Open:

```
installation/ox_lib/
```

Inside this folder you will find replacement files for supported `ox_lib` interface components.

Copy these files into:

```
ox_lib/resource/interface/client/
```

and replace the files with the same names.

We recommend keeping a backup of the original files before replacing them.

Files without a provided replacement should remain unchanged.

Restart the server after making the changes.

***

### Supported Functions

#### Alert Dialog

Replacement file:

```
alert.lua
```

Supports:

* `lib.alertDialog`
* `lib.closeAlertDialog`
* `ox_lib:alertDialog`

***

#### Context Menu

Replacement file:

```
context.lua
```

Supports:

* `lib.registerContext`
* `lib.showContext`
* `lib.hideContext`
* `lib.getOpenContextMenu`

***

#### Input Dialog

Replacement file:

```
input.lua
```

Supports:

* `lib.inputDialog`
* `lib.closeInputDialog`

***

#### Menu

Replacement file:

```
menu.lua
```

Supports:

* `lib.registerMenu`
* `lib.showMenu`
* `lib.hideMenu`
* `lib.setMenuOptions`
* `lib.getOpenMenu`

***

#### Notifications

Replacement file:

```
notify.lua
```

Supports:

* `lib.notify`
* `lib.defaultNotify`
* `ox_lib:notify`
* `ox_lib:defaultNotify`

***

#### Progress Bar

Replacement file:

```
progress.lua
```

Supports:

* `lib.progressBar`
* `lib.progressCircle`
* `lib.cancelProgress`
* `lib.progressActive`

***

#### Radial Menu

Replacement file:

```
radial.lua
```

Supports:

* `lib.registerRadial`
* `lib.addRadialItem`
* `lib.removeRadialItem`
* `lib.clearRadialItems`
* `lib.disableRadial`
* `lib.hideRadial`
* `lib.getCurrentRadialId`

***

#### Text UI

Replacement file:

```
textui.lua
```

Supports:

* `lib.showTextUI`
* `lib.hideTextUI`
* `lib.isTextUIOpen`


# 📖 Introduction

17mov Conflict Tool helps FiveM server owners find and resolve **map conflicts** between streamed resources such as MLOs, map packs, and prop packs.

When multiple resources modify the same part of GTA V's world, FiveM's streaming load order can silently decide which version wins. That can cause issues such as:

* flickering or duplicated props,
* props coming back after another map removed them,
* invisible or incorrect collision,
* buildings or interiors disappearing because of stale occlusion data,
* one resource silently overriding another asset.

Conflict Tool scans your started resources, compares them with the vanilla GTA V baseline and with each other, then lets you review and resolve conflicts before applying changes to the actual resource files.

### What the tool does

1. **Scans** streamed files from started resources.
2. **Detects** conflicts that can be proven against the vanilla GTA V baseline.
3. **Explains** what each resource changed.
4. Lets you **resolve conflicts manually** or use **Auto-resolve** for safe, high-confidence cases.
5. **Backs up** every file before it is changed.
6. **Applies** the selected fixes directly to the resource files.
7. Lets you **restore backups** from the in-game panel or server console.

{% hint style="warning" %}
After applying or restoring files, you must restart FXServer before players can see the changes.
{% endhint %}

### Start here

If this is your first time using Conflict Tool, start with our Getting Started guide. It will walk you through installation, running your first scan and the basic workflow.

[**🚀 Getting Started →**](https://docs.17movement.net/conflict-tool/getting-started)

Once you're familiar with the basics, you can continue with:

* [**⚠️ Understanding Conflicts**](https://docs.17movement.net/conflict-tool/understanding-conflicts) - Learn what each conflict type means and why it was detected.
* [**💾 Backups & Restore**](https://docs.17movement.net/conflict-tool/backups-and-restore) - Restore previous files if you need to roll back changes.
* [**❓ Troubleshooting & FAQ**](https://docs.17movement.net/conflict-tool/troubleshooting) - Find solutions to common problems.


# 🚀 Getting Started

This section will guide you through setting up 17mov\_ConflictTool and running your first conflict scan.

If this is your first time using the tool, we recommend following the pages below in order.

### 1. Install Conflict Tool

Start by adding the resource to your server and configuring the required permissions.

[**📥 Go to Installation →**](https://docs.17movement.net/conflict-tool/getting-started/installation)

### 2. Run your first scan

Once the tool is installed, open it in-game, scan your server and review the detected conflicts.

[**⚡ Go to Quick Start →**](https://docs.17movement.net/conflict-tool/getting-started/quick-start)


# 📥 Installation

Setting up 17mov\_ConflictTool only takes a few steps.

### 1. Add the resource

Place the `17mov_ConflictTool` folder inside your server's `resources/` directory.

### 2. Add the required permissions

Open your `server.cfg` and add the following lines:

```cfg
add_unsafe_worker_permission 17mov_ConflictTool
add_unsafe_child_process_permission 17mov_ConflictTool
ensure 17mov_ConflictTool
```

Keep both `add_unsafe_*` permission lines **before** the `ensure` line.

We recommend keeping:

```cfg
ensure 17mov_ConflictTool
```

at the **very end of your `server.cfg`**. This makes sure all framework and bridge resources are already available when Conflict Tool starts.

{% hint style="warning" %}
The two unsafe permissions are required for the recommended apply mode. They allow Conflict Tool to modify files inside other resources.

Without them, scanning and reviewing conflicts can still work, but applying changes may fall back to a limited mode.
{% endhint %}

### 3. Configure access permissions

Only authorized players can open Conflict Tool and perform server-side actions.

If your server uses one of the supported framework bridges, Conflict Tool uses the framework's existing admin permission check automatically.

If no supported bridge is being used, the player needs the following ACE permission:

```
command
```

#### Giving access to a specific player

You can grant the permission directly to a player's FiveM license identifier in `server.cfg`:

```cfg
add_ace identifier.license:YOUR_LICENSE_IDENTIFIER command allow
```

For example:

```cfg
add_ace identifier.license:1234567890abcdef1234567890abcdef12345678 command allow
```

Replace the example identifier with the actual `license:` identifier of the administrator who should have access.

> 💡 **Tip**
>
> The server console always has permission to use Conflict Tool server commands, regardless of player permissions.

#### Custom permission checks

Conflict Tool's permission check can also be customized if you use your own administration or permission system.

The permission logic can be found in:

```
bridge/framework/[your_framework]/server.lua
```

You can modify the check there to integrate Conflict Tool with your own admin system.

### You're ready

Once the resource is installed and permissions are configured, restart your server.

Then continue with the Quick Start guide to run your first scan and learn the basic workflow.

[**⚡ Continue to Quick Start →**](https://docs.17movement.net/conflict-tool/quick-start)


# ⚡ Quick Start

### 1. Open the panel

Run:

```
/conflicttool
```

Then start a **Scan**.

Large servers with hundreds of resources can take longer to scan. The progress overlay shows the current phase while the scan is running.

The scan is processed in slices, so players can remain connected and the server should continue ticking normally. If other players are online when the tool is opened, Conflict Tool shows a confirmation step before scanning.

### 2. Review the conflict list

Detected conflicts appear in the left panel and are grouped by type:

* **All**
* **Collision**
* **Occl**
* **Prop**
* **Asset**

Conflicts are also geographically clustered.

After a scan, a **focus filter** lets you choose which resources you currently care about. Other resources can be hidden from the list and shown again later.

### 3. Inspect a conflict

Select a conflict to see:

* what the conflict is,
* which resource changed what,
* the suggested resolution,
* the confidence level.

Depending on the conflict type, you can also inspect it in the world using tools such as:

* free cam,
* in-world markers,
* collision overlay for `.ybn` conflicts,
* prop movement gizmo,
* occluder position adjustments.

### 4. Choose a resolution

Depending on the conflict, available decisions can include:

* Keep
* Remove
* Move
* Pick a winner
* Merge
* Ignore

You can also use **Auto-resolve**.

Auto-resolve applies all suggestions the scanner marked as safe and skips conflicts marked **needs review** by default. You can explicitly include review items if you want them included.

Nothing is written to disk yet. Decisions remain pending until you apply them.

You can undo a decision or reset an individual conflict before applying.

### 5. Apply the changes

When you are satisfied with the pending decisions, click **Apply**.

Before changing any file, Conflict Tool automatically creates a backup of every file it is about to touch.

The confirmation dialog also lets you enter a title and note. These are saved with the backup so you can identify it later.

### 6. Restart FXServer

A restart is required after applying changes.

The rewritten resource files are only streamed to players after FXServer restarts. Once files have been applied or restored, the current session is locked to a restart screen so nobody continues editing a world that no longer matches the files on disk.


# ⚠️ Understanding Conflicts

Conflict Tool does **not** treat every duplicate file or every map edit as a conflict.

A conflict is reported only when the tool has enough information to prove that multiple resources disagree about the same part of the world.

### General rule

Something is considered a conflict when:

1. **Two or more resources** ship versions of the same thing,
2. those versions are **different**, and the vanilla GTA V baseline proves that at least one resource actually changed it.

### What is not a conflict?

#### Only one resource changes something

If only one resource ships an edited version, there is nothing to harmonize with another resource. That resource simply wins.

#### Multiple resources ship identical copies

If two or more resources contain the same bytes or agree on the same state, Conflict Tool does not report a conflict.

#### A custom asset has no vanilla original

Custom assets without a vanilla counterpart are intentionally skipped when the tool cannot establish a reliable baseline.

Without the original file, Conflict Tool cannot reliably tell whether a difference is an intentional custom edit or a conflicting modification. Automatically "fixing" that would be guesswork.

### Conflict categories

Conflict Tool groups detected issues into four main categories:

* [📦 Prop Conflicts](https://docs.17movement.net/conflict-tool/understanding-conflicts/prop-conflicts)
* [🧱 Collision Conflicts](https://docs.17movement.net/conflict-tool/understanding-conflicts/collision-conflicts)
* [👁️ Occlusion Conflicts](https://docs.17movement.net/conflict-tool/understanding-conflicts/occlusion-conflicts)
* [📁 Asset Override Conflicts](https://docs.17movement.net/conflict-tool/understanding-conflicts/asset-override-conflicts)

### Why some conflicts say "Needs review"

Auto-resolve only acts where the correct outcome can be proven with high confidence, such as:

* an unambiguous deletion,
* multiple resources agreeing on the same moved position,
* compatible non-overlapping edits,
* identical copies.

If two resources contain genuinely different authored edits, Conflict Tool cannot know which creator's intent is correct.

For example, two road models placed only a few centimeters apart may look like z-fighting, but they may also be intentionally layered geometry. Automatically deleting one could damage a map.

Those cases are marked **needs review** and skipped by Auto-resolve unless you explicitly choose to include them.


# 📦 Prop Conflicts

Prop conflicts happen when multiple resources modify the same YMAP entity but do not agree on its final state.

### When is it a conflict?

Conflict Tool can report a prop conflict when two or more resources refer to the same archetype in the same placement area and disagree about what should happen to it.

Examples:

* one resource deletes a prop while another still places it,
* one moves the prop while another keeps the original placement,
* multiple resources add near-duplicate props in the same location,
* one resource replaces the model while another keeps the original model.

The review panel shows the state contributed by each resource, such as:

* removed,
* moved / changed,
* added,
* unchanged.

### How prop fixes work

The goal is to make every conflicting copy agree so the visible result no longer depends on FiveM's resource streaming order.

A chosen removal or movement can therefore be mirrored across the relevant copies.

### Stale LOD

A resource may hide or remove a prop while another resource still contains its low-detail LOD version.

That can leave a distant "ghost" of a prop floating where the original object used to be.

Conflict Tool mirrors the LOD treatment across copies. If a removed prop leaves behind an orphaned LOD, the tool can sink that LOD out of sight using `Config.Lods.SinkDropZ`.

### Re-modelled prop / archetype swap

Sometimes a resource keeps the same placement but replaces the actual model.

In this case, Conflict Tool can rewrite losing copies to use the chosen model instead of deleting the entity entirely.

### Replacement overlap

Another special case is when a vanilla prop is removed and multiple resources add different replacements in approximately the same location.

Because these replacements may represent intentionally different authored content, some cases can require manual review.


# 🧱 Collision Conflicts

Collision conflicts involve `.ybn` collision dictionaries.

### When is it a conflict?

Conflict Tool reports a collision conflict when two or more resources reference the same collision dictionary but contain **different collision payloads**.

The scanner compares the actual collision data rather than just checking whether the filename or reference appears more than once.

If two resources contain the same untouched collision data, that is not a conflict.

### Why this matters

Only one version of the collision can win the streaming race.

That means load order can silently decide which walls, floors, holes, or other collision surfaces players actually get.

### Resolution

The tool can strip the losing physics reference so the chosen collision version applies consistently.

### Collision overlay

While reviewing `.ybn` conflicts, press **V** to toggle the collision overlay.

The overlay draws the contested collision against the vanilla collision in-world, making it easier to see what changed before choosing a resolution.


# 👁️ Occlusion Conflicts

Occluders are invisible culling volumes stored inside YMAPs. GTA V uses them to avoid rendering geometry that should not currently be visible.

When an occluder is wrong, entire buildings or interiors can disappear even though the actual model files are still present.

### When is it a conflict?

An occlusion conflict exists when two or more resources override the same YMAP and disagree about a vanilla occluder.

Conflict Tool classifies these disagreements based on what each resource changed.

### Common cases

|         Case        |                              What happened                              | Auto-resolve? |                            Why                           |
| :-----------------: | :---------------------------------------------------------------------: | :-----------: | :------------------------------------------------------: |
|  Removal propagate  |     One resource deleted the occluder while others still contain it     |       ✅       |      The deletion is unambiguous and can be mirrored     |
|      Mesh merge     | Resources changed different, non-overlapping parts of the occluder mesh |       ✅       |             The edits can be combined safely             |
|     Move mirror     |   One or more resources moved it and all agree on the same destination  |       ✅       |          Everyone agrees on the target position          |
| Deleted vs reshaped |         One resource deleted it while another reshaped its mesh         |       ❌       |           The intended result cannot be proven           |
|   Mesh edits clash  |        Multiple resources changed the same mesh area differently        |       ❌       | Merging could duplicate geometry and over-cull the world |
|     Moved apart     |           Resources moved the occluder to different locations           |       ❌       |   Conflict Tool cannot know which position is intended   |

### Why removing an occluder can sometimes be safer

For ambiguous occlusion conflicts, the suggested safe fallback may be to remove every copy.

That prevents an incorrect occluder from hiding geometry, but it is still treated as a review decision when the tool cannot prove that removal matches the map author's intent.

### Manual inspection

The panel explains who:

* deleted the occluder,
* reshaped it,
* moved it,
* or kept it unchanged.

You can then inspect the location in-game and choose the intended result.


# 📁 Asset Override Conflicts

An asset override conflict happens when multiple resources stream a file with the same name but the actual file contents are different.

Examples can include:

* `.ytd`
* `.ydr`
* `.ydd`
* `.yft`
* `.ytyp`
* `.ybn`
* LOD-light `.ymap` containers

### When is it a conflict?

Conflict Tool compares file sizes and hashes.

If multiple resources ship byte-identical copies, the tool does not report a conflict.

If the files differ, only one version can win FiveM's streaming race, so the final result may depend on load order.

### Pick a winner

You can choose one resource as the winner.

Conflict Tool copies that resource's version over the other conflicting copies so every shipper contains consistent bytes.

### Merge

Merge is offered only when the scan proves the edits are compatible.

Typed merge support exists for:

* `.ydr` - drawable geometry, including terrain edits,
* `.ybn` - collision triangles and BVH,
* `.ydd` - LOD dictionaries,
* `.ytyp` - archetype definitions,
* LOD-light `.ymap` containers.

The merged asset is built at apply time using the vanilla version as its base, then written to each resource involved in the conflict.

If a merge fails during apply, Conflict Tool falls back to copying the selected winner instead of failing the entire apply operation.

### Ignore

**Ignore** explicitly leaves the conflicting files unchanged.

It counts as a resolved decision inside Conflict Tool, but FiveM's load order will continue deciding which file wins.

### No access

If Conflict Tool cannot read one of the files, the conflict is marked **no access**.

Typical causes are:

* escrow encryption
* locked or otherwise unreadable files.

Unreadable files cannot be merged. The panel identifies which copy cannot be accessed so you can decide how to handle the conflict.

### Needs review

If multiple resources edit the same part of an asset in incompatible ways, a clean merge cannot be proven safe.

Those conflicts require an administrator to pick the intended result.


# 💾 Backups & Restore

Conflict Tool automatically backs up files before it modifies them.

### Automatic backups

Every apply creates a backup in:

```
backups/<timestamp>/
```

The backup mirrors the affected live resource tree.

It also contains `meta.json`, which stores information such as:

* backup title,
* note entered during apply,
* author,
* affected file list,
* snapshot of the resolved ledger.

### Restoring from the in-game panel

Open the **Backups** panel to see available backups.

Backups are marked as:

* **current** - matches the live files right now,
* **stale** - superseded by a later restore.

You can restore an available backup directly from the panel.

### Restoring from the server console

The `ct_restore` command exists so you can roll back a broken map even if entering the server is not possible.

#### List backups

```
ct_restore
```

Shows backups newest first, including:

* backup ID,
* `[current]` or `[stale]`,
* file count,
* title,
* author.

#### Restore the newest valid backup

```
ct_restore latest
```

Restores the newest backup that is not marked stale.

#### Restore a specific backup

```
ct_restore <id>
```

Replace `<id>` with the timestamp shown by the backup list.

{% hint style="warning" %}
Restored files are not streamed to players immediately. Restart FXServer after any restore operation.
{% endhint %}


# ❓ Troubleshooting & FAQ

## ❓ Troubleshooting & FAQ

<details>

<summary>Apply completed, but nothing changed in game</summary>

Restart FXServer.

Applied files only take effect for players after the server loads them again.

The same rule applies after restoring a backup.

</details>

<details>

<summary>An asset conflict says "no access"</summary>

At least one copy could not be read.

This usually means the file is either:

* protected by FiveM Asset Escrow
* corrupted / invalid.

Conflict Tool cannot safely analyze or merge a file it cannot read. The affected copy is marked as **no access** so you can identify which resource contains the problem.

</details>

<details>

<summary>The vanilla baseline says "not found"</summary>

Conflict Tool could not fetch or locate the vanilla original for that asset or game build.

Without the original baseline, some conflicts cannot be verified reliably and merge options may be limited.

</details>

<details>

<summary>Why did Auto-resolve skip a conflict?</summary>

The conflict is probably marked **needs review**.

Auto-resolve skips ambiguous conflicts by default because the tool cannot prove which authored result is correct.

You can review the conflict manually, or use the option that includes review items in Auto-resolve if you intentionally want to accept those suggestions.

</details>

<details>

<summary>Why is a duplicate asset not reported?</summary>

If multiple resources ship byte-identical copies, they already agree and there is no conflict to fix.

</details>

<details>

<summary>Why is a custom asset not reported?</summary>

If an asset has no vanilla original, Conflict Tool may intentionally skip it because there is no reliable baseline for deciding whether the difference represents a conflict.

</details>


# 📥 Installation

### 1. Requirements

17mov\_VendingMachines requires **oxmysql**.

Make sure `oxmysql` is installed and started before Vending Machines.

The resource automatically detects supported frameworks, inventories and interaction systems, so your framework, inventory and target resource should also start before Vending Machines.

### 2. Add the resource

Place the `17mov_VendingMachines` folder inside your server's `resources/` directory.

Then add this line to your `server.cfg`:

```cfg
ensure 17mov_VendingMachines
```

We recommend keeping it at the **end of `server.cfg`**, after your framework, inventory and target resources.

{% hint style="warning" %}
Start order matters. If Vending Machines starts before your framework or inventory, automatic bridge detection may fall back to standalone mode.
{% endhint %}

### 3. Database setup

The database is installed automatically by default when:

```lua
Config.InstallDatabaseAutomatically = true
```

### 4. Add the required items

Vending Machines uses two items:

| Item              | Purpose                                                                            |
| ----------------- | ---------------------------------------------------------------------------------- |
| `vending_machine` | Used to place a new vending machine                                                |
| `vending_tablet`  | Used to open the management tablet when tablet access is configured to use an item |

Choose your inventory below and copy the provided code into the indicated file or database table.

{% hint style="warning" %}
If you change `Config.RequireItem.ItemName` or `Config.Tablet.ItemName`, make sure the same item names are used in your inventory configuration.
{% endhint %}

{% tabs %}
{% tab title="QBCore" %}
Add the following entries to:

```
qb-core/shared/items.lua
```

```lua
['vending_machine'] = {
    name = 'vending_machine',
    label = 'Vending Machine',
    weight = 15000,
    type = 'item',
    image = 'vending_machine.png',
    unique = true,
    useable = true,
    shouldClose = true,
},

['vending_tablet'] = {
    name = 'vending_tablet',
    label = 'Vending Tablet',
    weight = 700,
    type = 'item',
    image = 'vending_tablet.png',
    unique = true,
    useable = true,
    shouldClose = true,
},
```

{% endtab %}

{% tab title="ESX" %}
If you use the default ESX database item system, run:

```sql
INSERT IGNORE INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
    ('vending_machine', 'Vending Machine', 15, 0, 1),
    ('vending_tablet', 'Vending Tablet', 1, 0, 1)
;
```

{% endtab %}

{% tab title="ox\_inventory" %}
Add the following entries to:

```
ox_inventory/data/items.lua
```

```lua
['vending_machine'] = {
    label = 'Vending Machine',
    weight = 15000,
    stack = false,
    close = true,
    consume = 0,
    server = {
        export = '17mov_VendingMachines.vending_machine'
    },
},

['vending_tablet'] = {
    label = 'Vending Tablet',
    weight = 700,
    stack = false,
    close = true,
    consume = 0,
    server = {
        export = '17mov_VendingMachines.vending_tablet'
    },
},
```

{% endtab %}
{% endtabs %}

### 5. Add item images

Copy the included item images to the image folder used by your inventory:

```
installation/images/vending_machine.png
installation/images/vending_tablet.png
```

### 6. Restart and verify

Restart your server after completing the installation.

If the resource reports that no supported framework or inventory was detected, check your resource start order and make sure `17mov_VendingMachines` starts after them.

{% hint style="info" %}
If automatic detection does not match your setup, bridge selection can be configured manually in `shared/bridge.lua` using `Config.Framework`, `Config.Inventory` and `Config.Interaction`.
{% endhint %}

### Next steps

Once Vending Machines is installed and running correctly, you can customize products, placement rules, the wholesaler, interactions and other behavior in the configuration.

[**⚙️ Continue to Configuration →**](https://docs.17movement.net/vending-machines/configuration)


# ⚙️ Configuration

Most server behavior can be customized from `configs/Config.lua`

### General

| Option                                | Purpose                                             |
| ------------------------------------- | --------------------------------------------------- |
| `Config.Debug`                        | Enables debug output.                               |
| `Config.DevMode`                      | Enables development-only functionality.             |
| `Config.InstallDatabaseAutomatically` | Automatically creates the required database tables. |
| `Config.Lang`                         | Selects the locale file, such as `en` or `pl`.      |

### Interaction

| Option                         | Purpose                                                    |
| ------------------------------ | ---------------------------------------------------------- |
| `Config.UseTarget`             | Uses ox\_target or qb-target when available.               |
| `Config.StandaloneInteraction` | Configures the built-in marker and key interaction system. |

The built-in interaction defaults include:

```lua
Key = 38 -- E
Distance = 2.0
DrawDistance = 12.0
```

Its refresh rate and marker definition are also configurable.

### Machine placement

| Option                         | Purpose                                                                    |
| ------------------------------ | -------------------------------------------------------------------------- |
| `Config.RequireItem`           | Requires an item to place a machine. If disabled, `createvending` is used. |
| `Config.RequireJob`            | Restricts placement to configured jobs and minimum grades.                 |
| `Config.Placement.MinDistance` | Minimum distance between machines. Default: `15.0`.                        |
| `Config.Placement.BlockOnRoad` | Prevents placement on roads.                                               |
| `Config.WhitelistedZones`      | Limits placement to allowed zones when configured.                         |
| `Config.BlacklistedZones`      | Blocks placement inside configured zones.                                  |

### Tablet

| Option                   | Purpose                                                 |
| ------------------------ | ------------------------------------------------------- |
| `Config.Tablet.OpenWith` | Chooses `command` or `item`.                            |
| `Config.Tablet.Command`  | Configurable tablet command. Default: `vendingMachine`. |
| `Config.Tablet.ItemName` | Item used to open the tablet.                           |

### Stock and products

| Option                       | Purpose                                                                                    |
| ---------------------------- | ------------------------------------------------------------------------------------------ |
| `Config.Stash.MaxPerSlot`    | Maximum number of items per one of the machine's 20 slots. `0` or `false` means unlimited. |
| `Config.DefaultProp`         | Fallback product prop.                                                                     |
| `Config.AllowAllProducts`    | Allows any inventory item to be sold.                                                      |
| `Config.BlacklistedProducts` | Case-insensitive blacklist that also blocks wholesaler orders.                             |
| `Config.Products`            | Per-item product configuration.                                                            |

Each entry in `Config.Products` can define:

* `price`
* `minPrice`
* `maxPrice`
* `prop`
* `max`, with a default of 7 product props in a slot
* `box`
* `step`
* `offset`
* `elevatorOffset`
* `holdOffset`
* `holdRotation`
* `onBuy`

### Removing original GTA vending props

`Config.RemoveProps` can remove original GTA vending machine props from the world.

It supports:

* `all`
* `radius`

The configuration contains a list of 12 original props. Custom Vending Machines placed by this resource are never removed by this system.

### Payout and collection

| Option                    | Purpose                                                                                |
| ------------------------- | -------------------------------------------------------------------------------------- |
| `Config.Payout.Account`   | Chooses `cash` or `bank` for collected revenue.                                        |
| `Config.Collect.Duration` | Controls the vending machine door-opening duration during collection, in milliseconds. |

### Wholesaler

`Config.Wholesaler` controls the complete ordering and delivery system, including:

* Payment account
* `BoxSize`
* Product catalog in `Prices`
* Wholesaler ped
* Delivery van, default `speedo`
* Vehicle spawn points
* `returnVehicle`
* Deposit settings
* Blips
* Vehicle return point

The default deposit is $500. By default, abandoning the van for more than 10 minutes causes a 24-hour delivery block.


# 🔌 Exports & API

Vending Machines exposes client and server exports for custom integrations such as phones, HUDs and other resources.

### Client exports

Client exports are available from `client/editable.lua` and include machine data, sales, history, settings, workers, wholesaler data and access checks.

[**View Client Exports**](https://docs.17movement.net/vending-machines/exports-and-api/client-exports)

{% hint style="info" %}
The editable integration files are outside escrow, including `client/editable.lua` and the complete `bridge/` directory.
{% endhint %}


# Client Exports

### `getOwnedMachines`

Returns all vending machines the player owns or works at.

#### Usage

```lua
local machines = exports['17mov_VendingMachines']:getOwnedMachines()
```

#### Returns

An array of machines:

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td><code>number</code></td><td>Machine ID</td></tr><tr><td><code>coords</code></td><td><code>table</code></td><td>Machine coordinates</td></tr><tr><td><code>balance</code></td><td><code>number</code></td><td>Current machine balance</td></tr><tr><td><code>stock.used</code></td><td><code>number</code></td><td>Currently used stock space</td></tr><tr><td><code>stock.total</code></td><td><code>number</code></td><td>Total stock capacity</td></tr><tr><td><code>label</code></td><td><code>string?</code></td><td>Custom machine label</td></tr><tr><td><code>role</code></td><td><code>string</code></td><td>Player's role for this machine</td></tr><tr><td><code>permissions</code></td><td><code>table</code></td><td>Player's permissions for this machine</td></tr><tr><td><code>street</code></td><td><code>string</code></td><td>Street name resolved from the machine location</td></tr></tbody></table>

#### Example

```lua
local machines = exports['17mov_VendingMachines']:getOwnedMachines()

for _, machine in ipairs(machines) do
    print(machine.id, machine.label, machine.balance)
end
```

***

### `getMapMachines`

Returns a lighter version of the player's machines intended for map integrations.

Unlike `getOwnedMachines`, this does not include labels or detailed permissions.

#### Usage

```lua
local machines = exports['17mov_VendingMachines']:getMapMachines()
```

#### Returns

An array of machines:

| Field             | Type     | Description                    |
| ----------------- | -------- | ------------------------------ |
| `id`              | `number` | Machine ID                     |
| `role`            | `string` | Player's role for this machine |
| `balance`         | `number` | Current machine balance        |
| `stock`           | `table`  | Current stock information      |
| `location.coords` | `table`  | Machine coordinates            |

#### Example

```lua
local machines = exports['17mov_VendingMachines']:getMapMachines()

for _, machine in ipairs(machines) do
    print(machine.id, machine.location.coords)
end
```

***

## Sales & History

### `getSales`

Returns revenue statistics from the last 7 days across all machines the player owns.

The entries are returned from the oldest day to the newest.

#### Usage

```lua
local sales = exports['17mov_VendingMachines']:getSales()
```

#### Returns

| Field     | Type     | Description                |
| --------- | -------- | -------------------------- |
| `date`    | `string` | Date of the entry          |
| `count`   | `number` | Number of purchases        |
| `revenue` | `number` | Revenue generated that day |

#### Example

```lua
local sales = exports['17mov_VendingMachines']:getSales()

for _, day in ipairs(sales) do
    print(day.date, day.count, day.revenue)
end
```

***

### `getHistory`

Returns purchase history for the player's vending machines.

Each call returns up to **25 purchases**, newest first.

#### Usage

```lua
local history = exports['17mov_VendingMachines']:getHistory(machineId, before)
```

#### Parameters

| Parameter   | Type     | Required | Description                                                         |
| ----------- | -------- | -------- | ------------------------------------------------------------------- |
| `machineId` | `number` | ❌        | Machine to filter by. Leave empty to include all owned machines     |
| `before`    | `number` | ❌        | ID of the last purchase from the previous page, used for pagination |

#### Returns

<table data-search="false"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td><code>number</code></td><td>Purchase ID</td></tr><tr><td><code>machine_id</code></td><td><code>number</code></td><td>Machine ID</td></tr><tr><td><code>buyer_name</code></td><td><code>string</code></td><td>Buyer name</td></tr><tr><td><code>item_name</code></td><td><code>string</code></td><td>Inventory item name</td></tr><tr><td><code>item_label</code></td><td><code>string</code></td><td>Display label of the item</td></tr><tr><td><code>price</code></td><td><code>number</code></td><td>Purchase price</td></tr><tr><td><code>created_at</code></td><td><code>number</code></td><td>Purchase timestamp</td></tr></tbody></table>

#### Example

```lua
local history = exports['17mov_VendingMachines']:getHistory(12)

for _, purchase in ipairs(history) do
    print(purchase.buyer_name, purchase.item_label, purchase.price)
end
```

#### Pagination

Pass the ID of the last entry from the previous page:

```lua
local nextPage = exports['17mov_VendingMachines']:getHistory(12, 1500)
```

***

## Machine Settings

### `getMachineSettings`

Returns editable settings for a machine.

This returns `nil` if the player is not the owner.

#### Usage

```lua
local settings = exports['17mov_VendingMachines']:getMachineSettings(machineId)
```

#### Parameters

| Parameter   | Type     | Required | Description |
| ----------- | -------- | -------- | ----------- |
| `machineId` | `number` | ✅        | Machine ID  |

#### Returns

| Field               | Type      | Description                      |
| ------------------- | --------- | -------------------------------- |
| `label`             | `string?` | Custom machine label             |
| `lowStockThreshold` | `number`  | Low-stock notification threshold |

Or `nil` if the player does not own the machine.

#### Example

```lua
local settings = exports['17mov_VendingMachines']:getMachineSettings(12)

if settings then
    print(settings.label, settings.lowStockThreshold)
end
```

***

### `saveMachineSettings`

Updates the machine label and low-stock threshold.

#### Usage

```lua
local result = exports['17mov_VendingMachines']:saveMachineSettings(
    machineId,
    label,
    lowStockThreshold
)
```

#### Parameters

| Parameter           | Type      | Required | Description             |
| ------------------- | --------- | -------- | ----------------------- |
| `machineId`         | `number`  | ✅        | Machine ID              |
| `label`             | `string?` | ❌        | New machine label       |
| `lowStockThreshold` | `number`  | ✅        | New low-stock threshold |

#### Returns

| Field               | Type      | Description                     |
| ------------------- | --------- | ------------------------------- |
| `success`           | `boolean` | Whether the settings were saved |
| `message`           | `string?` | Optional response message       |
| `label`             | `string?` | Saved machine label             |
| `lowStockThreshold` | `number?` | Saved low-stock threshold       |

#### Example

```lua
local result = exports['17mov_VendingMachines']:saveMachineSettings(
    12,
    'Legion Vending',
    5
)

if result.success then
    print('Settings saved')
end
```

***

## Notifications

### `getNotifications`

Returns the player's current notification preferences.

#### Usage

```lua
local notifications = exports['17mov_VendingMachines']:getNotifications()
```

#### Returns

| Field        | Type      | Description             |
| ------------ | --------- | ----------------------- |
| `lowStock`   | `boolean` | Low-stock notifications |
| `sales`      | `boolean` | Sales notifications     |
| `deliveries` | `boolean` | Delivery notifications  |

#### Example

```lua
local notifications = exports['17mov_VendingMachines']:getNotifications()

print(notifications.lowStock)
print(notifications.sales)
print(notifications.deliveries)
```

***

### `saveNotifications`

Updates the player's notification preferences.

You only need to provide the options you want to change.

#### Usage

```lua
local result = exports['17mov_VendingMachines']:saveNotifications({
    lowStock = true,
    sales = true,
    deliveries = false
})
```

#### Parameters

| Field        | Type       | Required | Description                               |
| ------------ | ---------- | -------- | ----------------------------------------- |
| `lowStock`   | `boolean?` | ❌        | Enable or disable low-stock notifications |
| `sales`      | `boolean?` | ❌        | Enable or disable sales notifications     |
| `deliveries` | `boolean?` | ❌        | Enable or disable delivery notifications  |

#### Returns

| Field           | Type      | Description                        |
| --------------- | --------- | ---------------------------------- |
| `success`       | `boolean` | Whether the preferences were saved |
| `message`       | `string?` | Optional response message          |
| `notifications` | `table?`  | Updated notification preferences   |

***

## Workers

### `getWorkers`

Returns all workers assigned to a machine.

#### Usage

```lua
local workers = exports['17mov_VendingMachines']:getWorkers(machineId)
```

#### Parameters

| Parameter   | Type     | Required | Description |
| ----------- | -------- | -------- | ----------- |
| `machineId` | `number` | ✅        | Machine ID  |

#### Returns

| Field         | Type                     | Description        |
| ------------- | ------------------------ | ------------------ |
| `identifier`  | `string`                 | Worker identifier  |
| `name`        | `string`                 | Worker name        |
| `permissions` | `table<string, boolean>` | Worker permissions |

#### Example

```lua
local workers = exports['17mov_VendingMachines']:getWorkers(12)

for _, worker in ipairs(workers) do
    print(worker.name, worker.identifier)
end
```

***

### `addWorker`

Adds an online player as a worker.

The player specified by `targetId` must currently be online.

#### Usage

```lua
local result = exports['17mov_VendingMachines']:addWorker(
    machineId,
    targetId,
    permissions
)
```

#### Parameters

| Parameter     | Type                     | Required | Description                     |
| ------------- | ------------------------ | -------- | ------------------------------- |
| `machineId`   | `number`                 | ✅        | Machine ID                      |
| `targetId`    | `number`                 | ✅        | Server ID of the player to hire |
| `permissions` | `table<string, boolean>` | ✅        | Worker permissions              |

Available permissions:

| Permission | Description             |
| ---------- | ----------------------- |
| `restock`  | Restock the machine     |
| `order`    | Place wholesaler orders |
| `collect`  | Collect machine revenue |
| `workers`  | Manage workers          |

#### Returns

| Field       | Type       | Description                  |
| ----------- | ---------- | ---------------------------- |
| `success`   | `boolean`  | Whether the worker was added |
| `message`   | `string?`  | Optional response message    |
| `employees` | `table[]?` | Updated worker list          |

#### Example

```lua
local result = exports['17mov_VendingMachines']:addWorker(12, 27, {
    restock = true,
    order = true,
    collect = false,
    workers = false
})
```

***

### `setWorkerPermissions`

Updates permissions for an existing worker.

Use the `identifier` returned by `getWorkers()`.

#### Usage

```lua
local result = exports['17mov_VendingMachines']:setWorkerPermissions(
    machineId,
    identifier,
    permissions
)
```

#### Parameters

| Parameter     | Type                     | Required | Description                                  |
| ------------- | ------------------------ | -------- | -------------------------------------------- |
| `machineId`   | `number`                 | ✅        | Machine ID                                   |
| `identifier`  | `string`                 | ✅        | Worker identifier returned by `getWorkers()` |
| `permissions` | `table<string, boolean>` | ✅        | New permission set                           |

#### Returns

| Field       | Type       | Description                          |
| ----------- | ---------- | ------------------------------------ |
| `success`   | `boolean`  | Whether the permissions were updated |
| `message`   | `string?`  | Optional response message            |
| `employees` | `table[]?` | Updated worker list                  |

#### Example

```lua
local result = exports['17mov_VendingMachines']:setWorkerPermissions(
    12,
    identifier,
    {
        restock = true,
        order = true,
        collect = true,
        workers = false
    }
)
```

***

### `removeWorker`

Removes a worker from a machine.

#### Usage

```lua
local result = exports['17mov_VendingMachines']:removeWorker(
    machineId,
    identifier
)
```

#### Parameters

| Parameter    | Type     | Required | Description       |
| ------------ | -------- | -------- | ----------------- |
| `machineId`  | `number` | ✅        | Machine ID        |
| `identifier` | `string` | ✅        | Worker identifier |

#### Returns

| Field       | Type       | Description                    |
| ----------- | ---------- | ------------------------------ |
| `success`   | `boolean`  | Whether the worker was removed |
| `message`   | `string?`  | Optional response message      |
| `employees` | `table[]?` | Updated worker list            |

***

## Wholesaler

### `getWholesalerCatalog`

Returns all products currently available from the wholesaler.

Products are returned with their inventory label and image.

#### Usage

```lua
local catalog = exports['17mov_VendingMachines']:getWholesalerCatalog()
```

#### Returns

| Field   | Type      | Description         |
| ------- | --------- | ------------------- |
| `item`  | `string`  | Inventory item name |
| `label` | `string`  | Display label       |
| `price` | `number`  | Wholesaler price    |
| `image` | `string?` | Item image          |

#### Example

```lua
local catalog = exports['17mov_VendingMachines']:getWholesalerCatalog()

for _, product in ipairs(catalog) do
    print(product.label, product.price)
end
```

***

### `getWholesalerOrders`

Returns wholesaler orders for a machine.

#### Usage

```lua
local orders = exports['17mov_VendingMachines']:getWholesalerOrders(machineId)
```

#### Parameters

| Parameter   | Type     | Required | Description |
| ----------- | -------- | -------- | ----------- |
| `machineId` | `number` | ✅        | Machine ID  |

#### Returns

Each order contains:

| Field    | Type      | Description                 |
| -------- | --------- | --------------------------- |
| `id`     | `number`  | Order ID                    |
| `status` | `string`  | Current order status        |
| `items`  | `table[]` | Items included in the order |

Each item contains:

| Field        | Type     | Description              |
| ------------ | -------- | ------------------------ |
| `item_name`  | `string` | Inventory item name      |
| `item_label` | `string` | Display label            |
| `quantity`   | `number` | Ordered quantity         |
| `remaining`  | `number` | Quantity still remaining |

#### Example

```lua
local orders = exports['17mov_VendingMachines']:getWholesalerOrders(12)

for _, order in ipairs(orders) do
    print(order.id, order.status)
end
```

***

### `getWholesalerCapacity`

Returns how much of each product can still fit inside a machine.

The returned table is indexed by item name.

#### Usage

```lua
local capacity = exports['17mov_VendingMachines']:getWholesalerCapacity(machineId)
```

#### Parameters

| Parameter   | Type     | Required | Description |
| ----------- | -------- | -------- | ----------- |
| `machineId` | `number` | ✅        | Machine ID  |

#### Returns

```lua
table<string, number> | false
```

For example:

```lua
{
    water = 20,
    cola = 15
}
```

Returns `false` if capacity could not be retrieved.

#### Example

```lua
local capacity = exports['17mov_VendingMachines']:getWholesalerCapacity(12)

if capacity then
    for item, amount in pairs(capacity) do
        print(item, amount)
    end
end
```

***

### `placeWholesalerOrder`

Places a wholesaler order and charges the configured account.

#### Usage

```lua
local result = exports['17mov_VendingMachines']:placeWholesalerOrder(
    machineId,
    cart
)
```

#### Parameters

| Parameter   | Type      | Required | Description       |
| ----------- | --------- | -------- | ----------------- |
| `machineId` | `number`  | ✅        | Machine ID        |
| `cart`      | `table[]` | ✅        | Products to order |

Each cart entry must contain:

| Field      | Type     | Description         |
| ---------- | -------- | ------------------- |
| `item`     | `string` | Inventory item name |
| `quantity` | `number` | Quantity to order   |

#### Returns

| Field     | Type       | Description                  |
| --------- | ---------- | ---------------------------- |
| `success` | `boolean`  | Whether the order was placed |
| `message` | `string?`  | Optional response message    |
| `total`   | `number?`  | Total order value            |
| `orders`  | `table[]?` | Updated order list           |

#### Example

```lua
local result = exports['17mov_VendingMachines']:placeWholesalerOrder(12, {
    { item = 'water', quantity = 10 },
    { item = 'cola', quantity = 5 }
})

if result.success then
    print('Order placed for $' .. result.total)
end
```

***

## Access

### `hasAccess`

Returns whether the player owns or works at any vending machine.

This is useful when you only need a simple yes/no check before opening your own UI or integration.

#### Usage

```lua
local hasAccess = exports['17mov_VendingMachines']:hasAccess()
```

#### Returns

| Type      | Description                                                        |
| --------- | ------------------------------------------------------------------ |
| `boolean` | `true` if the player owns or works at at least one vending machine |

#### Example

```lua
if exports['17mov_VendingMachines']:hasAccess() then
    print('Player can access vending management')
end
```


# 🌐 Discord Webhooks

Vending Machines can send selected vending activity to Discord using configurable webhook URLs.

Webhook configuration is stored in:

```
configs/ApiKeys.lua
```

### Main settings

`Config.Webhooks` contains:

| Option    | Purpose                              |
| --------- | ------------------------------------ |
| `Enabled` | Enables or disables webhook logging. |
| `Name`    | Name displayed by the webhook.       |
| `Color`   | Embed color.                         |
| `Urls`    | Per-event webhook URLs.              |

### Supported webhook events

You can configure separate URLs for:

* `OrderPlaced`
* `DeliveryStarted`
* `DeliveryFinished`
* `Purchase`
* `Restock`

If an event URL is empty, that event is skipped.

### Customizing embeds

Webhook embed layout can be edited in:

```
server/webhook/webhook.lua
```

{% hint style="info" %}
`configs/ApiKeys.lua` and `server/webhook/webhook.lua` are outside escrow and can be customized.
{% endhint %}


# Dependencies

The **17mov\_Phone** resource has minimal requirements.&#x20;

{% hint style="info" %}
The only required dependency is: `oxmysql`.
{% endhint %}

Apart from that, the script is fully **standalone** - it does not require any framework (QB-Core, ESX, OX, VRP, etc.). The entire system is built in a *plug-and-play* style, so the resource integrates easily with many popular systems out of the box.

Below is a list of resources supported by default:

<table><thead><tr><th width="122">Framework</th><th width="154">Housing</th><th width="157">Inventory</th><th width="124">Voice</th><th width="153">Banking</th><th width="205">Garages</th><th width="98">Target</th></tr></thead><tbody><tr><td>esx</td><td>esx_property</td><td>codem-inventory</td><td>mumble-voip</td><td>esx_society</td><td>cd_garage</td><td>qb-target</td></tr><tr><td>ox</td><td>loaf_housing</td><td>esx_inventory</td><td>pma-voice</td><td>fd_banking</td><td>esx_advancedgarage</td><td>ox_target</td></tr><tr><td>qb</td><td>ps-housing</td><td>ox_inventory</td><td>saltychat</td><td>ox_core</td><td>jg-advancedgarages</td><td>qtarget</td></tr><tr><td>vrp</td><td>qb-houses</td><td>ps-inventory</td><td></td><td>p_banking</td><td>loaf_garage</td><td></td></tr><tr><td>standalone</td><td>qs-housing</td><td>qb-inventory</td><td></td><td>qb-banking</td><td>lunar_garage</td><td></td></tr><tr><td></td><td>vms_housing</td><td>qs-inventory</td><td></td><td>renewed-banking</td><td>okokGarage</td><td></td></tr><tr><td></td><td>rx_housing</td><td>core_inventory</td><td></td><td>tgg-banking</td><td>qb_garages</td><td></td></tr><tr><td></td><td>nolag_properties</td><td>tgiann_inventory</td><td></td><td>tgiann-bank</td><td>qbx_garages</td><td></td></tr><tr><td></td><td><a href="https://www.vames-store.com/package/6923949">vms_housing</a></td><td>origen_inventory</td><td></td><td>wasabi_banking</td><td><a href="https://zsx-development.tebex.io/package/7176048">ZSX_Garages</a></td><td></td></tr><tr><td></td><td>bcs_housing</td><td>tgiann-inventory</td><td></td><td>okokBanking</td><td>vms_garages</td><td></td></tr><tr><td></td><td><a href="https://rtx.tebex.io/package/7181359">rtx_housing</a></td><td></td><td></td><td></td><td></td><td></td></tr></tbody></table>

If your scripts are not on the list — it does **not** mean they are incompatible. All functions that interact with external systems are modular and open to modification. You can add your own integrations or extend existing adapters to work with any framework or custom system.


# Installation

This guide describes the full installation process and the initial setup of the **17mov\_Phone** resource.\
The script is designed to require as few manual steps as possible - most configuration tasks are handled automatically.

***

{% stepper %}
{% step %}

### Downloading and adding the resource

1. Download the resource from the official [**Cfx.re portal**](https://portal.cfx.re/).
2. Extract the downloaded package.
3. Move the entire folder: `17mov_Phone` into your server’s: `resources` directory.
4. Add the following line to your `server.cfg` to start the resource: `ensure 17mov_Phone`. This guarantees the script will load automatically on every server restart.
   {% endstep %}

{% step %}

### Add permissions for Music App ( Optional )

If you want to use Music App on your server, you need to add permissions for our script\
1\. Open your `server.cfg`\
2\. Insert this 2 lines before starting 17mov\_Phone script

```
add_unsafe_worker_permission 17mov_Phone
add_unsafe_child_process_permission 17mov_Phone
```

This process is mandatory, **Music App will not work without it**
{% endstep %}

{% step %}

### Add Inventory Items

The next step is to add the required items:&#x20;`simcard`, `phone`, and `broken_phone`.

Follow the guides below for your specific framework or inventory system:

{% tabs %}
{% tab title="qb-core" %}

1. Go to your qb-core folder.
2. Open the file: /shared/items.lua
3. Scroll to the end of the file.
4. Before the closing } add the following code:

```lua
    phone                        = { name = 'phone', label = 'Phone', weight = 700, type = 'item', image = 'phone.png', unique = true, useable = true, shouldClose = false, description = 'Phone' },
    simcard                      = { name = 'simcard', label = 'SIM Card', weight = 1, type = 'item', image = 'simcard.png', unique = true, useable = true, shouldClose = false,  description = 'Use to install in your phone' },
    broken_phone                 = { name = 'broken_phone', label = 'broken_phone', weight = 700, type = 'item', image = 'broken_phone.png', unique = true, useable = false, shouldClose = false, description = 'Broken Phone' },
```

{% endtab %}

{% tab title="DEFAULT ESX" %}

1. Run this SQL query in your database:

```
    INSERT IGNORE INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
        ('phone', 'Phone', 1, 0, 1),
        ('simcard', 'Sim card', 1, 0, 1),
        ('broken_phone', 'Broken Phone', 1, 0, 1),
    ;
```

{% endtab %}

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

1. Go to your ox\_inventory folder.
2. Open modules/items/client.lua
3. Find and remove this code:  (Usually around 132 line)<br>

   ```lua
   Item('phone', function(data, slot)
   	local success, result = pcall(function()
   		return exports.npwd:isPhoneVisible()
   	end)
   	
   	if success then
   		exports.npwd:setPhoneVisible(not result)
   	end
   end)
   ```
4. Go back to main ox\_inventory folder and open the file: /data/items.lua
5. Scroll to the end of the file.
6. Before the closing } add the following code:

```lua
    ['phone'] = {
        label = 'Phone',
        weight = 150,
        client = {
            image = 'phone.png',
        },
        server = {
            export = '17mov_Phone.phone'
        },
        consume = 0,
        stack = false
    },

    ['simcard'] = {
        label = 'Sim Card',
        weight = 150,
        client = {
            image = 'simcard.png',
        },
        server = {
            export = '17mov_Phone.simcard'
        },
        consume = 0,
        stack = false
    },

    ['broken_phone'] = {
        label = 'Broken Phone',
        weight = 150,
        client = {
            image = 'broken_phone.png',
        },
        consume = 0,
        stack = false
    },

```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
**IMPORTANT:** Before adding items, ensure that the "phone" item didn't **previously exist** in your framework. In QB-Core, ox\_inventory, and many other popular inventories, it is already defined. Before adding ours, first remove your current phone; otherwise, the script likely won't work at all.
{% endhint %}
{% endstep %}

{% step %}

### Installing images into your inventory system

Inside the resource you will find: `installation/images` This folder contains example icons meant for integration with the inventory system used on your server (e.g., qb-inventory, ox\_inventory, etc.).

**Steps:**

1. Copy all files from the `installation/images` folder.
2. Paste them into the assets folder of your inventory system, for example:

| qb-inventory | ox\_inventory | ps-inventory | esx\_inventory  |
| ------------ | ------------- | ------------ | --------------- |
| /html/images | /web/images   | /html/images | /html/img/items |

> You can use your own custom icons if you want to keep your server’s unique visual style.
> {% endstep %}

{% step %}

### Database installation

The database structure is created **automatically** on the first start of the script. If the script detects that required tables do not exist, it will create them automatically.
{% endstep %}

{% step %}

### First launch of the phone

After a successful installation:

1. Join your server.
2. Give your character the required items: `phone`&#x20;
3. You can open the phone in two ways:

**From your inventory:**

Use the `phone` item directly inside your inventory.

**Using a keybind:**

Default key: `tilde`. This key opens the **last used** phone assigned to the player.

{% endstep %}

{% step %}

### Configure Api Keys

This step is optional, but skipping it may cause some apps, like the camera or video calls, to malfunction. We described this section later in [Configure ApiKeys](/phone/configure-apikeys)
{% endstep %}

{% step %}

### Done! 🎉

After completing all steps, the script is fully operational and ready to use on your FiveM server.

### Migrating data from your previous phone system

If you previously used a different phone script, it is possible to transfer selected data (such as contacts, messages, photos, SIM numbers, etc.) into the new **17mov\_Phone** system.

Migration options and supported formats are described in next page

{% endstep %}
{% endstepper %}


# Migration

Our script includes a built-in migration tool that helps you transfer data from the phone system previously used on your server.

Due to structural differences between various phone scripts, not all data types can be migrated from every resource.

The table below shows which data can be successfully transferred from each supported phone script:

<table><thead><tr><th width="167" align="center">DATA</th><th width="116" align="center">LB-Phone</th><th width="125" align="center">High-Phone</th><th width="142" align="center">QS-Phone-Pro</th><th width="97" align="center">YSeries</th><th align="center">QB-Phone</th></tr></thead><tbody><tr><td align="center">Phone Numbers</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td align="center">Settings</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td></tr><tr><td align="center">Contacts</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td align="center">Gallery</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td align="center">SMS</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td align="center">Voice Recordings</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td align="center">Recent Calls</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td></tr><tr><td align="center">Qwuaker</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td align="center">Notes</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td></tr><tr><td align="center">Mail</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td align="center">DarkChat</td><td align="center">✅</td><td align="center">❌</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td align="center">Peargram</td><td align="center">✅</td><td align="center">❌</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td align="center">Alarms</td><td align="center">✅</td><td align="center">❌</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td></tr><tr><td align="center">Map Waypoints</td><td align="center">✅</td><td align="center">❌</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td></tr><tr><td align="center">Swiply</td><td align="center">❌</td><td align="center">❌</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr></tbody></table>

{% hint style="danger" %}
**Why can’t we migrate all data?**

We have made every effort to migrate as much data as technically possible. However, in several cases a full migration is not feasible due to structural differences between systems.

#### High-Phone

Most applications in High-Phone do not support user account creation, while our system requires each app to operate under a user account. Because of this fundamental difference, applications without an account system cannot be migrated.

#### QS-Phone-Pro

We successfully migrate the majority of QS-Phone-Pro data.\
However, certain individual phones may not be transferred. This is due to the way Quasar stores its data: many values are saved inside the item’s metadata. Phones stored inside gloveboxes, vehicles, stashes, or any container other than the player’s inventory may not be detected by the migration script, and therefore cannot be processed.

#### YSeries

YSeries contains a critical issue that prevents us from transferring passwords.\
There is a flaw in the password-encryption mechanism where a single encrypted value corresponds to multiple valid password combinations.\
For example: if you create an account with the password VWZt1234, you can also log into the same account with: VWtt1234, VWZZ1234, or VWtZ1234.\
Because of this behavior, securely migrating passwords without compromising account integrity is not possible.
{% endhint %}

## How to run the migration?

{% stepper %}
{% step %}

### Create a backup of your database

Please do not skip this step. The migration performs many operations on your database, and if any error occurs, you may need to restore your backup.
{% endstep %}

{% step %}

### Start Migrate

In your server console, use the command `migrate` and provide the name of your previous phone script as the first argument.

Supported options:

\- lb-phone&#x20;

\- high-phone&#x20;

\- qs-smartphone-pro&#x20;

\- qb-phone&#x20;

\- yseries-phone

Example: `migrate lb-phone` or `migrate high-phone`&#x20;

This command will begin migrating data from lb-phone into 17mov\_Phone.
{% endstep %}

{% step %}

### Confirm Migrate

This action is irreversible. For safety, the system requires a second confirmation step.&#x20;

To confirm the migration, run: `migrateconfirm`&#x20;

After confirmation, the console will show detailed logs of the entire process. When the migration is complete, all transferred data will be available inside 17mov\_Phone.
{% endstep %}

{% step %}

### Restart the 17mov\_Phone

Run `ensure 17mov_Phone` in your server console.
{% endstep %}
{% endstepper %}


# Configure ApiKeys

Our product requires several API Access Keys to function fully. Some of these keys require creating accounts on external platforms, and all steps are explained below.&#x20;

All API keys can be configured inside: `/configs/ApiKeys.lua`&#x20;

The required keys include:&#x20;

* **WebRTC API** - used for video calls and the nearby voices feature
* **FiveManage** - a CDN platform used to store all media data such as photos, videos, voice messages, etc.

## WebRTC API

Our phone uses a Cloudflare TURN server. Cloudflare offers this service for free (with generous limits).\
To create your TURN server, follow these steps:

{% stepper %}
{% step %}
Go to the official [Cloudflare dashboard](https://dash.cloudflare.com/), create an account, and then navigate to: [TURN SERVERS](https://dash.cloudflare.com/?to=/:account/realtime/turn/overview)
{% endstep %}

{% step %}
Click the "CREATE" button and optionally enter a name for your TURN server.
{% endstep %}

{% step %}
Save the Turn Token ID and API Token that appear on the screen. Do not share these credentials with anyone.
{% endstep %}

{% step %}
Open the file: `/configs/ApiKeys.lua` and paste:

* the API Token into `ApiToken`
* the Turn Token ID into `TurnTokenID`
  {% endstep %}

{% step %}
Complete the setup by clicking Finish on the Cloudflare panel.
{% endstep %}
{% endstepper %}

## FiveManage

To generate your FiveManage API Key, follow these steps:

{% stepper %}
{% step %}
Go to the official [FiveManage ](https://fivemanage.com/)website and create an account.
{% endstep %}

{% step %}
Create a new team by clicking "Create New Team".
{% endstep %}

{% step %}
After entering the dashboard, open the "Tokens" tab from the left navigation menu.
{% endstep %}

{% step %}
Create a new token by clicking "Create Token".

{% endstep %}

{% step %}
Optionally enter a name for the token and select the "Media" type.

Copy the generated token and paste it into: `/configs/ApiKeys.lua` and `API.FiveManage`
{% endstep %}
{% endstepper %}

**Done**! Now you can use all features that 17mov\_Phone brings to you!


# Exports

Welcome to the official developer documentation for 17mov\_Phone. This section provides detailed information about the server-side exports available for developers. You can use these exports to integra


# GLOBAL EXPORTS

This section contains helper exports that are not tied to a specific application but provide general utility functions related to the phone system. These are useful for managing phone numbers, retrieving player information, controlling phone accessibility, or interacting with the phone's UI (e.g., Streamer Mode).

#### Available Exports

Below is a quick reference list of global exports available.

### **Server-Side:**

* **`GeneratePhoneNumber`**
* **`GetIdentifierFromNumber`**
* **`GetNumberFromPlayer`**
* **`GetPlayerSrcFromActiveNumber`**
* **`SetPlayerPhoneBlockState`**

### **Client-Side:**

* **`OpenPhone`**
* **`ClosePhone`**
* **`SetPlayerPhoneBlockState`**
* **`HasPhoneItem`**
* **`IsPhoneOpen`**
* **`CreateNotification`**
* **`ToggleFlashlight`**
* **`GetFlashlightState`**
* **`SetStreamerModeState`**
* **`GetStreamerModeState`**
* **`OpenApp`**
* **`CloseApp`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

### Generate Phone Number

Generates a new, unique, random phone number formatted according to the server's configuration.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local newNumber = exports["17mov_Phone"]:GeneratePhoneNumber()
```

{% endcode %}

**Returns:**

* `string`: The generated phone number.

### Get Identifier From Number

Retrieves the player identifier (license) associated with a specific phone number. This function works even if the player is offline, as it checks the database.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local identifier = exports["17mov_Phone"]:GetIdentifierFromNumber(number)
```

{% endcode %}

**Returns:**

* `string`: The identifier assigned to the given phone number.

<table><thead><tr><th width="124" align="center">Argument</th><th width="158" align="center">Type</th><th width="104" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number | string</code></td><td align="center">❌</td><td>A target phone number</td></tr></tbody></table>

### Get Number From Player

Retrieves the phone number currently active on a player's device using their Server ID (Source).

<pre class="language-lua" data-overflow="wrap" data-line-numbers><code class="lang-lua"><strong>local phoneNumber = exports["17mov_Phone"]:GetNumberFromPlayer(src)
</strong></code></pre>

**Returns:**

* `string`: The active phone number of the player.

<table><thead><tr><th width="116" align="center">Argument</th><th width="89" align="center">Type</th><th width="109" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr></tbody></table>

### GetNumberFromIdentifier

Retrieves the phone number currently in use by a player based on their unique identifier

```lua
local playerNumber = exports["17mov_Phone"]:GetNumberFromIdentifier(identifier)
```

#### Returns:

* `string` / `nil` playerNumber: The phone number currently used by the player, or `nil` if the player is offline or has no active number.

#### Arguments:

<table><thead><tr><th width="116" align="center">Argument</th><th width="89" align="center">Type</th><th width="109" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">identifier</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Player identifier</td></tr></tbody></table>

### Get Player Source From Number

Retrieves the Server ID (Source) of an online player currently using the specified phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local src = exports["17mov_Phone"]:GetPlayerSrcFromActiveNumber(number)
```

{% endcode %}

**Returns:**

* `number` | `nil`: The player's server ID if they are online and using the number, otherwise `nil`.

<table><thead><tr><th width="116" align="center">Argument</th><th width="156" align="center">Type</th><th width="106" align="center">Optional</th><th align="center">Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number | string</code></td><td align="center">❌</td><td align="center">A target phone number</td></tr></tbody></table>

### Set Phone Block State

Blocks or unblocks a player from using their phone. When blocked, the player cannot open the phone interface. This is useful for situations like being handcuffed, unconscious, or in a restricted zone.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:SetPlayerPhoneBlockState(src, state)
```

{% endcode %}

<table><thead><tr><th width="116" align="center">Argument</th><th width="95" align="center">Type</th><th width="108" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">state</td><td align="center"><code>boolean</code></td><td align="center">❌</td><td><code>true</code> to block the phone, <code>false</code> to unblock.</td></tr></tbody></table>

### Change User Password

Changes the user password for a specific application account (e.g., Darkchat, Swiply). This is useful for "forgot password" mechanics or admin tools.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:ChangeUserPassword(app, username, newPassword)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the password was changed successfully.

<table><thead><tr><th width="132" align="center">Argument</th><th width="89" align="center">Type</th><th width="109" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">app</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The internal name of the app (e.g., <code>"darkchat"</code>, <code>"swiply"</code>).</td></tr><tr><td align="center">username</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The username of the account.</td></tr><tr><td align="center">newPassword</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The new password to set.</td></tr></tbody></table>

### Send Notification (By Source)

Sends a notification to a player using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:SendNotificationToSrc(src, notification)
```

{% endcode %}

<table><thead><tr><th width="112" align="center">Argument</th><th width="124" align="center">Type</th><th width="106" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">notification</td><td align="center"><code>Notification</code></td><td align="center">❌</td><td>The notification data object.</td></tr></tbody></table>

<details>

<summary>Notification Structure</summary>

```
{
    app = "MESSAGES", -- App identifier (e.g., "MESSAGES", "BANK", "SYSTEM")
    title = "New Alert", -- Title string or ReplaceType object
    message = "This is a test notification", -- Message string or ReplaceType object
    data = {
        href = "/messages/1", -- (Optional) Internal link to open on click
        alwaysShow = true -- (Optional) Show even if app is open
    }
}
```

</details>

<details>

<summary>ReplaceType Structure (Localization)</summary>

Use this structure if you want to use localized strings with dynamic replacements.

```
{
    key = "LocaleKey", -- e.g., "Messages:NewMessage"
    replace = "%s", -- Placeholder to replace
    value = "John Doe" -- Value to insert
}
```

</details>

### Send Notification (By Number)

Sends a notification to a player using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:SendNotificationToNumber(number, notification)
```

{% endcode %}

<table><thead><tr><th width="114" align="center">Argument</th><th width="139" align="center">Type</th><th width="115" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The target phone number.</td></tr><tr><td align="center">notification</td><td align="center"><code>Notification</code></td><td align="center">❌</td><td>The notification data object (same structure as above).</td></tr></tbody></table>

### SendNotificationToEveryone

Sends a notification to all players

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:SendNotificationToEveryone(notification)
```

{% endcode %}

<table><thead><tr><th width="114" align="center">Argument</th><th width="139" align="center">Type</th><th width="115" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">notification</td><td align="center"><code>Notification</code></td><td align="center">❌</td><td>The notification data object (same structure as above).</td></tr></tbody></table>

### GetSignalTowers

Returns a table with all Signal Towers from config

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:GetSignalTowers()
```

{% endcode %}

**Returns:**

* `{coords: vector3, radius: number}[]`&#x20;

### GetSignalLevelForCoords

Returns a 0-4 value that represent signal strenght for given coordinates

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:GetSignalLevelForCoords(coords)
```

{% endcode %}

<table><thead><tr><th width="118" align="center">Argument</th><th width="99" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">coords</td><td align="center"><code>vector3</code></td><td align="center">❌</td><td>Coords to check signal</td></tr></tbody></table>

**Returns:**

* `number (0-4)`&#x20;

### GetPlayerSignalLevel

Returns a current player signal strenght&#x20;

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:GetPlayerSignalLevel(playerId)
```

{% endcode %}

<table><thead><tr><th width="118" align="center">Argument</th><th width="99" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">playerId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Player ID to check</td></tr></tbody></table>

**Returns:**

* `number (0-4)`&#x20;

### AddSimcard

Adding a given player simcard with given number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:AddSimcard(playerId, number)
```

{% endcode %}

<table><thead><tr><th width="118" align="center">Argument</th><th width="99" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">playerId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Player ID to Give</td></tr><tr><td align="center">number</td><td align="center"><code>string</code></td><td align="center">✅</td><td>What number should be assigned. If no number is given then script will generate random</td></tr></tbody></table>

**Returns:**

* `boolean` true if sucess&#x20;

### EjectSimCard

Eject simcard with given number, or if no number is given, then current phone will be ejected

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:EjectSimCard(playerId, number)
```

{% endcode %}

**Returns:**

* `boolean` true if sucess&#x20;

<table><thead><tr><th width="118" align="center">Argument</th><th width="99" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">playerId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Player ID to Eject</td></tr><tr><td align="center">number</td><td align="center"><code>string</code></td><td align="center">✅</td><td>What number should be ejected. If no number is given, then current phone number will eject.</td></tr></tbody></table>

### GetPhoneSettings

Returns all settings and configuration for a specific phone number.

{% code lineNumbers="true" %}

```lua
local settings = exports["17mov_Phone"]:GetPhoneSettings(number)
```

{% endcode %}

#### Returns:

* `table` PhoneSettings: A table containing all device configurations.

<details>

<summary>PhoneSettings class</summary>

```lua
---@class PhoneSettings
---@field number string Phone number
---@field configured boolean Whether phone is configured
---@field language string Language code
---@field wallpaper string | nil Wallpaper url | nil for default
---@field theme 'dark' | 'light' Theme
---@field frameColor string Frame color hex code
---@field planemode boolean Whether planemode is enabled
---@field easydrop boolean Whether easydrop is enabled
---@field location boolean Whether location is enabled
---@field silentMode boolean Whether silent mode is enabled
---@field pinCode string | nil Phone pin code | nil if not set
---@field faceId string | nil Face ID owner identifier | nil if not set
---@field ringtoneVolume number Ringtone volume (0-100)
---@field notificationVolume number Notifications volume (0-100)
---@field mediaVolume number Media volume (0-100)
---@field ringtoneSound string | nil Ringtone sound name | nil for default
---@field notificationSound string | nil Notification sound name | nil for default
---@field streamerMode boolean Whether streamer mode is enabled
---@field scale number | nil Phone scale | nil for default
---@field companiesCalls boolean Whether companies call are enabled
---@field companiesNotifications boolean Whether companies notifications are enabled
---@field mycard {firstname: string; lastname: string; image: string; notes: string} | nil Player phone card data | nil if not set
---@field hideNumber boolean Whether to hide number on calls
```

</details>

<table><thead><tr><th width="124" align="center">Argument</th><th width="158" align="center">Type</th><th width="104" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>string</code></td><td align="center">❌</td><td>A target phone number</td></tr></tbody></table>

### GetConfig

Retrieves the full phone configuration, allowing other scripts to read global settings, available applications, and their specific parameters.

{% code lineNumbers="true" %}

```lua
local phoneConfig = exports["17mov_Phone"]:GetConfig()
```

{% endcode %}

#### Returns:

* `table` Config: The main configuration table of the script.

<details>

<summary>Usage Examples:</summary>

```lua
local config = exports["17mov_Phone"]:GetConfig()

-- Check if phones are unique (metadata-based)
if config.UniquePhones then
    print("The server is using unique phone items")
end

-- Accessing specific app settings
local showNumber = config.Apps['Companies'].ShowCompanyExactNumber
print("Show exact company number: " .. tostring(showNumber))
```

</details>

### SetPinCodeBySrc

Sets the phone PIN code for a specific player. This function updates the PIN in the database and synchronizes it with the player's client.

{% code lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:SetPinCodeBySrc(src, pin)
```

{% endcode %}

#### Returns:

* `boolean`: Returns `true` if the PIN was successfully updated, `false` otherwise (e.g., if the PIN is too long or not a string).

<table><thead><tr><th width="124" align="center">Argument</th><th width="158" align="center">Type</th><th width="104" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The server ID (source) of the player.</td></tr><tr><td align="center">pin</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The new PIN code. Max 4 characters.</td></tr></tbody></table>

### GetPhoneMode

Returns current phone mode -  Default / Simcards / Unique Phones

```lua
local phoneMode = exports['17mov_Phone']:GetPhoneMode()
```

#### Returns:

* `PhoneModes phoneMode`

```
PhoneModes = {
    DEFAULT = 0,
    UNIQUE_PHONES = 1,
    SIMCARDS = 2,
}
```

### GetAllPlayerNumbersFromSrc

Returns all phone numbers associated with given player source

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:GetAllPlayerNumbersFromSrc(playerId)
```

{% endcode %}

<table><thead><tr><th width="118" align="center">Argument</th><th width="99" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">playerId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Player ID to get from</td></tr></tbody></table>

#### **Returns:**

* `{number: string}[] numbers`


# CLIENT EXPORTS

### Open Phone

Opens the phone interface for the local player.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:OpenPhone()
```

{% endcode %}

### Close Phone

Closes the phone interface for the local player.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:ClosePhone()
```

{% endcode %}

### Set Phone Block State

Blocks or unblocks the local player from using their phone. When blocked, the player cannot open the phone interface.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:SetPlayerPhoneBlockState(state)
```

{% endcode %}

<table><thead><tr><th width="120" align="center">Argument</th><th width="97" align="center">Type</th><th width="114" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">state</td><td align="center"><code>boolean</code></td><td align="center">❌</td><td><code>true</code> to block the phone, <code>false</code> to unblock.</td></tr></tbody></table>

### Has Phone Item

Checks if the player currently has the required phone item in their inventory.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local hasPhone = exports["17mov_Phone"]:HasPhoneItem()
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the player has the phone item, `false` otherwise.

### Is Phone Open

Checks if the phone interface is currently visible (open) for the player.

<pre class="language-lua" data-overflow="wrap" data-line-numbers><code class="lang-lua"><strong>local isOpen = exports["17mov_Phone"]:IsPhoneOpen()
</strong></code></pre>

**Returns:**

* `boolean`: `true` if the phone is open.

### Is Phone Minimized

Checks if the phone interface is currently minimzed, and if so - it returns current position and height

<pre class="language-lua" data-overflow="wrap" data-line-numbers><code class="lang-lua"><strong>local minimized = exports["17mov_Phone"]:IsPhoneMinimized()
</strong></code></pre>

**Returns:**

* `{height = number, x = number, y = number, isMinimized = boolean}`

### Create Notification

Sends a custom notification to the player's phone. Supports locale keys and dynamic replacements.

{% code lineNumbers="true" %}

```lua
exports["17mov_Phone"]:CreateNotification({
    app = "MESSAGES",
    title = "New Message",
    message = "You have a new message from John.",
    number = "555-0123"
})
```

{% endcode %}

<details>

<summary>Notification Structure</summary>

```lua
{
    number = "555-0123", -- (Optional) Player number associated with notification
    app = "APP_NAME", -- App identifier (e.g., "MESSAGES", "BANK")
    title = "Title", -- Title string or ReplaceType object
    message = "Message Content", -- Message string or ReplaceType object
    data = {
        href = "/messages/1", -- (Optional) Internal link to open on click
        alwaysShow = true -- (Optional) Show even if app is open
    }
}
```

</details>

### Toggle Flashlight

Toggles the phone's flashlight mode.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:ToggleFlashlight(state)
```

{% endcode %}

<table><thead><tr><th width="115" align="center">Argument</th><th width="110" align="center">Type</th><th width="104" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">state</td><td align="center"><code>boolean</code></td><td align="center">❌</td><td><code>true</code> to turn on, <code>false</code> to turn off.</td></tr></tbody></table>

### Get Flashlight State

Checks if the flashlight is currently active.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local isOn = exports["17mov_Phone"]:GetFlashlightState()
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the flashlight is on.

### Set Streamer Mode State

Toggles the Streamer Mode setting. Streamer Mode hides sensitive information (like phone numbers or explicit images) from the UI.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:SetStreamerModeState(state)
```

{% endcode %}

<table><thead><tr><th width="135" align="center">Argument</th><th width="114" align="center">Type</th><th width="118" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">state</td><td align="center"><code>boolean</code></td><td align="center">❌</td><td><code>true</code> to enable Streamer Mode.</td></tr></tbody></table>

### Get Streamer Mode State

Checks whether Streamer Mode is currently enabled.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local isStreamerModeOn = exports["17mov_Phone"]:GetStreamerModeState()
```

{% endcode %}

**Returns:**

* `boolean`: `true` if enabled.

### Open App

Opens a specific application on the phone programmatically.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:OpenApp(appName)
```

{% endcode %}

<table><thead><tr><th width="122" align="center">Argument</th><th width="87" align="center">Type</th><th width="106" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">appName</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The internal name of the app (e.g., <code>"messages"</code>, <code>"camera"</code>).</td></tr></tbody></table>

### Close App

Closes a currently open application on the phone.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:CloseApp(appName)
```

{% endcode %}

<table><thead><tr><th width="118" align="center">Argument</th><th width="99" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">appName</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The internal name of the app to close.</td></tr></tbody></table>

### GetSignalTowers

Returns a table with all Signal Towers from config

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:GetSignalTowers()
```

{% endcode %}

**Returns:**

* `{coords: vector3, radius: number}[]`&#x20;

### GetSignalLevelForCoords

Returns a 0-4 value that represent signal strenght for given coordinates

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:GetSignalLevelForCoords(coords)
```

{% endcode %}

<table><thead><tr><th width="118" align="center">Argument</th><th width="99" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">coords</td><td align="center"><code>vector3</code></td><td align="center">❌</td><td>Coords to check signal</td></tr></tbody></table>

**Returns:**

* `number (0-4)`&#x20;

### GetPlayerSignalLevel

Returns a current player signal strenght&#x20;

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:GetPlayerSignalLevel()
```

{% endcode %}

**Returns:**

* `number (0-4)`&#x20;

### GetConfig

Retrieves the full phone configuration, allowing other scripts to read global settings, available applications, and their specific parameters.

{% code lineNumbers="true" %}

```lua
local phoneConfig = exports["17mov_Phone"]:GetConfig()
```

{% endcode %}

#### Returns:

* `table` Config: The main configuration table of the script.

<details>

<summary>Usage Examples:</summary>

```lua
local config = exports["17mov_Phone"]:GetConfig()

-- Check if phones are unique (metadata-based)
if config.UniquePhones then
    print("The server is using unique phone items")
end

-- Accessing specific app settings
local showNumber = config.Apps['Companies'].ShowCompanyExactNumber
print("Show exact company number: " .. tostring(showNumber))
```

</details>

### GetPlayerNumber

Returns the phone number of the simcard currently equipped by the player.

```lua
local playerNumber = exports["17mov_Phone"]:GetPlayerNumber()
```

#### Returns:

* `string` playerNumber: The current player's active phone number.

### SetPhonePosition

Sets phone position on screen

```lua
exports["17mov_Phone"]:SetPhonePosition(0.5, 0.5)
```

<table><thead><tr><th width="118" align="center">Argument</th><th width="99" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">x</td><td align="center"><code>float</code></td><td align="center">❌</td><td>x coordinates (0.0-1.0) 0 being right edge of the screen, 1 being left edge of the screen</td></tr><tr><td align="center">y</td><td align="center"><code>float</code></td><td align="center">❌</td><td>y coordinates (0.0-1.0) 0 being bottom edge of the screen, 1 being top edge of the screen</td></tr></tbody></table>

### GetPhoneNumberFormat

Returns the current config.lua number formating style

```lua
local numberFormat = exports['17mov_Phone']:GetPhoneNumberFormat()
```

#### Returns:

* `string | nil phoneNumberFormat`

### GetPhoneMode

Returns current phone mode -  Default / Simcards / Unique Phones

```lua
local phoneMode = exports['17mov_Phone']:GetPhoneMode()
```

#### Returns:

* `PhoneModes phoneMode`

```
PhoneModes = {
    DEFAULT = 0,
    UNIQUE_PHONES = 1,
    SIMCARDS = 2,
}
```

### GetUserPhoneNumbers

Returns all user phone numbers

```lua
local numbers = exports['17mov_Phone']:GetUserPhoneNumbers()
```

#### Returns:

* `{number: string, active: number}[] numbers`

### GetUserBrokenPhones

Returns all user broken phones

```lua
local numbers = exports['17mov_Phone']:GetUserBrokenPhones()
```

#### Returns:

* `InventoryItem[] brokenPhones`


# COMPANIES

The Companies application serves as the central hub for communication between citizens and various factions (e.g., Police, EMS, Mechanics). It allows players to check faction availability, send direct messages, or call for assistance. For faction leaders, it provides tools to publish official announcements.

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* `Companies_AddJobNews`
* `Companies_SetCompaniesCallStateBySrc`
* `Companies_SetCompaniesCallStateByNumber`
* `Companies_SetCompaniesMessagesStateBySrc`
* `Companies_SetCompaniesMessagesStateByNumber`

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

### Add Job News

Use this export to add a news post to a specific company page as a "System" message.&#x20;

{% code lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Companies_AddJobNews(title, content, image, company)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, itemId: number }`

<table><thead><tr><th width="118" align="center">Argument</th><th width="206" align="center">Type</th><th width="107" align="center">Optional</th><th align="center">Explanatio</th></tr></thead><tbody><tr><td align="center">title</td><td align="center"><code>string</code></td><td align="center">❌</td><td align="center">The title of the news post.</td></tr><tr><td align="center">content</td><td align="center"><code>string | JSONContent</code>   </td><td align="center">❌</td><td align="center">The content of the post. Can be a simple string or a <code>JSONContent</code> object.</td></tr><tr><td align="center">image</td><td align="center"><code>string</code></td><td align="center">❌</td><td align="center">URL to the image attached to the news.</td></tr><tr><td align="center">company</td><td align="center"><code>string</code></td><td align="center">❌</td><td align="center">The identifier of the company posting the news.</td></tr></tbody></table>

<details>

<summary>JSONContent Example</summary>

```
{
    "type": "doc",
    "content": [
        {
            "type": "paragraph",
            "content": [
                {
                    "type": "text",
                    "marks": [
                        {
                            "type": "bold"
                        }
                    ],
                    "text": "This is Bold text"
                }
            ],
            "attrs": []
        },
        {
            "type": "paragraph",
            "content": [
                {
                    "type": "text",
                    "marks": [
                        {
                            "type": "italic"
                        }
                    ],
                    "text": "This is Italic text"
                }
            ],
            "attrs": []
        },
        {
            "type": "paragraph",
            "content": [
                {
                    "type": "text",
                    "marks": [
                        {
                            "type": "strike"
                        }
                    ],
                    "text": "This is Strikethrough text"
                }
            ],
            "attrs": []
        },
        {
            "type": "paragraph",
            "content": [
                {
                    "type": "text",
                    "text": "This is normal text"
                }
            ],
            "attrs": []
        }
    ]
}
--- Comes from https://tiptap.dev/
```

**OUTPUT:**

<div align="left"><figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FhKPF6FwiZxLDz34GfH0N%2Fimage.png?alt=media&amp;token=7f244d17-14ff-4d6d-bbd0-3e0ee326a710" alt=""><figcaption></figcaption></figure></div>

</details>

### Set Call Notifications (By Source)

Toggle the state of company calls notifications for a player using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:Companies_SetCompaniesCallStateBySrc(src, state)
```

{% endcode %}

<table><thead><tr><th width="115" align="center">Argument</th><th width="90" valign="middle">Type</th><th width="113" align="center" valign="middle">Optional</th><th align="center" valign="middle">Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td valign="middle"><code>number</code></td><td align="center" valign="middle">❌</td><td align="center" valign="middle">The player's server ID (source).</td></tr><tr><td align="center">state</td><td valign="middle"><code>boolean</code></td><td align="center" valign="middle">❌</td><td align="center" valign="middle"><code>true</code> to enable notifications, <code>false</code> to disable.</td></tr></tbody></table>

### Set Call Notifications (By Number)

Toggle the state of company calls notifications for a player using their phone number.

<pre class="language-lua" data-line-numbers><code class="lang-lua"><strong>exports["17mov_Phone"]:Companies_SetCompaniesCallStateByNumber(number, state)
</strong></code></pre>

<table><thead><tr><th width="117" align="center">Argument</th><th width="111" align="center">Type</th><th width="107" align="center">Optional</th><th align="center">Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td align="center">The player's phone number.</td></tr><tr><td align="center">state</td><td align="center"><code>boolean</code></td><td align="center">❌</td><td align="center"><code>true</code> to enable notifications, <code>false</code> to disable.</td></tr></tbody></table>

### Set Message Notifications (By Source)

Toggle the state of company message notifications for a player using their Server ID (Source).

{% code lineNumbers="true" %}

```lua
exports["17mov_Phone"]:Companies_SetCompaniesMessagesStateBySrc(src, state)
```

{% endcode %}

<table><thead><tr><th width="114" align="center">Argument</th><th width="109" align="center">Type</th><th width="114" align="center">Optional</th><th align="center">Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td align="center">The player's server ID (source).</td></tr><tr><td align="center">state</td><td align="center"><code>boolean</code></td><td align="center">❌</td><td align="center"><code>true</code> to enable notifications, <code>false</code> to disable.</td></tr></tbody></table>

### Set Message Notifications (By Number)

Toggle the state of company message notifications for a player using their phone number.

{% code lineNumbers="true" %}

```lua
exports["17mov_Phone"]:Companies_SetCompaniesMessagesStateByNumber(number, state)
```

{% endcode %}

<table><thead><tr><th width="115" align="center">Argument</th><th width="106" align="center">Type</th><th width="107" align="center">Optional</th><th align="center">Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td align="center">The player's phone number.</td></tr><tr><td align="center">state</td><td align="center"><code>boolean</code></td><td align="center">❌</td><td align="center"><code>true</code> to enable notifications, <code>false</code> to disable.</td></tr></tbody></table>


# GALLERY

The **Gallery** application serves as the multimedia hub for players, allowing them to store, view, and manage their photos and videos. It supports organizing media into albums, marking favorites, and sharing content. Developers can use these exports to automatically save images (e.g., from speed cameras or evidence systems) or manage a player's gallery programmatically.

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* **`Gallery_SaveMediaBySrc`**
* **`Gallery_SaveMediaByNumber`**
* **`Gallery_DeleteMediaBySrc`**
* **`Gallery_DeleteMediaByNumber`**
* **`Gallery_FetchAllMediaBySrc`**
* **`Gallery_FetchAllMediaByNumber`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

<details>

<summary>GalleryMedia Structure</summary>

```
{
  "id": 123,
  "type": "image",
  "url": "[https://example.com/image.jpg](https://example.com/image.jpg)",
  "thumbnail": "[https://example.com/thumb.jpg](https://example.com/thumb.jpg)",
  "favorite": false,
  "albumId": null,
  "createdAt": 1678900000
}
```

</details>

### Save Media (By Source)

Saves a photo or video to the player's gallery using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Gallery_SaveMediaBySrc(src, type, url)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, data: GalleryMedia }`

<table><thead><tr><th width="123" align="center">Argument</th><th width="140" align="center">Type</th><th width="107" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">type</td><td align="center"><code>photo | video</code></td><td align="center">❌</td><td>Type of media</td></tr><tr><td align="center">url</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Direct URL to the image or video file.</td></tr></tbody></table>

### Save Media (By Number)

Saves a photo or video to the player's gallery using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Gallery_SaveMediaByNumber(number, type, url)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, data: GalleryMedia }`

<table><thead><tr><th width="118" align="center">Argument</th><th width="140" align="center">Type</th><th width="107" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">type</td><td align="center"><code>photo | video</code></td><td align="center">❌</td><td>Type of media</td></tr><tr><td align="center">url</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Direct URL to the image or video file.</td></tr></tbody></table>

### Delete Media (By Source)

Deletes specific media from the player's gallery using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Gallery_DeleteMediaBySrc(src, mediaId)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if deletion was successful.

<table><thead><tr><th width="118" align="center">Argument</th><th width="188" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">mediaId</td><td align="center"><code>number | number[]</code></td><td align="center">❌</td><td>The id of media</td></tr></tbody></table>

### Delete Media (By Number)

Deletes specific media from the player's gallery using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Gallery_DeleteMediaByNumber(src, mediaId)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if deletion was successful.

<table><thead><tr><th width="118" align="center">Argument</th><th width="188" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">mediaId</td><td align="center"><code>number | number[]</code></td><td align="center">❌</td><td>The id of media</td></tr></tbody></table>

### Fetch All Media (By Source)

Retrieves all media items from a player's gallery using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local mediaList = exports["17mov_Phone"]:Gallery_FetchAllMediaBySrc(src)
```

{% endcode %}

**Returns:**

* `GalleryMedia[]`: An array of media objects.

<table><thead><tr><th width="125" align="center">Argument</th><th width="121" align="center">Type</th><th width="110" align="center" valign="top"></th><th></th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center" valign="top">❌</td><td>The player's server ID (source).</td></tr></tbody></table>

### Fetch All Media (By Number)

Retrieves all media items from a player's gallery using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local mediaList = exports["17mov_Phone"]:Gallery_FetchAllMediaByNumber(number)
```

{% endcode %}

**Returns:**

* `GalleryMedia[]`: An array of media objects.

<table><thead><tr><th width="125" align="center">Argument</th><th width="121" align="center">Type</th><th width="110" align="center" valign="top">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center" valign="top">❌</td><td>The player's phone number.</td></tr></tbody></table>


# EMAIL

The **Email** application is a versatile tool for communication and business. It allows players to receive job offers, system notifications, and messages from other players. Developers can leverage these exports to integrate external scripts (e.g., job systems, car theft missions) to send immersive emails directly to a player's inbox.

#### **Available Exports**

Below is a quick reference list of server-side exports available for this application.

* **`Email_FetchEmailsBySrc`**
* **`Email_FetchEmailsByNumber`**
* **`Email_SendSystemEmailBySrc`**
* **`Email_SendSystemEmailByNumber`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

<details>

<summary>EmailMessage Structure</summary>

{% code lineNumbers="true" %}

```lua
{
  "id": 1,
  "emailId": 101,
  "senderId": 5,
  "senderUsername": "police_department",
  "recipients": ["citizen1", "citizen2"],
  "topic": "Traffic Violation",
  "message": "You have been fined $500 for speeding.",
  "isRead": false,
  "isFavourite": false,
  "isInbox": true,
  "isInBin": false,
  "images": ["[https://example.com/evidence.jpg](https://example.com/evidence.jpg)"],
  "createdAt": 1678900000
}
```

{% endcode %}

</details>

### Fetch Emails (By Source)

Fetches a list of emails for a player using their Server ID (Source). You can filter by inbox type (inbox, favourites, sent, bin).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local emails = exports["17mov_Phone"]:Email_FetchEmailsBySrc(src, inboxType)
```

{% endcode %}

**Returns:**

* `EmailMessage[]`: An array of email objects.

<table><thead><tr><th width="123" align="center">Argument</th><th width="217" align="center">Type</th><th width="133" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">inboxType</td><td align="center"><p><code>"inbox" | "favourites"</code> </p><p><code>| "sent" | "bin"</code></p></td><td align="center">❌</td><td>Target inbox of player</td></tr></tbody></table>

### Fetch Emails (By Number)

Fetches a list of emails for a player using their phone number. You can filter by inbox type. (inbox, favourites, sent, bin).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local emails = exports["17mov_Phone"]:Email_FetchEmailsByNumber(number, inboxType)
```

{% endcode %}

**Returns:**

* `EmailMessage[]`: An array of email objects.

<table><thead><tr><th width="123" align="center">Argument</th><th width="217" align="center">Type</th><th width="133" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">inboxType</td><td align="center"><p><code>"inbox" | "favourites"</code> </p><p><code>| "sent" | "bin"</code></p></td><td align="center">❌</td><td>Target inbox of player</td></tr></tbody></table>

### Send System Email (By Source)

Sends an automated system email to a player using their Server ID (Source). This is useful for job notifications, alerts, or automated messages.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:Email_SendSystemEmailBySrc(src, topic, message, images)
```

{% endcode %}

<table><thead><tr><th width="112" align="center">Argument</th><th width="94" align="center">Type</th><th width="99" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">topic</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The subject line of the email.</td></tr><tr><td align="center">message</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The main content of the email.</td></tr><tr><td align="center">images</td><td align="center"><code>string[]</code></td><td align="center">✅</td><td>An optional array of image URLs to attach.</td></tr></tbody></table>

### Send System Email (By Number)

Sends an automated system email to a player using their phone number. This is useful for job notifications, alerts, or automated messages.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Phone"]:Email_SendSystemEmailByNumber(number, topic, message, images)
```

{% endcode %}

<table><thead><tr><th width="112" align="center">Argument</th><th width="94" align="center">Type</th><th width="99" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">topic</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The subject line of the email.</td></tr><tr><td align="center">message</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The main content of the email.</td></tr><tr><td align="center">images</td><td align="center"><code>string[]</code></td><td align="center">✅</td><td>An optional array of image URLs to attach.</td></tr></tbody></table>

### Send Email (By Source)

Sends an email to a player using their Server ID (Source) with a specified sender name.

{% code lineNumbers="true" %}

```lua
exports["17mov_Phone"]:Email_SendEmailBySrc(src, sender, topic, message, images, customUser)
```

{% endcode %}

<table><thead><tr><th width="118.5625" align="center">Argument</th><th width="94" align="center">Type</th><th width="99" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">sender</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The display name of the email sender. (if using fake-data set last variable to true)</td></tr><tr><td align="center">topic</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The subject line of the email.</td></tr><tr><td align="center">message</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The main content of the email.</td></tr><tr><td align="center">images</td><td align="center"><code>string[]</code></td><td align="center">✅</td><td>An optional array of image URLs to attach.</td></tr><tr><td align="center">customUser</td><td align="center"><code>boolean</code></td><td align="center">✅</td><td>Set this to true, if sender is account that doesn't exist </td></tr></tbody></table>

### Send Email (By Number)

Sends an email to a player using their phone number with a specified sender name

{% code lineNumbers="true" %}

```lua
exports["17mov_Phone"]:Email_SendEmailByNumber(number, sender, topic, message, images)
```

{% endcode %}

<table><thead><tr><th width="129.9453125" align="center">Argument</th><th width="94" align="center">Type</th><th width="99" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">sender</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The display name of the email sender (if using fake-data set last variable to true)</td></tr><tr><td align="center">topic</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The subject line of the email.</td></tr><tr><td align="center">message</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The main content of the email.</td></tr><tr><td align="center">images</td><td align="center"><code>string[]</code></td><td align="center">✅</td><td>An optional array of image URLs to attach.</td></tr><tr><td align="center">customUser</td><td align="center"><code>boolean</code></td><td align="center">✅</td><td>Set this to true, if sender is account that doesn't exist </td></tr></tbody></table>


# MAPS

The **Maps** application provides essential navigation and location sharing features. Players can save important locations, share their position with friends, and view custom blips. Developers can use these exports to programmatically add pins to a player's map, which is useful for mission objectives, delivery locations, or marking points of interest.

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* **`Maps_AddPinBySrc`**
* **`Maps_AddPinByNumber`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

### Add Pin (By Source)

Adds a custom pin (marker) to the player's map using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Maps_AddPinBySrc(src, label, color, position)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, id?: number }`

<table><thead><tr><th width="113" align="center">Argument</th><th width="90" align="center">Type</th><th width="117" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">label</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The name/label of the pin.</td></tr><tr><td align="center">color</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Index of the pin color (0-4).</td></tr><tr><td align="center">position</td><td align="center"><code>Vector2</code></td><td align="center">❌</td><td>Coordinates for the pin.</td></tr></tbody></table>

### Add Pin (By Number)

Adds a custom pin (marker) to the player's map using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Maps_AddPinByNumber(number, label, color, position)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, id?: number }`

<table><thead><tr><th width="118" align="center">Argument</th><th width="102" align="center" valign="top">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center" valign="top"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">label</td><td align="center" valign="top"><code>string</code></td><td align="center">❌</td><td>The name/label of the pin.</td></tr><tr><td align="center">color</td><td align="center" valign="top"><code>number</code></td><td align="center">❌</td><td>Index of the pin color (0-4).</td></tr><tr><td align="center">position</td><td align="center" valign="top"><code>Vector2</code></td><td align="center">❌</td><td>Coordinates for the pin.</td></tr></tbody></table>


# VOICE RECORDER

The **Voice** application functions as a voice recorder and dictaphone. Players can record their own voice notes or discreetly capture conversations around them. Developers can use these exports to programmatically save audio recordings for a player, which is useful for evidence systems, voicemail integration, or story-driven content.

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* **`Voice_SaveMessageToPlayerBySrc`**
* **`Voice_SaveMessageToPlayerByNumber`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

<details>

<summary>Return Object Structure</summary>

```
{
  "id": 1,
  "number": 123456789,
  "title": "Suspicious Conversation",
  "url": "[https://example.com/audio.ogg](https://example.com/audio.ogg)",
  "createdAt": 1678900000
}
```

</details>

### Save Voice Message (By Source)

Saves a voice recording (audio file) to the player's voice recorder app using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Voice_SaveMessageToPlayerBySrc(src, title, url)
```

{% endcode %}

**Returns:**

* `table`: Object containing details of the saved message.

<table><thead><tr><th width="116" align="center">Argument</th><th width="111" align="center">Type</th><th width="106" align="center">Optional</th><th align="center">Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td align="center">The player's server ID (source).</td></tr><tr><td align="center">title</td><td align="center"><code>string</code></td><td align="center">❌</td><td align="center">The title/name of the recording.</td></tr><tr><td align="center">url</td><td align="center"><code>string</code></td><td align="center">❌</td><td align="center">Direct URL to the audio file (e.g., .ogg, .mp3).</td></tr></tbody></table>

### Save Voice Message (By Number)

Saves a voice recording (audio file) to the player's voice recorder app using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Voice_SaveMessageToPlayerByNumber(number, title, url)
```

{% endcode %}

**Returns:**

* `table`: Object containing details of the saved message.

<table><thead><tr><th width="124" align="center">Argument</th><th width="95" align="center">Type</th><th width="130" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">title</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The title/name of the recording.</td></tr><tr><td align="center">url</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Direct URL to the audio file (e.g., .ogg, .mp3).</td></tr></tbody></table>


# CRYPTO

The **Crypto** application allows players to trade cryptocurrencies, view live market data, and transfer digital assets. Developers can use these exports to check player balances or modify them, enabling integration with job payouts, illegal activities, or custom economy systems involving crypto.

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* **`Crypto_GetBalanceById`**
* **`Crypto_GetBalanceByNumber`**
* **`Crypto_SetCoinBalanceById`**
* **`Crypto_SetCoinBalanceByNumber`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

### Get Crypto Balance (By Source)

Retrieves the current balance of a specific cryptocurrency for a player using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local balance = exports["17mov_Phone"]:Crypto_GetBalanceById(src, coin)
```

{% endcode %}

**Returns:**

* `number`: The current balance of the specified coin.

<table><thead><tr><th width="118" align="center">Argument</th><th width="93" align="center">Type</th><th width="116" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">coin</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The name/identifier of the coin (e.g., "bitcoin").</td></tr></tbody></table>

### Get Crypto Balance (By Number)

Retrieves the current balance of a specific cryptocurrency for a player using their phone number.

{% code lineNumbers="true" %}

```lua
local balance = exports["17mov_Phone"]:Crypto_GetBalanceByNumber(number, coin)
```

{% endcode %}

**Returns:**

* `number`:  The current balance of the specified coin. Returns `0` if the player is not found.

<table><thead><tr><th width="121" align="center">Argument</th><th width="104" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">coin</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The name/identifier of the coin (e.g., "bitcoin").</td></tr></tbody></table>

### Set Crypto Balance (By Source)

Sets a specific cryptocurrency balance for a player using their Server ID (Source). This overwrites the previous balance.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Crypto_SetCoinBalanceById(src, coin, balance)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the operation was successful.

<table><thead><tr><th width="115" align="center">Argument</th><th width="89" align="center">Type</th><th width="106" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">coin</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The name/identifier of the coin (e.g., "bitcoin").</td></tr><tr><td align="center">balance</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The new balance to set.</td></tr></tbody></table>

### Set Crypto Balance (By Number)

Sets a specific cryptocurrency balance for a player using their phone number. This overwrites the previous balance.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Crypto_SetCoinBalanceByNumber(number, coin, balance)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the operation was successful.

<table><thead><tr><th width="115" align="center">Argument</th><th width="89" align="center">Type</th><th width="109" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">coin</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The name/identifier of the coin (e.g., "bitcoin").</td></tr><tr><td align="center">balance</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The new balance to set.</td></tr></tbody></table>


# DARKCHAT

The **Darkchat** application provides a secure, encrypted communication channel for illegal activities. Players can create anonymous group chats, invite others via codes, and exchange messages without fear of interception. Developers can use these exports to programmatically manage darkchat conversations, adding or removing users based on in-game events (e.g., joining a gang, completing a mission).

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* **`Darkchat_CreateConversationBySrc`**
* **`Darkchat_CreateConversationByNumber`**
* **`Darkchat_AddToConversationBySrc`**
* **`Darkchat_AddToConversationByNumber`**
* **`Darkchat_RemoveFromConversationBySrc`**
* **`Darkchat_RemoveFromConversationByNumber`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

<details>

<summary>Darkchat Conversation Structure</summary>

{% code overflow="wrap" %}

```
{
  "id": 1,
  "name": "Heist Planning",
  "appName": "darkchat",
  "createdBy": 5,
  "joinCode": "X9Z2A1",
  "createdAt": 1678900000,
  "updatedAt": 1678900000,
  "recipients": [],
  "messages": [],
  "isItRead": true
}
```

{% endcode %}

</details>

### Create Conversation (By Source)

Creates a new Darkchat conversation for a player using their Server ID (Source). You can optionally specify a join code.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Darkchat_CreateConversationBySrc(src, name, joinCode)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, newConv?: DarkchatConversation }`

<table><thead><tr><th width="114" align="center">Argument</th><th width="97" align="center">Type</th><th width="110" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">name</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The name of the conversation.</td></tr><tr><td align="center">joinCode</td><td align="center"><code>string</code></td><td align="center">✅</td><td>Code to join conversation. If not provided, it will be generated automatically.</td></tr></tbody></table>

### Create Conversation (By Number)

Creates a new Darkchat conversation for a player using their phone number.

<pre class="language-lua" data-overflow="wrap" data-line-numbers><code class="lang-lua"><strong>local result = exports["17mov_Phone"]:Darkchat_CreateConversationByNumber(number, name, joinCode)
</strong></code></pre>

**Returns:**

* `table`: `{ success: boolean, newConv?: DarkchatConversation }`

<table><thead><tr><th width="122" align="center">Argument</th><th width="103" align="center">Type</th><th width="114" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">name</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The name of the conversation.</td></tr><tr><td align="center">joinCode</td><td align="center"><code>string</code></td><td align="center">✅</td><td>Code to join conversation. If not provided, it will be generated automatically.</td></tr></tbody></table>

### Add To Conversation (By Source)

Adds a player to an existing Darkchat conversation using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Darkchat_AddToConversationBySrc(src, conversationId)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the user was successfully added.

<table><thead><tr><th width="136" align="center">Argument</th><th width="95">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">conversationId</td><td><code>number</code></td><td align="center">❌</td><td>ID of the conversation to add the user to.</td></tr></tbody></table>

### Add To Conversation (By Number)

Adds a player to an existing Darkchat conversation using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Darkchat_AddToConversationByNumber(number, conversationId)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the user was successfully added.

<table><thead><tr><th width="136" align="center">Argument</th><th width="97" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">conversationId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>ID of the conversation to add the user to.</td></tr></tbody></table>

### Remove From Conversation (By Source)

Removes a player from a Darkchat conversation using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Darkchat_RemoveFromConversationBySrc(src, conversationId, isSelf)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the user was successfully removed.

<table><thead><tr><th width="135" align="center">Argument</th><th width="82" align="center">Type</th><th width="107" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">conversationId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>ID of the conversation to remove the user from.</td></tr></tbody></table>

### Remove From Conversation (By Number)

Removes a player from a Darkchat conversation using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Darkchat_RemoveFromConversationByNumber(number, conversationId, isSelf)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the user was successfully removed.

<table><thead><tr><th width="140" align="center">Argument</th><th width="96" align="center">Type</th><th width="117" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">conversationId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>ID of the conversation to remove the user from.</td></tr></tbody></table>


# EBUY

The **Ebuy** application is the server's marketplace, allowing players to buy and sell items, vehicles, and more. It features listing management, searching, and filtering. Developers can use these exports to automatically create listings (e.g., for rare item drops, police auctions) or manage existing auctions programmatically.

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* **`Ebuy_FetchAuctions`**
* **`Ebuy_AddAuction`**
* **`Ebuy_DeleteAuction`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

<details>

<summary>EbuyAuction Structure</summary>

```
{
  "id": 1,
  "seller": 5,
  "category": "car",
  "title": "Sports Car",
  "description": "Fast and reliable.",
  "images": ["[https://example.com/car.jpg](https://example.com/car.jpg)"],
  "phoneNumber": "555-0123",
  "price": 50000,
  "hideNumber": false,
  "createdAt": 1678900000
}
```

</details>

### Fetch Auctions

Fetches a list of auctions based on various filters like search query, category, sorting order, and pagination limits.

{% code lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Ebuy_FetchAuctions(search, category, sort, cursor, limit, userId)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, auctions: EbuyAuction[] }`

<table><thead><tr><th width="125" align="center">Argument</th><th width="313" align="center">Type</th><th width="142" align="center">Optional</th><th align="center">Explanation</th></tr></thead><tbody><tr><td align="center">search</td><td align="center"><code>string</code></td><td align="center">✅</td><td align="center">Search query to filter auctions by title.</td></tr><tr><td align="center">category</td><td align="center"><code>"all" | "item" | "car"</code>    </td><td align="center">✅</td><td align="center">Fetching auctions only from this category</td></tr><tr><td align="center">sort</td><td align="center"><code>"priceAsc" | "priceDesc"| "newest"</code> </td><td align="center">✅</td><td align="center">Sorting auctions by this param</td></tr><tr><td align="center">cursor</td><td align="center"><code>number</code></td><td align="center">✅</td><td align="center">Number of items to skip (pagination). Requires <code>limit</code>.</td></tr><tr><td align="center">limit</td><td align="center"><code>number</code></td><td align="center">✅</td><td align="center">Maximum number of auctions to return.</td></tr><tr><td align="center">userId</td><td align="center"><code>number</code></td><td align="center">✅</td><td align="center">Filter auctions created by a specific user ID.</td></tr></tbody></table>

### Add Auction

Creates a new auction listing in the Ebuy marketplace.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Ebuy_AddAuction(title, description, images, phoneNumber, price, seller, category, hideNumber)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, id?: number }`

<table><thead><tr><th width="130" align="center">Argument</th><th width="146" align="center">Type</th><th width="112" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">title</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The title of the auction.</td></tr><tr><td align="center">description</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Detailed description of the item/vehicle.</td></tr><tr><td align="center">images</td><td align="center"><code>string[]</code></td><td align="center">❌</td><td>Array of image URLs.</td></tr><tr><td align="center">phoneNumber</td><td align="center"><code>string</code></td><td align="center">✅</td><td>Contact phone number for the seller.</td></tr><tr><td align="center">price</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Price of the item.</td></tr><tr><td align="center">seller</td><td align="center"><code>number</code></td><td align="center">✅</td><td>User ID of the seller. Defaults to 0 (System).</td></tr><tr><td align="center">category</td><td align="center"><code>"item" | "car"</code></td><td align="center">❌</td><td>Auction category</td></tr><tr><td align="center">hideNumber</td><td align="center"><code>boolean</code></td><td align="center">❌</td><td>Whether to hide the seller's phone number.</td></tr></tbody></table>

### Delete Auction

Removes an existing auction listing from the marketplace.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:Ebuy_DeleteAuction(id)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean }`

| Argument | Type     | Optional | Explanation                      |
| -------- | -------- | -------- | -------------------------------- |
| id       | `number` | ❌        | The ID of the auction to delete. |


# NOTES

The **Notes** application is a personal organizer for players. It allows them to write down important information, format text, and share notes with others. Developers can use these exports to automatically create notes for players (e.g., finding a letter with a clue, receiving instructions from an NPC) or retrieve existing notes for specific gameplay mechanics.

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* **`Notes_FetchNotesById`**
* **`Notes_FetchNotesByNumber`**
* **`Notes_AddNoteBySrc`**
* **`Notes_AddNoteByNumber`**
* **`Notes_DeleteNote`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

<details>

<summary>NotesItem Structure</summary>

```
{
  "id": 1,
  "number": "555-0123",
  "title": "Shopping List",
  "description": {
    "type": "doc",
    "content": [
      {
        "type": "paragraph",
        "content": [
          {
            "type": "text",
            "text": "Milk, Bread, Eggs"
          }
        ]
      }
    ]
  },
  "createdAt": 1678900000
}
```

</details>

### Fetch Notes (By Source)

Retrieves a list of all notes belonging to a player using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local notes = exports["17mov_Phone"]:Notes_FetchNotesById(src)
```

{% endcode %}

**Returns:**

* `NotesItem[]`: An array of note objects.

<table><thead><tr><th width="114" align="center">Argument</th><th width="93" align="center">Type</th><th width="133" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr></tbody></table>

Retrieves a list of all notes belonging to a player using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local notes = exports["17mov_Phone"]:Notes_FetchNotesByNumber(number)
```

{% endcode %}

**Returns:**

* `NotesItem[]`: An array of note objects.

<table><thead><tr><th width="112" align="center">Argument</th><th width="100" align="center">Type</th><th width="109" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr></tbody></table>

### Add Note (By Source)

Creates a new note for a player using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local noteId = exports["17mov_Phone"]:Notes_AddNoteBySrc(src, title, description)
```

{% endcode %}

**Returns:**

* `number`: The ID of the newly created note.

<table><thead><tr><th width="133" align="center">Argument</th><th width="205" align="center">Type</th><th width="161" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">title</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The title of the note.</td></tr><tr><td align="center">description</td><td align="center"><code>string | JSONContent</code> </td><td align="center">❌</td><td>Note description (content). JSONContet is same as <a href="/phone/exports/companies/server-exports#jsoncontent-example">here</a></td></tr></tbody></table>

### Add Note (By Number)

Creates a new note for a player using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local noteId = exports["17mov_Phone"]:Notes_AddNoteByNumber(number, title, description)
```

{% endcode %}

**Returns:**

* `number`: The ID of the newly created note.

<table><thead><tr><th width="137" align="center">Argument</th><th width="206" align="center">Type</th><th width="149" align="center">Optional</th><th align="center">Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td align="center">The player's phone number.</td></tr><tr><td align="center">title</td><td align="center"><code>string</code></td><td align="center">❌</td><td align="center">The title of the note.</td></tr><tr><td align="center">description</td><td align="center"><code>string | JSONContent</code></td><td align="center">❌</td><td align="center">Note description (content). JSONContet is same as <a href="/phone/exports/companies/server-exports#jsoncontent-example">here</a></td></tr></tbody></table>

### Delete Note

Permanently deletes a specific note.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Notes_DeleteNote(noteId)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the note was successfully deleted.

<table><thead><tr><th width="125" align="center">Argument</th><th width="109" align="center">Type</th><th width="114" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">noteId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The ID of the note to delete.</td></tr></tbody></table>


# PHONE

The **Phone** application is the core of the device, managing calls, contacts, and basic communication. Developers can use these exports to programmatically manage a player's contact list, adding or removing entries based on in-game events (e.g., giving a player the contact of a quest giver, removing a contact after a character permadeath).

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* **`PhoneApp_FetchContactsBySrc`**
* **`PhoneApp_FetchContactsByNumber`**
* **`PhoneApp_AddContactBySrc`**
* **`PhoneApp_AddContactByNumber`**
* **`PhoneApp_DeleteContact`**<br>
* **`PhoneApp_StartCall`**
* **`PhoneApp_EndCallBySrc`**
* **`PhoneApp_EndCallByNumber`**
* **`PhoneApp_IsInCallBySrc`**
* **`PhoneApp_IsInCallByNumber`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

<details>

<summary>PhoneContact Structure</summary>

```
{
  "id": 1,
  "number": "555-0123",
  "contactNumber": "555-9876",
  "name": "John Doe",
  "firstname": "John",
  "lastname": "Doe",
  "notes": "Best Mechanic",
  "image": "[https://example.com/avatar.jpg](https://example.com/avatar.jpg)",
  "isBlocked": false,
  "favorite": true
}
```

</details>

<details>

<summary>PhoneCall Structure</summary>

```
{
  "callId": "unique-uuid",
  "fromId": 1,
  "fromNumber": "555-0101",
  "toId": 2,
  "toNumber": "555-0202",
  "callTime": 1678900000,
  "inCall": true,
  "type": "phone",
  "isNumberHidden": false,
  "isCompanyCall": false
}
```

</details>

### Fetch Contacts (By Source)

Retrieves a list of all contacts belonging to a player using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local contacts = exports["17mov_Phone"]:PhoneApp_FetchContactsBySrc(src)
```

{% endcode %}

**Returns:**

* `PhoneContact[]`: An array of contact objects.

<table><thead><tr><th width="115" align="center">Argument</th><th width="88" align="center">Type</th><th width="119" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr></tbody></table>

### Fetch Contacts (By Number)

Retrieves a list of all contacts belonging to a player using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local contacts = exports["17mov_Phone"]:PhoneApp_FetchContactsByNumber(number)
```

{% endcode %}

**Returns:**

* `PhoneContact[]`: An array of contact objects.

<table><thead><tr><th width="126">Argument</th><th width="117">Type</th><th width="105">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td>number</td><td><code>number</code></td><td>❌</td><td>The player's phone number.</td></tr></tbody></table>

### Add Contact (By Source)

Adds a new contact to the player's phone book using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:PhoneApp_AddContactBySrc(src, contactNumber, firstname, lastname, notes, image)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, contact?: PhoneContact, message?: string }`

<table><thead><tr><th width="164" align="center">Argument</th><th width="103" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">contactNumber</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The phone number of the contact to add.</td></tr><tr><td align="center">firstname</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The contact's first name.</td></tr><tr><td align="center">lastname</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The contact's last name.</td></tr><tr><td align="center">notes</td><td align="center"><code>string</code></td><td align="center">✅</td><td>Optional notes about the contact.</td></tr><tr><td align="center">image</td><td align="center"><code>string</code></td><td align="center">✅</td><td>Optional URL for the contact's avatar.</td></tr></tbody></table>

### Add Contact (By Number)

Adds a new contact to the player's phone book using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local result = exports["17mov_Phone"]:PhoneApp_AddContactByNumber(number, contactNumber, firstname, lastname, notes, image)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, contact?: PhoneContact, message?: string }`

<table><thead><tr><th width="145" align="center">Argument</th><th width="106" align="center">Type</th><th width="116" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">contactNumber</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The phone number of the contact to add.</td></tr><tr><td align="center">firstname</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The contact's first name.</td></tr><tr><td align="center">lastname</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The contact's last name.</td></tr><tr><td align="center">notes</td><td align="center"><code>string</code></td><td align="center">✅</td><td>Optional notes about the contact.</td></tr><tr><td align="center">image</td><td align="center"><code>string</code></td><td align="center">✅</td><td>Optional URL for the contact's avatar.</td></tr></tbody></table>

### Delete Contact

Permanently removes a contact from the player's phone book.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:PhoneApp_DeleteContact(contactId)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the contact was successfully deleted.

<table><thead><tr><th width="122" align="center">Argument</th><th width="102" align="center">Type</th><th width="107" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">contactId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The ID of the contact to delete.</td></tr></tbody></table>

### Start Call

Initiates a call between two phone numbers. Useful for NPC interactions or automated services.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local callData = exports["17mov_Phone"]:PhoneApp_StartCall(from, to, type)
```

{% endcode %}

**Returns:**

* `PhoneCall | false`: The call object if successful, or `false` if it failed.

<table><thead><tr><th width="112" align="center">Argument</th><th width="169" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">from</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Caller's phone number.</td></tr><tr><td align="center">to</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Receiver's phone number.</td></tr><tr><td align="center">type</td><td align="center"><code>"phone" | "video"</code></td><td align="center">✅</td><td>Is video-call or default call?</td></tr></tbody></table>

### End Call (By Source)

Ends any active call for a player using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:PhoneApp_EndCallBySrc(src)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if a call was found and ended.

<table><thead><tr><th width="118" align="center">Argument</th><th width="192" align="center">Type</th><th width="106" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Player ID </td></tr></tbody></table>

### End Call (By Number)

Ends any active call for a player using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:PhoneApp_EndCallByNumber(number)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if a call was found and ended.

<table><thead><tr><th width="113">Argument</th><th width="215">Type</th><th width="105">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td>number</td><td><code>number | string</code>  </td><td>❌</td><td>Number of player in call to end</td></tr></tbody></table>

### Check If In Call (By Source)

Checks if a player is currently in an active call using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local inCall = exports["17mov_Phone"]:PhoneApp_IsInCallBySrc(src)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the player is in a call.

<table><thead><tr><th width="118" align="center">Argument</th><th width="118" align="center">Type</th><th width="125" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr></tbody></table>

### Get Call Id From Src

Get Player current call Id by source

{% code overflow="wrap" lineNumbers="true" %}

```lua
local callId = exports["17mov_Phone"]:PhoneApp_GetCallIdFromSrc(src)
```

{% endcode %}

**Returns:**

* `string | nil callid call id`

| Argument | Type     | Optional | Explanation      |
| -------- | -------- | -------- | ---------------- |
| src      | `number` | ❌        | Player Server ID |

### Get Call Id From Number

Get Player current call Id by number

{% code overflow="wrap" lineNumbers="true" %}

```lua
local callId = exports["17mov_Phone"]:PhoneApp_GetCallIdFromNumber(number)
```

{% endcode %}

**Returns:**

* `string | nil callid call id`

| Argument | Type     | Optional | Explanation         |
| -------- | -------- | -------- | ------------------- |
| number   | `string` | ❌        | Player Phone Number |

### Get Call Data From Src

Get Player current Call Data by source

{% code overflow="wrap" lineNumbers="true" %}

```lua
local callData = exports["17mov_Phone"]:PhoneApp_GetCallDataFromSrc(src)
```

{% endcode %}

**Returns:**

* `PhoneCall | nil callData call data`

| Argument | Type     | Optional | Explanation      |
| -------- | -------- | -------- | ---------------- |
| src      | `number` | ❌        | Player Server ID |

### Get Call Data From Number

Get Player current call data by number

{% code overflow="wrap" lineNumbers="true" %}

```lua
local callData = exports["17mov_Phone"]:PhoneApp_GetCallDataFromNumber(number)
```

{% endcode %}

**Returns:**

* `PhoneCall | nil callData call data`

| Argument | Type     | Optional | Explanation         |
| -------- | -------- | -------- | ------------------- |
| number   | `string` | ❌        | Player Phone Number |

### Get Call Data From Call Id&#x20;

Get Calls data from call id

{% code overflow="wrap" lineNumbers="true" %}

```lua
local callData = exports["17mov_Phone"]:PhoneApp_GetCallDataFromCallId(callId)
```

{% endcode %}

**Returns:**

* `PhoneCall | nil callData call data`

| Argument | Type     | Optional | Explanation   |
| -------- | -------- | -------- | ------------- |
| callId   | `string` | ❌        | Phone call ID |


# MESSAGES

The **Messages** application handles SMS and multimedia messaging between players. Developers can use these exports to send automated messages from "System" or specific NPCs/numbers.

#### Available Exports

Below is a quick reference list of server-side exports available for this application.

* **`Messages_SendMessageToSrc`**
* **`Messages_SendMessageToNumber`**
* **`Messages_DeleteMessageById`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

<details>

<summary>Return Object Structure</summary>

```
{
  "success": true,
  "message": {
    "id": 123,
    "sender": "System",
    "content": "Your vehicle is ready.",
    "type": "text",
    "createdAt": 1678900000
  },
  "conversation": {
    "name": "Mechanic",
    "avatar": "default",
    "conversationId": 45,
    "participants": ["555-0101", "System"],
    "isGroup": false,
    "createdAt": 1678900000
  }
}
```

</details>

### Send Message (By Source)

Sends a text message to a player using their Server ID (Source). If a conversation does not exist, it will be created automatically.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local response = exports["17mov_Phone"]:Messages_SendMessageToSrc(src, message, sender)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, message?: MessageData, conversation?: ConversationData }`

<table><thead><tr><th width="112" align="center">Argument</th><th width="159" align="center">Type</th><th width="110" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr><tr><td align="center">message</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Message content.</td></tr><tr><td align="center">sender</td><td align="center"><code>number | string</code></td><td align="center">✅</td><td>Sender number or name, default is "System"</td></tr></tbody></table>

### Send Message (By Number)

Sends a text message to a player using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local response = exports["17mov_Phone"]:Messages_SendMessageToNumber(number, message, sender)
```

{% endcode %}

**Returns:**

* `table`: `{ success: boolean, message?: MessageData, conversation?: ConversationData }`

<table><thead><tr><th width="122" align="center">Argument</th><th width="163" align="center">Type</th><th width="112" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Target player number</td></tr><tr><td align="center">message</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Message content.</td></tr><tr><td align="center">sender</td><td align="center"><code>number | string</code></td><td align="center">✅</td><td>Sender number or name, default is "System"</td></tr></tbody></table>

### Delete Message

Permanently deletes a specific message from the database and updates all clients involved.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Messages_DeleteMessageById(messageId)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if message was successfully deleted.

<table><thead><tr><th width="118" align="center">Argument</th><th width="95" align="center">Type</th><th width="111" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">messageId</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Target message ID.</td></tr></tbody></table>

### How to send location

{% code lineNumbers="true" %}

```lua
exports['17mov_Phone']:Messages_SendMessageToSrc(src, {
    sender = "6872731",
    content = json.encode(vector2(1000.0, 1000.0)),
    type = 'location'
})
```

{% endcode %}


# QWUAKER

**Qwuaker** is a social media platform designed for short updates and community interaction. It features a verification system that allows server administrators or automated scripts to assign verification badges (Verified, Business, Government) to users. Developers can use these exports to integrate Qwuaker verification with job systems, VIP statuses, or other server mechanics.

#### Available Exports & Commands

Below is a quick reference list of server-side exports and commands available for this application.

**Exports:**

* **`Qwuaker_SetVerificationBySrc`**
* **`Qwuaker_SetVerificationByNumber`**
* **`Qwuaker_GetVerificationBySrc`**
* **`Qwuaker_GetVerificationByNumber`**

**Commands:**

* **`/setVerificationLevel [level] [username]`**

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

### Set Verification (By Source)

Sets the verification badge level for a specific player's Qwuaker account using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Qwuaker_SetVerificationBySrc(src, level)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the operation was successful.

<table><thead><tr><th width="117">Argument</th><th width="90">Type</th><th width="105">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td>src</td><td><code>number</code></td><td>❌</td><td>The player's server ID (source).</td></tr><tr><td>level</td><td><code>number</code></td><td>❌</td><td>Verification level: <code>0</code> (None), <code>1</code> (Verified), <code>2</code> (Business), <code>3</code> (Gov).</td></tr></tbody></table>

### Set Verification (By Number)

Sets the verification badge level for a specific player's Qwuaker account using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local success = exports["17mov_Phone"]:Qwuaker_SetVerificationByNumber(number, level)
```

{% endcode %}

**Returns:**

* `boolean`: `true` if the operation was successful.

<table><thead><tr><th width="112" align="center">Argument</th><th width="87" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr><tr><td align="center">level</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Verification level: <code>0</code> (None), <code>1</code> (Verified), <code>2</code> (Business), <code>3</code> (Gov).</td></tr></tbody></table>

### Get Verification (By Source)

Retrieves the current verification level of a player's Qwuaker account using their Server ID (Source).

{% code overflow="wrap" lineNumbers="true" %}

```lua
local level = exports["17mov_Phone"]:Qwuaker_GetVerificationBySrc(src)
```

{% endcode %}

**Returns:**

* `number` | `nil`: The current verification level (0-3), or `nil` if the user was not found.

<table><thead><tr><th width="118" align="center">Argument</th><th width="93" align="center">Type</th><th width="115" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's server ID (source).</td></tr></tbody></table>

### Get Verification (By Number)

Retrieves the current verification level of a player's Qwuaker account using their phone number.

{% code overflow="wrap" lineNumbers="true" %}

```lua
local level = exports["17mov_Phone"]:Qwuaker_GetVerificationByNumber(number)
```

{% endcode %}

**Returns:**

* `number` | `nil`: The current verification level (0-3), or `nil` if the user was not found.

<table><thead><tr><th width="118" align="center">Argument</th><th width="90" align="center">Type</th><th width="108" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The player's phone number.</td></tr></tbody></table>

## Commands

#### Set Verification Level

Allows administrators (or console) to manually set a verification level for a specific Qwuaker username.

**Usage:**

```
/setVerificationLevel [username] [level]
```

<table><thead><tr><th width="123" align="center">Argument</th><th>Explanation</th></tr></thead><tbody><tr><td align="center"><code>username</code></td><td>The unique username of the Qwuaker account (e.g., <code>@JohnDoe</code>).</td></tr><tr><td align="center"><code>level</code></td><td><p><code>0</code> = Not Verified</p><p><code>1</code> = Verified (Blue Check)</p><p><code>2</code> = Business (Gold Check)</p><p><code>3</code> = Government (Grey Check)</p></td></tr></tbody></table>

**Example:**

```
/setVerificationLevel JohnDoe 2
```


# WALLET

The Wallet application serves as a comprehensive digital banking and transaction system. It enables players to manage their finances, track transaction history, and perform transfers using unique wallet account numbers. Developers can utilize these exports to link the phone’s wallet with jobs, business systems, or automated payment gateways.

### Available Exports

Below is a quick reference list of server-side exports available for the Wallet application:

* `Wallet_GetAccountNumberBySrc`: Retrieves the wallet account number using a player's server ID.
* `Wallet_GetAccountNumberByNumber`: Retrieves the wallet account number using a specific phone number.
* `Wallet_AddTransaction`: Creates a secure transaction between two accounts and updates the UI for online players.

To learn more about specific exports, please navigate to the next tab.


# SERVER EXPORTS

## Wallet\_GetAccountNumberBySrc

Retrieves the unique wallet account number associated with a player's server ID.

```lua
local accountId = exports["17mov_Phone"]:Wallet_GetAccountNumberBySrc(src)
```

#### Returns:

* `string` / `nil` accountId: The wallet account number or `nil` if not found.

#### Arguments:

<table><thead><tr><th width="112" align="center">Argument</th><th width="87" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>Player Server ID (source)</td></tr></tbody></table>

***

## Wallet\_GetAccountNumberByNumber

Retrieves the unique wallet account number associated with a specific phone number.

```lua
local accountId = exports["17mov_Phone"]:Wallet_GetAccountNumberByNumber(number)
```

#### Returns:

* `string` / `nil` accountId: The wallet account number or `nil` if not found.

#### Arguments:

<table><thead><tr><th width="112" align="center">Argument</th><th width="87" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Target phone number</td></tr></tbody></table>

***

## Wallet\_AddTransaction

Creates a new transaction between two wallet accounts and synchronizes the data with the active players' phones.

```lua
exports["17mov_Phone"]:Wallet_AddTransaction(fromAccount, toAccount, fromName, toName, description, amount)
```

#### Arguments:

<table><thead><tr><th width="143" align="center">Argument</th><th width="117" align="center">Type</th><th width="114" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">fromAccount</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Sender's wallet account number</td></tr><tr><td align="center">toAccount</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Receiver's wallet account number</td></tr><tr><td align="center">fromName</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Name displayed as the sender</td></tr><tr><td align="center">toName</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Name displayed as the receiver</td></tr><tr><td align="center">description</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Transaction label or description</td></tr><tr><td align="center">amount</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The monetary value of the transaction</td></tr></tbody></table>


# Store

The Store application functions as a centralized hub for managing mobile software and interface enhancements. It allows players to personalize their devices by installing and removing various applications and widgets.&#x20;

### Available Exports

Below is a quick reference list of exports available for the Store application:

`InstallApp`: Installs a specific application or widget onto the player's phone.

`UninstallApp`: Removes a specific application or widget from the player's phone.

To learn more about specific exports, please navigate to the next tab.


# CLIENT EXPORTS

## UninstallApp

Uninstalling the given in parameter app.

```lua
exports["17mov_Phone"]:UninstallApp(appName)
```

#### Arguments:

<table><thead><tr><th width="122" align="center">Argument</th><th width="93" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">appName</td><td align="center"><code>string</code></td><td align="center">❌</td><td>App name from Config.AppDefinitions</td></tr></tbody></table>

***

## InstallApp

Installing the given in parameter app

```lua
exports["17mov_Phone"]:InstallApp(appName)
```

#### Arguments:

<table><thead><tr><th width="122" align="center">Argument</th><th width="93" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">appName</td><td align="center"><code>string</code></td><td align="center">❌</td><td>App name from Config.AppDefinitions</td></tr></tbody></table>


# ACCOUNT MANAGER

The Account Manager is a core module of `17mov_Phone` that handles cross-app user registration, profile data validation, and credential management. It provides a centralized way to create accounts for various integrated social and utility apps.

### Account Data

To create an account via exports, in addition to standard credentials like `username` and `password`, you must provide a dedicated `accountData` table.

{% hint style="warning" %}
AccountData contains app-specific settings and profile details. Each application requires a different data structure. If the data provided does not match the specific app's schema requirements (e.g., missing interests for Swiply or an invalid verification level for Qwuaker), the registration will fail.
{% endhint %}

Below are the classes required for the `accountData` parameter based on the application you are registering for.

<details>

<summary>SwiplyData</summary>

```lua
--- @class SwiplyData
--- @field firstname string Display Name
--- @field lastname string Display Last Name
--- @field aboutme string Profile description
--- @field dateofbirth number Unix Timestamp (seconds or ms)
--- @field gender     0|1|2 = 0: Male, 1: Female, 2: Other
--- @field preference 0|1|2 = 0: Male, 1: Female, 2: Other
--- @field lookingfor 0|1|2 = 0: None, 1: FWB, 2: Relationship
--- @field intrests number[] Array of interest IDs (min 3, max 15)
--- @field photos string[] Array of image URLs (min 2, max 6)
--- @field location {x: number, y: number} Coordinates
```

</details>

<details>

<summary>QwuakerData</summary>

```lua
--- @class QwuakerData
--- @field avatar string URL link to profile picture
--- @field background string URL link to profile background
--- @field name string Display Name
--- @field gender string Display Gender
--- @field verifiedLevel 0|1|2|3 Account verification level (range 0-3)
```

</details>

<details>

<summary>PeargramData</summary>

```lua
--- @class PeargramData
--- @field avatar string URL link to profile picture
--- @field name string Display Name
```

</details>

<details>

<summary>DarkChatData</summary>

```lua
--- @class DarkChatData
--- @field name string Display Name
--- @field avatar string URL link to avatar image
```

</details>

<details>

<summary>EbuyData</summary>

```lua
--- @field name string Display Name
--- @field avatar string URL link to profile image
```

</details>

<details>

<summary>MailData</summary>

```lua
--- @field name string Display Name
--- @field avatar string URL link to profile image
```

</details>

### RegisterAccountBySrc

Creates a new user account for a specific application using the player's Server ID.

```lua
local success = exports["17mov_Phone"]:RegisterAccountBySrc(src, username, password, appName, accountData)
```

Returns:

* `table`: Returns the account object on success, or `object` if validation fails with the error code.

<table><thead><tr><th width="122" align="center">Argument</th><th width="93" align="center">Type</th><th width="103.37109375" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">src</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The server ID of the player.</td></tr><tr><td align="center">username</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The desired login/username</td></tr><tr><td align="center">password</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The account password.</td></tr><tr><td align="center">appName</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Name of the app (Full list available in Config.AppDefinitions)</td></tr><tr><td align="center">accountData</td><td align="center"><code>table</code></td><td align="center">❌</td><td>The specific Data Object for the app (see above)</td></tr></tbody></table>

### RegisterAccountByNumber

Creates a new user account using the player's Phone Number. Useful for offline registrations or number based logic

```lua
local success = exports["17mov_Phone"]:RegisterAccountByNumber(number, username, password, appName, accountData)
```

Returns:

* `table`: Returns the account object on success, or `object` if validation fails with the error code.

<table><thead><tr><th width="122" align="center">Argument</th><th width="93" align="center">Type</th><th width="103.37109375" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">number</td><td align="center"><code>number</code></td><td align="center">❌</td><td>The target player's phone number</td></tr><tr><td align="center">username</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The desired login/username</td></tr><tr><td align="center">password</td><td align="center"><code>string</code></td><td align="center">❌</td><td>The account password.</td></tr><tr><td align="center">appName</td><td align="center"><code>string</code></td><td align="center">❌</td><td>Name of the app (Full list available in Config.AppDefinitions)</td></tr><tr><td align="center">accountData</td><td align="center"><code>table</code></td><td align="center">❌</td><td>The specific Data Object for the app (see above)</td></tr></tbody></table>


# Building custom apps

Welcome to the 17mov\_Phone Custom Apps developer documentation. This guide provides a comprehensive overview of how to create, register, and develop external applications for the 17mov\_Phone ecosystem. Using our boilerplate and exposed APIs, you can extend the phone's functionality with your own React-based applications.

#### Prerequisites

* Basic knowledge of Lua (for server/client registration).
* Knowledge of React.js and TypeScript (for UI development).
* Node.js installed on your development machine.

#### Resources

* Boilerplate Repository: <https://github.com/17movement-net/17mov_Phone_app_boilerplate>


# Getting Started

### Installation

1. Download the boilerplate [repository](https://github.com/17movement-net/17mov_Phone_app_boilerplate).
2. Place the resource in your server's resources folder.
3. Open a terminal in the `web` directory of the resource.
4. Install dependencies:

{% code lineNumbers="true" %}

```bash
npm install
```

{% endcode %}

### Development Mode

To see changes in real-time without rebuilding the application every time:

1. Go to the `config.lua` in your external app resource.
2. Set `Config.DevMode` to `true`.
3. In your terminal (`ui` folder), run:

{% code lineNumbers="true" %}

```bash
npm run dev
```

{% endcode %}

This will start a local development server on `http://localhost:1717`.


# Registering the Application

To make the phone recognize your new application, you must register it using the exported function in your `client/main.lua`.

<details>

<summary><strong>Icon Background Object</strong></summary>

{% code overflow="wrap" lineNumbers="true" %}

```lua
{
    angle = 45,
    colors = { "#FF0000", "#0000FF" }
}
```

{% endcode %}

</details>

<details>

<summary><strong>Job Object</strong></summary>

{% code overflow="wrap" lineNumbers="true" %}

```lua
{
    name = "police",
    grade = 0
}
```

{% endcode %}

</details>

#### AddApplication

Registers a new application icon on the home screen.

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports['17mov_Phone']:AddApplication(appData)
```

{% endcode %}

<table><thead><tr><th width="157" align="center">Property</th><th width="190" align="center">Type</th><th>Description</th></tr></thead><tbody><tr><td align="center"><code>name</code></td><td align="center">string</td><td>Unique identifier for the application.</td></tr><tr><td align="center"><code>label</code></td><td align="center">string</td><td>The display name shown under the icon.</td></tr><tr><td align="center"><code>ui</code></td><td align="center">string</td><td>URL to the app's <code>index.html</code>.</td></tr><tr><td align="center"><code>icon</code></td><td align="center">string</td><td>Path to the icon image.</td></tr><tr><td align="center"><code>iconBackground</code></td><td align="center">string | object</td><td>The <a href="#icon-background-object"><strong>Icon Background Object</strong> </a>or HEX VALUE</td></tr><tr><td align="center"><code>default</code></td><td align="center">boolean</td><td>If <code>true</code>, the app cannot be uninstalled and is preinstalled</td></tr><tr><td align="center"><code>preInstalled</code></td><td align="center">boolean</td><td>If <code>true</code>, the app is installed by default on new phones.</td></tr><tr><td align="center"><code>resourceName</code></td><td align="center">string</td><td>The name of your resource</td></tr><tr><td align="center"><code>rating</code></td><td align="center">float</td><td>Store rating (1-5)</td></tr><tr><td align="center"><code>job</code></td><td align="center">object</td><td>The <a href="#job-object">Job Object</a><a href="#icon-background-object"> </a>or nil. This parameter is optional. Here you can restrict app to certain jobs</td></tr></tbody></table>

#### RemoveApplication

Works only for external apps

{% code lineNumbers="true" %}

```lua
exports['17mov_Phone']:RemoveApplication(data)
```

{% endcode %}

<table><thead><tr><th width="157" align="center">Property</th><th width="190" align="center">Type</th><th>Description</th></tr></thead><tbody><tr><td align="center"><code>name</code></td><td align="center">string</td><td>Same name as in AddApplication</td></tr><tr><td align="center"><code>resourceName</code></td><td align="center">string</td><td>Resource name that registered the app</td></tr><tr><td align="center"><code>uninstall?</code></td><td align="center">boolean</td><td>Whether or not uninstall app from player phone (If this is not set to true, you'll remove app temporary, if it has preInstalled or default value it will automatically install itself again</td></tr></tbody></table>

### Locales

We've introduced system that lets you translate AppName or description in store via locales - depends what language user selects, description and name can be different. In order to do this, you need to specify new locales in locale:<br>

* `Appname:Title` - For changing titles. Please be aware that AppName needs to be capitalized - that means only first letter can be uppercase
* `Store:Appname:Description` - For changing description. Please be aware that AppName needs to be capitalized - that means only first letter can be uppercase


# React Hooks & API

We provide a set of custom hooks to interact with the phone's core functions.

### useNuiEvent

Listens for events sent from the Lua client script.

{% code title="LUA CLIENT" overflow="wrap" lineNumbers="true" %}

```lua
Core.SendNuiMessage("MyCustomEvent", { someData = "Hello" })
```

{% endcode %}

{% code title="REACT" %}

```typescript
import { useNuiEvent } from '@/hooks/useNui';

const MyComponent = () => {
    useNuiEvent<{ someData: string }>('MyCustomEvent', (payload) => {
        console.log(payload.someData); // "Hello"
    });
}
```

{% endcode %}

### useNuiCallback

Triggers a client-side NUI Callback and awaits a response.

{% code title="LUA CLIENT" overflow="wrap" lineNumbers="true" %}

```lua
RegisterNUICallback("Core:MyCallback", function(data, cb)
    print("Received:", data)
    cb({ success = true })
end)
```

{% endcode %}

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
import { useNuiCallback } from '@/hooks/useNui';

const MyComponent = () => {
    const [triggerCallback] = useNuiCallback<string, { success: boolean }>('Core:MyCallback');
    
    useEffect(() => {
        triggerCallback("Data sent to client").then((response) => {
            if (response.success) {
                console.log("Callback successful");
            }
        });
    }, [triggerCallback]);
}
```

{% endcode %}

### useSettings

Retrieves the current player settings (wallpaper, scale, zoom, etc.). You can check the settings structure at `web\src\types\types.ts`

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
const MyComponent = () => {
    const settings = useSettings();
    // Access settings properties here
}
```

{% endcode %}

### useLanguage

Handles localization based on the user's selected language.

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
import { useLanguage } from '@/hooks/useLanguage';

const MyComponent = () => {
    const language = useLanguage();
    
    return <span>{language.getLang("MyTranslationKey")}</span>;
}
```

{% endcode %}

### useNavigateWithApps

A wrapper around the router to ensure smooth transitions between apps.

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
import { useNavigateWithApps } from '@/hooks/useNavigateWithApps';

const MyComponent = () => {
    const navigate = useNavigateWithApps();
    
    const handleClick = () => {
        navigate('/internal-route');
    }
}
```

{% endcode %}


# Native Features Integration

The phone exposes several native components (Camera, Gallery, Inputs) that you can trigger from your app.

### Camera

Opens the phone's camera to take a photo or video.

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
// Callback receives the URL of the uploaded media
const cameraCallback = useCallback((url: string) => {
    console.log('Captured media:', url);
}, []);

// openCameraComponent(callback, disablePhoto, disableVideo)
openCameraComponent(cameraCallback, false, false);
```

{% endcode %}

<table><thead><tr><th width="134" align="center">Argument</th><th width="211" align="center">Type</th><th width="110" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">callback</td><td align="center"><code>(url: string) => void</code></td><td align="center">❌</td><td>Function triggered when media is saved. Receives the URL.</td></tr><tr><td align="center">disablePhoto</td><td align="center"><code>boolean</code></td><td align="center">✅</td><td>If <code>true</code>, the photo mode will be disabled. Default value is <code>false</code></td></tr><tr><td align="center">disableVideo</td><td align="center"><code>boolean</code></td><td align="center">✅</td><td>If <code>true</code>, the video mode will be disabled. Default value is <code>false</code></td></tr></tbody></table>

### Gallery Picker

Opens the gallery for the user to select existing media.

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
const galleryCallback = useCallback((data: string | string[]) => {
    console.log('Selected:', data);
}, []);

// openGalleryPicker(callback, multiple, type, enableLinkInput)
openGalleryPicker(galleryCallback, true, 'image', true);
```

{% endcode %}

<table><thead><tr><th width="114" align="center">Argument</th><th width="298" align="center">Type</th><th width="105" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">callback</td><td align="center"><code>(data: string | string[]) => void</code></td><td align="center">❌</td><td>Triggered when media is selected. Returns URL or array of URLs.</td></tr><tr><td align="center">multiple</td><td align="center"><code>boolean</code></td><td align="center">✅</td><td>If true, user can select multiple items. Default value is <code>false</code></td></tr><tr><td align="center">type</td><td align="center"><code>'image' | 'video' | 'both'</code></td><td align="center">✅</td><td>What type of media should be pickable. Default value is <code>'image'</code></td></tr><tr><td align="center">enableLink</td><td align="center"><code>boolean</code></td><td align="center">✅</td><td>If true, allows user to input a custom URL link. Default value is <code>false</code></td></tr></tbody></table>

### Contact Picker

Opens the contacts list to select a phone number.

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
const contactCallback = useCallback((number: string) => {
    console.log('Selected Number:', number);
}, []);

openContactPicker(contactCallback);
```

{% endcode %}

<table><thead><tr><th width="122" align="center">Argument</th><th width="227" align="center">Type</th><th width="104" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">callback</td><td align="center"><code>(number: string) => void</code></td><td align="center">❌</td><td>Triggered when a contact is selected. Returns the phone number.</td></tr></tbody></table>

### Emoji & GIF Pickers

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
// Emoji
const emojiCallback = useCallback((emoji: string) => {
    console.log('Selected Emoji:', emoji);
}, []);
openEmojiPicker(emojiCallback);

// GIF
const gifCallback = useCallback((gifUrl: string) => {
    console.log('Selected GIF:', gifUrl);
}, []);
openGIFPicker(gifCallback);
```

{% endcode %}

<table><thead><tr><th width="112" align="center">Argument</th><th width="216" align="center">Type</th><th width="109" align="center">Optional</th><th>Explanation</th></tr></thead><tbody><tr><td align="center">callback</td><td align="center"><code>(emoji: string) => void</code></td><td align="center">❌</td><td>Triggered when an emoji/GIF is selected. Returns the emoji char/gif url.</td></tr></tbody></table>

### Listen to AppOpen/AppClose

You can use such event to listen if app was opened or closed

```lua
RegisterNetEvent("17mov_Phone:Client:AppStateChanged", function(appName, state)
    print(appName, state)
end)
```


# Routing & Pages

To add new screens (pages) to your custom application:

{% stepper %}
{% step %}
Create the View

Navigate to `web/src/views` and create your component (e.g., `MyPage.tsx`).

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
const MyPage = () => {
    return <div className="p-4">My Custom Page Content</div>;
};
export default MyPage;
```

{% endcode %}
{% endstep %}

{% step %}
Import the View

Open `web/src/routes/index.tsx` and import your component.

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
import MyPage from "@/views/MyPage";
```

{% endcode %}
{% endstep %}

{% step %}
Register the Route

Add your route to the `AppRoutes` array.

{% code title="REACT" overflow="wrap" lineNumbers="true" %}

```typescript
export const AppRoutes: RouteType[] = [
    {
        path: '/',
        element: <Homepage />,
        className: "bg-white",
    },
    {
        path: '/my-new-page',
        element: <MyPage />,
        className: "bg-gray-100", // Optional wrapper class
    }
];
```

{% endcode %}
{% endstep %}
{% endstepper %}


# Building for Production

When your application is ready for release:

{% stepper %}
{% step %}
Build the React App

Open the terminal in the `web` folder and run:

{% code overflow="wrap" lineNumbers="true" %}

```bash
npm run build
```

{% endcode %}

This creates a `build` folder with the compiled static files.
{% endstep %}

{% step %}
Update Manifest

Open `fxmanifest.lua`.

* Comment out: `ui_page "http://localhost:1717"`
* Uncomment: `ui_page "web/build/index.html"`
  {% endstep %}

{% step %}
Disable Dev Mode

Set `Config.DevMode = false` in your configuration file.
{% endstep %}
{% endstepper %}

Your custom app is now ready to be distributed!


# AUTH

Our phone also offers the capability to create custom applications utilizing our built-in **Auth System**. This grants access to the full **Account Manager**, enabling seamless account creation and management. To learn more details, please proceed to the next page.

You can find an example of a custom application with the built-in authenticator on our GitHub, in the "auth" branch of our [boilerplate](https://github.com/17movement-net/17mov_Phone_app_boilerplate/tree/auth)


# DEFINING GLOBAL TYPES

To ensure Type Safety throughout the application and allow the [**TypeScript**](https://www.typescriptlang.org/) compiler to recognize the new global authentication functions (such as `handleLogin` and `handleRegister`), we must extend the global type declarations.

### Implementation

Open the file `web/src/types.d.ts` and add the following type declarations starting from `handleLogin` down to `useSignOut`. These definitions inform **TypeScript** about the existence, arguments, and return types of these functions, preventing compilation errors in the views and hooks.

{% code overflow="wrap" lineNumbers="true" %}

```typescript
declare function handleLogin(
    username: string,
    password: string
): Promise<{ success: boolean; message: string; }>;

declare function handleRegister(
    username: string,
    password: string,
    accountData: unknown,
): Promise<{ success: boolean; message: string; }>;

declare function useCurrentUser(): { username: string; accountData: unknown } | null;

declare function useIsAuthenticated(): boolean;
declare function useSignOut(): void;
```

{% endcode %}

{% hint style="info" %}
***Note**: The existing utility declarations (e.g., `openGalleryPicker`, `startCall`, `useSettings`, etc.) already present in `types.d.ts` should remain untouched. You only need to append the new authentication declarations listed above.*
{% endhint %}

### Functions Reference

#### `handleLogin`

```typescript
const response = await handleLogin(
    'username', 
    'password'
);
console.log(response);
```

**Arguments**

<table><thead><tr><th width="120" align="center">Name</th><th width="100" align="center">Type</th><th width="100" align="center">Optional</th><th>Description</th></tr></thead><tbody><tr><td align="center"><code>username</code></td><td align="center">string</td><td align="center">❌</td><td>The username to attempt login with.</td></tr><tr><td align="center"><code>password</code></td><td align="center">string</td><td align="center">❌</td><td>The user's password.</td></tr></tbody></table>

**Returns**

<table><thead><tr><th width="120" align="center">Name</th><th width="95" align="center">Type</th><th>Description</th></tr></thead><tbody><tr><td align="center"><code>success</code></td><td align="center">boolean</td><td>The <code>success</code> boolean is <code>true</code> upon success.</td></tr><tr><td align="center"><code>message</code></td><td align="center">string</td><td>The <code>message</code> string provides an error description (if unsuccessful) or a confirmation message.</td></tr></tbody></table>

#### `handleRegister`

**Example**

```typescript
const response = await handleRegister(
    'newuser',
    'securepassword',
    {
        email: 'test@example.com',
        age: 30
    }
);
console.log(response);
```

**Arguments**

<table data-header-hidden><thead><tr><th width="120" align="center">Name</th><th width="100" align="center">Type</th><th width="100" align="center">Optional</th><th>Description</th></tr></thead><tbody><tr><td align="center"><code>username</code></td><td align="center">string</td><td align="center">❌</td><td>The username for the new registration.</td></tr><tr><td align="center"><code>password</code></td><td align="center">string</td><td align="center">❌</td><td>The new user's password.</td></tr><tr><td align="center"><code>accountData</code></td><td align="center">unknown</td><td align="center">✅</td><td>Optional additional account data to be saved (e.g., email, date of birth). Can be <code>null</code>.</td></tr></tbody></table>

**Returns**

<table><thead><tr><th width="120" align="center">Name</th><th width="95" align="center">Type</th><th>Description</th></tr></thead><tbody><tr><td align="center"><code>success</code></td><td align="center">boolean</td><td>The <code>success</code> boolean is <code>true</code> upon success.</td></tr><tr><td align="center"><code>message</code></td><td align="center">string</td><td>The <code>message</code> string provides an error description (if unsuccessful) or a confirmation message.</td></tr></tbody></table>

#### `useCurrentUser`

**Example**

```typescript
const user = useCurrentUser();
if (user) {
    console.log(`Logged in as: ${user.username}`);
}
```

**Returns**

<table><thead><tr><th width="120" align="center">Name</th><th width="100" align="center">Type</th><th>Description</th></tr></thead><tbody><tr><td align="center"><code>username</code></td><td align="center">string</td><td>Current login username</td></tr><tr><td align="center"><code>accountData</code></td><td align="center">unknown</td><td>Return data provided while registering</td></tr></tbody></table>


# CONTEXT & PROVIDERS

**Context** and **Provider** are React mechanisms that allow data to be shared between components without passing props. In our case:

<table data-header-hidden><thead><tr><th width="228"></th><th></th></tr></thead><tbody><tr><td><strong><code>ExternalAuthContext</code></strong></td><td><em>stores information about whether the user is logged in</em></td></tr><tr><td><strong><code>ExternalAuthProvider</code></strong></td><td><em>manages authorization logic and automatic redirects</em></td></tr></tbody></table>

{% stepper %}
{% step %}

#### Creating context

Create file name **`ExternalAuthContext.ts`** in **`web/src/contexts`** and paste following code:

{% tabs %}
{% tab title="web/src/contexts/ExternalAuthContext.ts" %}

```typescript
import { createContext, useContext } from "react";

export type ExternalAuthContextValue = {
    isAuth: boolean;
    logoutUser: () => void;
};

export const ExternalAuthContext = createContext<ExternalAuthContextValue | null>(null);

export const useExternalAuth = () => {
    const ctx = useContext(ExternalAuthContext);
    if (!ctx) {
        throw new Error("useExternalAuth must be used within <ExternalAuthProvider>");
    }
    return ctx;
};
```

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

{% step %}

#### Creating Provider

Create file name **`ExternalAuthProvider.tsx`** in **`web/src/providers`** and paste following code:

{% tabs %}
{% tab title="web/src/providers/ExternalAuthProvider.tsx" %}

```typescript
import { ExternalAuthContext } from "@/contexts/ExternalAuthContext";
import { useNavigateWithApps } from "@/hooks/useNavigateWithApps";
import { useEffect, useCallback } from "react";
import { useLocation } from "react-router-dom";

export const ExternalAuthProvider = ({
    authPage,
    homePage,
    children,
}: {
    authPage: string;
    homePage: string;
    children: React.ReactNode;
}) => {
    const isAuth = useIsAuthenticated();
    const location = useLocation();
    const navigate = useNavigateWithApps();

    const path = location.pathname;
    const isInsideAuth = path === authPage || path.startsWith(authPage + "/");

    const logoutUser = useCallback(() => {
        useSignOut();
        navigate(authPage);
    }, [navigate, authPage]);

    useEffect(() => {
        if (!isAuth) {
            if (!isInsideAuth) navigate(authPage);
            return;
        }

        if (isAuth && isInsideAuth) {
            navigate(homePage);
            return;
        }
    }, [isAuth, isInsideAuth, authPage, homePage, navigate]);

    if (!isAuth && !isInsideAuth) return null;
    if (isAuth && isInsideAuth) return null;

    return (
        <ExternalAuthContext.Provider value={{ isAuth, logoutUser }}>
            {children}
        </ExternalAuthContext.Provider>
    );
};
```

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

{% step %}

#### Integrating the External Authentication Provider

To enable external authentication, the **`ExternalAuthProvider`** must be integrated into the main provider chain within the **`ProvidersManager`** component. Go to **`web/src/providers/index.tsx`**.

1. Import the **`ExternalAuthProvider`** component:

```typescript
import { ExternalAuthProvider } from './ExternalAuthProvider';
```

2. Wrap the **`NuiProvider`** and its children with the **`ExternalAuthProvider`** inside the **`ProvidersManager`**. The new provider should be placed between the **`NavigateProvider`** and the **`NuiProvider`**.

{% tabs %}
{% tab title="web/src/providers/index.tsx" %}

```typescript
import type { ReactNode } from 'react';
import { Provider } from 'react-redux';

import store from '@/store';

import LanguageProvider from './LanguageProvider';
import NavigateProvider from './NavigateProvider';
import NuiProvider from './NuiProvider';
import { ExternalAuthProvider } from './ExternalAuthProvider'

const ProvidersManager = ({ children }: { children: ReactNode }) => {
    return (
        <Provider store={store}>
            <NavigateProvider>
                <ExternalAuthProvider authPage='/auth' homePage='/'>
                    <NuiProvider>
                        <LanguageProvider>{children}</LanguageProvider>
                    </NuiProvider>
                </ExternalAuthProvider>
            </NavigateProvider>
        </Provider>
    );
};

export default ProvidersManager;
```

{% endtab %}
{% endtabs %}

The `authPage` and `homePage` props are required to configure the redirect paths for authentication handling.
{% endstep %}
{% endstepper %}


# VIEWS & ROUTING

In this step, we create the user interface for login and registration, and then register these components in the application routing system so that they are accessible at the appropriate URLs.

{% stepper %}
{% step %}

### Creating views

Create a file `web/src/views/Auth.tsx` with the following code. It contains two main UI components: `AuthLogin` and `AuthRegister`.

* Both components use local state (`useState`) to manage form data (`username`, `password`) and error messages (error).
* The `useLanguage` hook is used for text internationalization and the `useNavigateWithApps` hook is used for navigation after successful login/registration.
* Important note: The `handleLogin` and `handleRegister` functions are defined globally and do not need to be imported.

```typescript
import { useLanguage } from "@/hooks/useLanguage";
import { useNavigateWithApps } from "@/hooks/useNavigateWithApps";
import { useState } from "react";

const AuthLogin = () => {
    const { getLang } = useLanguage();
    const navigate = useNavigateWithApps();
    
    const [username, setUsername] = useState<string>('');
    const [password, setPassword] = useState<string>('');
    const [error, setError] = useState<string | null>(null);

    const handleLoginButton = async () => {
        if (!username || !password) {
            setError(getLang('Auth:Messages.FillAllFields'));
            return;
        }

        const response = await handleLogin(username, password);

        if (response.success) {
            navigate('/');
        } else {
            setError(response.message || getLang('Auth:Errors.Error'));
        }
    }

    return (
        <div className='px-4 pt-10 size-full flex flex-col gap-6 justify-end'>
            <h2 className='text-black dark:text-white text-2xl font-bold text-center'>{getLang('Auth:Pages.Login.Title')}</h2>
            <div className='flex flex-col gap-4'>
                <div className='flex flex-col gap-2'>
                    <div className='flex flex-col gap-1'>
                        <label className='text-sm font-medium text-black dark:text-white'>{getLang('Auth:Pages.Login.Form.Username')}</label>
                        <input
                            type='text'
                            className='h-10 text-sm px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-[#121318] text-black dark:text-white transition duration-300 focus:!border-blue-700 focus:ring-4 focus:ring-blue-700/15 focus:outline-none'
                            value={username}
                            onChange={(e) => {
                                setUsername(e.target.value);
                                if (error) setError(null);
                            }}
                        />
                    </div>
                    <div className='flex flex-col gap-1'>
                        <label className='text-sm font-medium text-black dark:text-white'>{getLang('Auth:Pages.Login.Form.Password')}</label>
                        <input
                            type='password'
                            className='h-10 text-sm px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-[#121318] text-black dark:text-white transition duration-300 focus:!border-blue-700 focus:ring-4 focus:ring-blue-700/15 focus:outline-none'
                            value={password}
                            onChange={(e) => {
                                setPassword(e.target.value);
                                if (error) setError(null);
                            }}
                        />
                    </div>
                </div>
                <div className='flex flex-col gap-2'>
                    {(error && error?.trim() !== '') && (
                        <p className='text-xs text-center text-red-500'>{getLang(error)}</p>
                    )}
                    <button
                        type='button'
                        className='w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition duration-300 disabled:opacity-30'
                        disabled={!username || !password}
                        onClick={handleLoginButton}
                    >
                        {getLang('Auth:Pages.Login.Form.Button')}
                    </button>
                    <p className='text-sm text-center text-gray-500 dark:text-gray-400'>
                        {getLang('Auth:Pages.Login.Form.NoAccount')}{' '}
                        <button 
                            type='button' 
                            className='text-blue-600 hover:underline'
                            onClick={() => navigate('/auth/register')}
                        >
                            {getLang('Auth:Pages.Login.Form.NoAccount.Register')}
                        </button>
                    </p>
                </div>
            </div>
        </div>
    );
};

const AuthRegister = () => {
    const { getLang } = useLanguage();
    const navigate = useNavigateWithApps();
    
    const [username, setUsername] = useState<string>('');
    const [password, setPassword] = useState<string>('');
    const [repeatPassword, setRepeatPassword] = useState<string>('');
    const [error, setError] = useState<string | null>(null);

    const handleRegisterButton = async () => {
        if (!username || !password || !repeatPassword) {
            setError(getLang('Auth:Messages.FillAllFields'));
            return;
        }

        if (password !== repeatPassword) {
            setError(getLang('Auth:Messages.InvalidUsernameOrPassword'));
            return;
        }
        const response = await handleRegister(username, password, null);

        if (response.success) {
            navigate('/');
        } else {
            setError(response.message || getLang('Auth:Errors.Error'));
        }
    }

    return (
        <div className='px-4 pt-10 size-full flex flex-col gap-6 justify-end'>
            <h2 className='text-black dark:text-white text-2xl font-bold text-center'>{getLang('Auth:Pages.Register.Title')}</h2>
            <div className='flex flex-col gap-4'>
                <div className='flex flex-col gap-2'>
                    <div className='flex flex-col gap-1'>
                        <label className='text-sm font-medium text-black dark:text-white'>{getLang("Auth:Pages.Register.Form.Username")}</label>
                        <input
                            type='text'
                            className='h-10 text-sm px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-[#121318] text-black dark:text-white transition duration-300 focus:!border-blue-700 focus:ring-4 focus:ring-blue-700/15 focus:outline-none'
                            value={username}
                            onChange={(e) => {
                                setUsername(e.target.value);
                                if (error) setError(null);
                            }}
                        />
                    </div>
                    <div className='flex flex-col gap-1'>
                        <label className='text-sm font-medium text-black dark:text-white'>{getLang("Auth:Pages.Register.Form.Password")}</label>
                        <input
                            type='password'
                            className='h-10 text-sm px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-[#121318] text-black dark:text-white transition duration-300 focus:!border-blue-700 focus:ring-4 focus:ring-blue-700/15 focus:outline-none'
                            value={password}
                            onChange={(e) => {
                                setPassword(e.target.value);
                                if (error) setError(null);
                            }}
                        />
                    </div>
                    <div className='flex flex-col gap-1'>
                        <label className='text-sm font-medium text-black dark:text-white'>{getLang("Auth:Pages.Register.Form.RepeatPassword")}</label>
                        <input
                            type='password'
                            className='h-10 text-sm px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-[#121318] text-black dark:text-white transition duration-300 focus:!border-blue-700 focus:ring-4 focus:ring-blue-700/15 focus:outline-none'
                            value={repeatPassword}
                            onChange={(e) => {
                                setRepeatPassword(e.target.value);
                                if (error) setError(null);
                            }}
                        />
                    </div>
                </div>
                <div className='flex flex-col gap-2'>
                    {(error && error?.trim() !== '') && (
                        <p className='text-xs text-center text-red-500'>{getLang(error)}{error}</p>
                    )}
                    <button
                        type='button'
                        className='w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition duration-300 disabled:opacity-30'
                        disabled={!username || !password || !repeatPassword}
                        onClick={handleRegisterButton}
                    >
                        {getLang("Auth:Pages.Register.Form.Button")}
                    </button>
                    <p className='text-sm text-center text-gray-500 dark:text-gray-400'>
                        {getLang("Auth:Pages.Register.Form.HasAccount")}{' '}
                        <button 
                            type='button' 
                            className='text-blue-600 hover:underline'
                            onClick={() => navigate('/auth')}
                        >
                            {getLang("Auth:Pages.Register.Form.HasAccount.Login")}
                        </button>
                    </p>
                </div>
            </div>
        </div>
    );
};

export { AuthLogin, AuthRegister };

```

{% endstep %}

{% step %}

### Route Definition

Add new Route objects to the `routes` array, defining the paths for login and registration.

* The path for Login is `/auth` (this is the main path configured in the `ExternalAuthProvider`).
* The path for Registration is `/auth/register`.

{% tabs %}
{% tab title="web/src/routes/index.tsx" %}

```typescript
const routes: Route[] = [
    {
        path: '/',
        component: <Homepage />,
        className: '',
    },
    // ⬅️ ADD THE ROUTES BELOW
    { 
        path: '/auth', 
        component: <AuthLogin />, 
    },
    { 
        path: '/auth/register', 
        component: <AuthRegister />,
    },
    // ... other Routes
];
```

{% endtab %}
{% endtabs %}

Your file should look like this:

{% tabs %}
{% tab title="web/src/routes/index.tsx" %}

```typescript
import type { RouteType } from '@/types/types';

import Homepage from '@/views/Homepage';
import Page from '@/views/Page';
import { AuthLogin, AuthRegister } from '@/views/Auth';

export const AppRoutes: RouteType[] = [
    {
        path: '/',
        element: <Homepage />,
        className: '',
    },
    {
        path: '/page',
        element: <Page />,
        className: '',
    },
    {
        path: '/auth',
        element: <AuthLogin />,
        className: '',
    },
    {
        path: '/auth/register',
        element: <AuthRegister />,
        className: '',
    },
];
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Once these changes are implemented, the application will correctly render the login and registration forms at the `/auth` and `/auth/register` paths.
{% endhint %}
{% endstep %}

{% step %}

### Integrating User Info and Logout on Homepage

Now that the authentication context is set up, we can utilize the user data and the logout function within the main application view (`Homepage`). We'll add a simple user bar displaying the current username and a dedicated Log out button.

#### Importing Hooks

Inside the `Homepage.tsx` file, ensure you have imported the necessary components and hooks:

1. `useCurrentUser`: A global function to retrieve the currently logged-in user object.
2. `useExternalAuth`: The hook to get the context value, specifically the `logoutUser` function.
3. `Power`: The icon used for the logout button (already included in the original component imports).

```typescript
// web/src/views/Homepage.tsx

import { CardSim, ChevronRight, Hammer, Power, SunMoon, UserRound } from 'lucide-react';

import { useNavigateWithApps } from '@/hooks/useNavigateWithApps';
import { useLanguage } from '@/hooks/useLanguage';
import { useExternalAuth } from '@/contexts/ExternalAuthContext'; // ⬅️ IMPORT THIS HOOK
// ... other imports
```

#### Accessing User Data and Logout Function

Inside the `Homepage` component function, access the user data and the logout function:

```typescript
const Homepage = () => {
    const { getLang } = useLanguage();
    const navigate = useNavigateWithApps();
    const settings = useSettings();
    
    const currentUser = useCurrentUser(); // ⬅️ GET CURRENT USER DATA
    const { logoutUser } = useExternalAuth(); // ⬅️ GET LOGOUT FUNCTION

    // ... Callbacks and Handlers below
```

#### Adding the User Info UI Block

Place the following JSX block inside the main return statement of `Homepage`, right after the opening `<div>` with `className='px-4 pt-10 ...'`, to display the user bar at the top of the homepage if the user is logged in.

This block conditionally renders the user's name and a logout button using the retrieved `currentUser` object and the `logoutUser` function.

```typescript
// web/src/views/Homepage.tsx - Inside the return statement:

return (
    <div className='px-4 pt-10 bg-white dark:bg-[#03050B] w-full flex flex-col gap-9'>
        
        {/* ⬅️ ADD THIS BLOCK */}
        {currentUser && (
            <div className='flex items-center justify-between'>
                <div className='flex items-center gap-2.5'>
                    <div className='size-10 rounded-[10px] bg-gradient-to-br from-[#7DA6FF] to-[#1A63FF] flex items-center justify-center text-white'>
                        <UserRound className='size-5' />
                    </div>
                    <div className='flex flex-col'>
                        <h3 className='text-[10px] font-bold text-[#7A7E96]'>{getLang('Userbar:Title')}</h3>
                        <p className='text-sm text-black dark:text-white font-bold'>{currentUser.username}</p>
                    </div>
                </div>
                <button 
                    type='button' 
                    className='size-8 rounded-full bg-[#7A7E96]/15 text-[#7A7E96] flex items-center justify-center transition duration-300 hover:bg-[#7A7E96] hover:text-white' 
                    onClick={logoutUser} // ⬅️ LOGOUT ON CLICK
                >
                    <Power className='size-3' />
                </button>
            </div>
        )}
        {/* ⬅️ END OF USER BAR BLOCK */}
        
        <div className='flex flex-col items-center justify-center gap-4'>
        {/* ... Rest of the component content ... */}
```

{% endstep %}
{% endstepper %}


# TRANSLATIONS FOR AUTH VIEWS

To ensure the new authentication views support multiple languages, you need to define the translation keys for all user-facing text used in `AuthLogin` and `AuthRegister`.

### Defining Keys in Locale File

You must add these translation keys and their corresponding English values to the appropriate locale file, typically `locale/en.lua` (for English).

The format within the `.lua` file is: `["key"] = "value",`

Add the following keys to your `locale/en.lua` file. These keys correspond to the strings used in `Auth.tsx`:

{% tabs %}
{% tab title="locale/en.lua" %}

<pre class="language-lua"><code class="lang-lua"><strong>-- Add these keys to the locale/en.lua file:
</strong>
["Userbar:Title"] = "Welcome",

["Auth:Pages.Login.Title"] = "Login",
["Auth:Pages.Login.Form.Username"] = "Username",
["Auth:Pages.Login.Form.Password"] = "Password",
["Auth:Pages.Login.Form.Button"] = "Login",
["Auth:Pages.Login.Form.NoAccount"] = "Don't have an account?",
["Auth:Pages.Login.Form.NoAccount.Register"] = "Register",

["Auth:Pages.Register.Title"] = "Register",
["Auth:Pages.Register.Form.Username"] = "Username",
["Auth:Pages.Register.Form.Password"] = "Password",
["Auth:Pages.Register.Form.RepeatPassword"] = "Repeat Password",
["Auth:Pages.Register.Form.Button"] = "Register",
["Auth:Pages.Register.Form.HasAccount"] = "Already have an account?",
["Auth:Pages.Register.Form.HasAccount.Login"] = "Login",

["Auth:Errors.Error"] = "An error occurred",
["Auth:Messages.FillAllFields"] = "Please fill in all fields",
["Auth:Messages.AccountCreated"] = "Account created successfully",
["Auth:Messages.InvalidOldPassword"] = "Invalid old password",
["Auth:Messages.InvalidPassword"] = "Invalid password",
["Auth:Messages.InvalidUsernameOrPassword"] = "Invalid username or password",
["Auth:Messages.InvalidUserOrApp"] = "Invalid user ID or app name",
["Auth:Messages.InvalidUser"] = "Invalid user ID",
["Auth:Messages.NoValidUsers"] = "No valid users found",
["Auth:Messages.UsernameExists"] = "Username already exists",
["Auth:Messages.UpdatedSuccessfully"] = "Updated successfully",
["Auth:Messages.AllFieldsRequired"] = "All fields are required",
["Auth:Messages.DifferentPassword"] = "Passwords do not match",
["Auth:Messages.Logout"] = "You have been logged out of the app due to login on another device.",
</code></pre>

{% endtab %}
{% endtabs %}

### Localization for Other Languages

To support other languages, repeat this process by creating or editing the corresponding locale file (e.g., `locale/fr.lua`) and adding the same keys with their appropriate localized values.


# Battery System

This page describes how to enable and configure battery system

## Installation

{% stepper %}
{% step %}

### Enable battery system in config

1. Open `configs/Config.lua`
2. Set `Config.BatterySystem = true`&#x20;
   {% endstep %}

{% step %}

### Add required items

Next step is to add 3 new default items `powerbank_small`, `powerbank`, `powerbank_pro` in your inventory.

{% tabs %}
{% tab title="qb-core" %}

1. Go to your qb-core folder.
2. Open the file: /shared/items.lua
3. Scroll to the end of the file.
4. Before the closing } add the following code:

```lua
["powerbank_small"] = { name = 'powerbank_small', label = 'Small Powerbank', weight = 700, type = 'item', image = 'powerbank_small.png',  unique = true, useable = false, shouldClose = false, description = 'A compact powerbank, fits right in your pocket.' },
["powerbank"]       = { name = 'powerbank',       label = 'Powerbank',       weight = 700, type = 'item', image = 'powerbank.png',        unique = true, useable = false, shouldClose = false, description = 'A reliable powerbank to keep your devices charged on the go.' },
["powerbank_pro"]   = { name = 'powerbank_pro',   label = 'Powerbank Pro',   weight = 700, type = 'item', image = 'powerbank_pro.png',    unique = true, useable = false, shouldClose = false, description = 'High-capacity powerbank for those who never want to run out of juice.' },
```

{% endtab %}

{% tab title="esx default" %}

1. Run this SQL query in your database:

```sql
INSERT IGNORE INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
        ('powerbank_small', 'Small Powerbank', 1, 0, 1),
        ('powerbank', 'Normal Powerbank', 1, 0, 1),
        ('powerbank_pro', 'Powerbank Pro', 1, 0, 1),
        ('powerbank_small_discharged', 'Discharged Small Powerbank', 1, 0, 1),
        ('powerbank_discharged', 'Discharged Normal Powerbank', 1, 0, 1),
        ('powerbank_pro_discharged', 'Discharged Powerbank Pro', 1, 0, 1);
```

{% endtab %}

{% tab title="ox inventory" %}

1. Go to your ox\_inventory folder.
2. open the file: `/data/items.lua`
3. Scroll to the end of the file.
4. Before the closing } add the following code:

```lua
['powerbank_small'] = {
    label = 'Small Powerbank',
    weight = 150,
    client = {
        image = 'powerbank_small.png',
    },
    server = {
        export = '17mov_Phone.powerbank_small'
    },
    consume = 0,
    stack = false
},

['powerbank'] = {
    label = 'Powerbank',
    weight = 150,
    client = {
        image = 'powerbank.png',
    },
    server = {
        export = '17mov_Phone.powerbank'
    },
    consume = 0,
    stack = false
},

['powerbank_pro'] = {
    label = 'Powerbank Pro',
    weight = 150,
    client = {
        image = 'powerbank_pro.png',
    },
    server = {
        export = '17mov_Phone.powerbank_pro'
    },
    consume = 0,
    stack = false
},
```

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

{% step %}

### Installing images into your inventory system

Inside the resource you will find: `installation/images` This folder contains example icons meant for integration with the inventory system used on your server (e.g., qb-inventory, ox\_inventory, etc.).

**Steps:**

1. Copy all powerbank images from the `installation/images` folder.
2. Paste them into the assets folder of your inventory system, for example:

| qb-inventory     | ox\_inventory | ps-inventory | esx\_inventory  |
| ---------------- | ------------- | ------------ | --------------- |
| /html/images     | /web/images   | /html/images | /html/img/items |
| {% endstep %}    |               |              |                 |
| {% endstepper %} |               |              |                 |

After these steps, battery system should be working. You can configure everything related to Battery System inside `configs/Config.lua`, including custom powerbanks, battery lifespan and more.&#x20;

## Exports

### Server

#### ToggleCharging

Toggles charging phone with active number

**params:**

<table><thead><tr><th width="145" align="center" valign="middle">Argument</th><th align="center">Type</th><th align="center">Optional</th><th align="center">Explanation</th></tr></thead><tbody><tr><td align="center" valign="middle"><code>src</code></td><td align="center"><code>number</code></td><td align="center">❌</td><td align="center">The player's server ID (source).</td></tr><tr><td align="center" valign="middle"><code>state</code></td><td align="center"><code>boolean</code></td><td align="center">❌</td><td align="center">Set true to start chargingg, false to stop charging</td></tr><tr><td align="center" valign="middle"><code>chargeValue</code></td><td align="center"><code>number</code></td><td align="center">✅</td><td align="center">How fast phone will be charged (default 10000)</td></tr></tbody></table>


# FAQ

This page answers the most common questions about the script.

<details>

<summary>Error when converting videos on Music App</summary>

if you experience sserver errors when trying to convert music from youtube, it would require you to add your cookies to config. Follow these steps:\
1\. Open Google Chrome\
2\. Add extension Get cookies.txt LOCALLY to your browser\
3\. Open youtube with your account logged in\
4\. Click on the extension, and then click export\
5\. Change name of the downloaded file to cookies.txt\
6\. Drop the file to `17mov_Phone/configs`\
7\. In `configs/ApiKeys.lua` set `API.CookieFile` to `API.CookieFile = "configs/cookies.txt"` \
8\. Restart your server

After completing these steps, the music app should work as intended. However, you may need to repeat them occasionally, as cookies can expire over time.

<br>

</details>


# How to Install?

The script is drag & drop if you're using ESX or QBCore. The only thing you must do is disable or remove old scripts from your server:

#### QBCore

* `qb-loading`
* `qb-multicharacter`
* `qb-spawn`
* `qb-clothing`

#### ESX

* `esx_multicharacter`
* `esx_identity`
* `esx_skin`
* `esx_loadingscreen`
* `skinchanger`
* `illenium-appearance` (\*)

## ⚠️ **Important:**

These scripts cannot run at the same time as this system.\
If they are active, you will get problems like broken skins, overlapping menus, and other conflicts.

{% hint style="info" %}
You can still use **Illenium Appearance or above scripts**, but only if in `Configs/skin.lua` → `Skin.Enabled = false`.
{% endhint %}


# Missing Photos?

This script comes with a feature called **Photos Generation Tool**. The tool allows you to automatically generate and save pictures of all clothes that exist on your server.

If you use addon or replace clothes on your server, it is necessary to run this tool to make sure the pictures display correctly in the skin menu.

<div align="center" data-full-width="false"><figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FcRvKxAUUH3JEmYmjtat1%2Fimage.png?alt=media&amp;token=eedacf24-76c8-4531-9228-9d3ba16e28e4" alt="" width="305"><figcaption><p>Example of the menu without generated pictures</p></figcaption></figure> <figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FvfQOpJZsSvRACbAXDTGk%2Fobraz_2025-09-17_132710568.png?alt=media&amp;token=bd476023-f609-4966-ba1f-eca08cdb16dc" alt="" width="305"><figcaption><p>Example of the menu with generated pictures</p></figcaption></figure></div>

***

To run the tool, use the command  `/photos`  or use one of these methods:

* **With event**: `17mov_CharacterSystem:OpenClothesPhotos`
* **With export**: `exports["17mov_CharacterSystem"]:OpenClothesPhotos()`

## Main Menu

The main menu looks like this:

<figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FRs1UaA8nHqjCM1qQuiI4%2Fimage.png?alt=media&amp;token=56d8e06f-d2a0-4d41-a1f3-5d94c01160ac" alt=""><figcaption></figcaption></figure>

To start, press **Start**, then wait around **10–30 minutes** *(time depends on amount of addon clothes)* for the system to take pictures of all clothes and props.

### Console Output During Work

While the tool is running, the server console will show information about the status of saving pictures.

```markdown
[script:17mov_Charact] [INFORMATION]: Saved photo. Model: mp_m_freemode_01, component: torso_1, drawable: 49
[script:17mov_Charact] [INFORMATION]: Saved photo. Model: mp_m_freemode_01, component: torso_1, drawable: 50
[script:17mov_Charact] [INFORMATION]: Saved photo. Model: mp_m_freemode_01, component: torso_1, drawable: 51
[script:17mov_Charact] [INFORMATION]: Saved photo. Model: mp_m_freemode_01, component: torso_1, drawable: 52
[script:17mov_Charact] [INFORMATION]: Saved photo. Model: mp_m_freemode_01, component: torso_1, drawable: 53
[script:17mov_Charact] [INFORMATION]: Saved photo. Model: mp_m_freemode_01, component: torso_1, drawable: 54
[script:17mov_Charact] [INFORMATION]: Saved photo. Model: mp_m_freemode_01, component: torso_1, drawable: 55
[script:17mov_Charact] [INFORMATION]: Saved photo. Model: mp_m_freemode_01, component: torso_1, drawable: 56
```


# FAQ

This page answers the most common questions about the script.

<details>

<summary>Can I use the menu without photos?</summary>

Yes, if you want to use arrow inputs instead of photos, just delete all pictures from the folder: `/web/photos/` and script will automatically switch to arrow mode.

<div><figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FbC9sqBIX4KUQosLAkuCS%2Fimage.png?alt=media&amp;token=929caadc-4f35-4e7e-8606-7377279077af" alt="" width="162"><figcaption><p>Images Based Input</p></figcaption></figure> <figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FqxZ2YT59M7cZhr7IckGx%2Fobraz_2025-09-17_141231538.png?alt=media&amp;token=050b9479-749d-4b19-a6e1-5aafd3b2345e" alt=""><figcaption><p>Arrows Based Input</p></figcaption></figure></div>

</details>

<details>

<summary>Can I use another skin system or spawn selector?</summary>

Yes, that's why we designed Character System resource as "modular". There is a few modules, that you can disable:

```
- Location Selector
- Register Menu
- Character Selector
- Skin System
```

Each of this module can be disabled and replaced by some another resource.&#x20;

To disable specified module just go to their config file and set `Module.Enabled = false`.&#x20;

For ex. if you want to disable *Location Selector* open `/configs/Location.lua` file, at very top you should see `Location.Enabled = true`, so to disable it just set: `Location.Enabled = false`. After this Location module will be disabled

{% hint style="info" %}
In case of Loadingscreen replace, you would need to navigate into fxmanifest.lua and delete those lines:
{% endhint %}

```
loadscreen "web/index.html"
loadscreen_manual_shutdown "yes"
loadscreen_cursor "yes"
```

</details>

<details>

<summary>Can I limit character slots for players?</summary>

### **Yes, You can limit character slots in two ways:**&#x20;

#### Checking the player's Discord role:

To do this, you first need to configure `configs/Discord.lua` and add the IDs of your Discord roles. Then, you can set how many character slots each role should have.

```lua
Selector.Discord = {
    Enable = true,                                            -- Should enable system?
    Token = "enter_your_discord_bot_token_here",              -- Discord Bot token (bot must be on guild)
    Guild = "enter_your_guild_id_here",                       -- DiscordId of your server guild
    Roles = {                                                 -- There you can add roles and assign number of characters
        -- ["DISCORD_ID_OF_ROLE"] = NUMBER_OF_CHARACTERS,     -- Template
        -- ["1111774118820446259"] = 10,                      -- Example
    }
}
```

#### Adding player identifiers manually in the config

You can also assign the number of character slots **manually** based on the player’s identifier.\
All identifiers supported by **FiveM** are supported here, for example: `discord`, `steam`, `ip`, etc.

```lua
Selector.PlayerMaxCharacters = {
    ["license:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] = 5,
}
```

#### The default ESX `multicharacter_slots` database table is also supported.&#x20;

</details>

<details>

<summary>Can I block specific clothes or reserve them for players?</summary>

**Yes, you can completely disable a specific clothing item in:** `Configs/Skin.lua → Skin.BlacklistedInputValues`:

```lua
Skin.BlacklistedInputValues = { -- Here you can blacklist some clothes (inputs values)
    [`mp_m_freemode_01`] = {
        ["tshirt_1"] = { 15, 16, 17 },
    },
}    
```

This will block the clothing item for everyone. Then, you can reserve the blocked item for:

* A single player
* A specific job
* A gang

in: `Configs/Skin.lua → Skin.WhitelistedInputValues`:

```lua
Skin.WhitelistedInputValues = { 
    [`mp_m_freemode_01`] = {
        {
            name = "tshirt_1",
            values = { 15, 16 },
            jobs = { "police" },
            gangs = { "ballas" },
            identifiers = { "license:8cbd53588ae8a50cf28da72afa411ca2453fde40" }
        },
    },
}
```

</details>


# Common Issues

On this page, you will find the most common errors and their solutions. If your issue is not listed here, or you still have trouble fixing it, please join our Discord where you can get more help

<details>

<summary>Missing Clothing / Missing Photos</summary>

If a clothing item does not show up in the menu at all, this is not an issue with our menu but with the clothing itself - if the clothing works, it **will be automatically added** to the menu.

{% hint style="info" %}
For problems with clothing functionality, please contact the **clothing creators**, not us.
{% endhint %}

If the clothing **shows up but has no picture**, check the guide in the [**"Missing Photos?"**](/character-system/missing-photos) section.

</details>

<details>

<summary>Missing Faces</summary>

In GTA natives, the logic for faces works like this:

* **0–21** → Male faces
* **21–45** → Female faces

Some other menus break this logic. They allow you to pick, for example, two female faces at the same time. This makes no sense, because you can only set female once and then adjust `shapeMix` fully to the mother to get the same result.

In our system, we decided to **split the 45 faces into male and female** groups.

{% hint style="info" %}
If you don’t like this and want it to work like in other menus, you just need to replace the Skin.FemaleFaceTranslations and Skin.MaleFaceTranslations from Configs/Skin.lua to this:
{% endhint %}

<pre class="language-lua"><code class="lang-lua"><strong>Skin.FemaleFaceTranslation = {
</strong>    [0] = 0,
    [1] = 1,
    [2] = 2,
    [3] = 3,
    [4] = 4,
    [5] = 5,
    [6] = 6,
    [7] = 7,
    [8] = 8,
    [9] = 9,
    [10] = 10,
    [11] = 11,
    [12] = 12,
    [13] = 13,
    [14] = 14,
    [15] = 15,
    [16] = 16,
    [17] = 17,
    [18] = 18,
    [19] = 19,
    [20] = 20,
    [21] = 21,
    [22] = 22,
    [23] = 23,
    [24] = 24,
    [25] = 25,
    [26] = 26,
    [27] = 27,
    [28] = 28,
    [29] = 29,
    [30] = 30,
    [31] = 31,
    [32] = 32,
    [33] = 33,
    [34] = 34,
    [35] = 35,
    [36] = 36,
    [37] = 37,
    [38] = 38,
    [39] = 39,
    [40] = 40,
    [41] = 41,
    [42] = 42,
    [43] = 43,
    [44] = 44,
    [45] = 45,
}

Skin.MaleFaceTranslation = {
    [0] = 0,
    [1] = 1,
    [2] = 2,
    [3] = 3,
    [4] = 4,
    [5] = 5,
    [6] = 6,
    [7] = 7,
    [8] = 8,
    [9] = 9,
    [10] = 10,
    [11] = 11,
    [12] = 12,
    [13] = 13,
    [14] = 14,
    [15] = 15,
    [16] = 16,
    [17] = 17,
    [18] = 18,
    [19] = 19,
    [20] = 20,
    [21] = 21,
    [22] = 22,
    [23] = 23,
    [24] = 24,
    [25] = 25,
    [26] = 26,
    [27] = 27,
    [28] = 28,
    [29] = 29,
    [30] = 30,
    [31] = 31,
    [32] = 32,
    [33] = 33,
    [34] = 34,
    [35] = 35,
    [36] = 36,
    [37] = 37,
    [38] = 38,
    [39] = 39,
    [40] = 40,
    [41] = 41,
    [42] = 42,
    [43] = 43,
    [44] = 44,
    [45] = 45,
}
</code></pre>

</details>

<details>

<summary>Addon Faces</summary>

If you're using addon Faces, you need to manually add them into `Skin.FemaleFaceTranslation` and `Skin.MaleFaceTranslation`. This both tables tell script which face should be on which slot for specified sex. Keys are value being displaied into menu and values are numbers from game. So for example:

```
Skin.FemaleFaceTranslation = {
    -- Face "21" from game will be displaied as 0 into menu for female characters
    [0] = 21,
}
```

**If you're using the popular ONX Faces, they're supported out of the box, no configuration needed (Skin.EnableONXCustomFaces setting)**

</details>

<details>

<summary>Cannot Delete Character (error in server console)</summary>

If you see this error in the console when deleting a character, that means you missed some steps when installing your custom phone resource

<div align="left"><figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FI46RiEILO2DkD4XXwm8j%2Fimage.png?alt=media&amp;token=b996d014-922c-450b-b012-d7f69f44f435" alt=""><figcaption></figcaption></figure></div>

Easiest way to fix this issue, is just remove phone tables from `qb-core/server/player.lua` and playertables table (around line 526).

Replace this:

```lua
local playertables = { -- Add tables as needed
    { table = 'players' },
    { table = 'apartments' },
    { table = 'bank_accounts' },
    { table = 'crypto_transactions' },
    { table = 'phone_invoices' },
    { table = 'phone_messages' },
    { table = 'playerskins' },
    { table = 'player_contacts' },
    { table = 'player_houses' },
    { table = 'player_mails' },
    { table = 'player_outfits' },
    { table = 'player_vehicles' }
}

```

With this:

```lua
local playertables = { -- Add tables as needed
    { table = 'players' },
    { table = 'apartments' },
    { table = 'bank_accounts' },
    { table = 'crypto_transactions' },
    { table = 'playerskins' },
    { table = 'player_contacts' },
    { table = 'player_houses' },
    { table = 'player_mails' },
    { table = 'player_outfits' },
    { table = 'player_vehicles' }
}
```

#### ⚠️ This will solve issue with deleting character, but may cause problems with your phone resource. For more specific instructions please contact your phone provider&#x20;

</details>

<details>

<summary>Video on loading screen is not working</summary>

If you configured the **loading screen** correctly but the `.mp4` file is still not showing, this may be caused by two reasons:

* **Render errors** – try re-rendering the file again in `.mp4` format
* **File size too large** – try compressing your video so that its size is **less than 100 MB**

Sometimes FiveM internal http server causes weird problems with serving large files. You can try to upload your video to some external cloud services like [FiveManage ](https://fivemanage.com/)or [Cloudflare R2](https://www.cloudflare.com/developer-platform/products/r2)

</details>

<details>

<summary>Interior Not Loading</summary>

If you are seeing interior failing to load while selecting characters, please check `configs/Selector.lua` and ensure that `Selector.RefreshInterior` is set to `true`.

If that does not fix the issue, you will need to modify default fivem spawn points using instruction below:

1. Navigate to your server's **resources** folder and locate the default map path, which is typically `resources/[cfx-default]/[gamemodes]/[maps]`.
2. Find the two specific resource folders named `fivem-map-hipster` and `fivem-map-skater`.
3. Inside each of these folders, locate and open the file named `map.lua`.
4. Delete all of the existing code within `map.lua` and replace it entirely with the following configuration:

```
'a_m_y_hipster_02' { x = -827.42388916016, y = -730.15417480469, z = 108.13386535645 }
```

5. Restart your server. Now your problem should be gone

</details>


# Exports/Events

## Base Usage

Our script is designed to be **drag-and-drop**. This also means that all **events** and **exports** are bridged from the following resources:

* `esx_skin`
* `skinchanger`
* `qb-clothing`
* `illenium-appearance`

So you can still use events like:

```lua
TriggerEvent('skinchanger:loadSkin')
```

and all others as usual.

***

⚠️ **Important**:\
When using exports for **illenium-appearance**, you must still use `illenium-appearance` as the resource name, **not** `17mov_CharacterSystem`.

{% columns %}
{% column width="50%" valign="middle" %}
✅ Correct:&#x20;

```lua
exports["illenium-appearance"]:setPedComponents()
```

{% endcolumn %}

{% column width="50%" %}
❌ Incorrect:

```lua
exports["17mov_CharacterSystem"]:setPedComponents()
```

{% endcolumn %}
{% endcolumns %}

***

For more details, check the documentation of:

* [**qb-clothing**](https://docs.qbcore.org/qbcore-documentation/qbcore-resources/qb-clothing)
* [**illenium-appearance**](https://docs-illenium-dev-phem.vercel.app/free-resources/illenium-appearance/intro/#features)
* [**skinchanger**](https://docs.esx-framework.org/en/esx_core/skinchanger)

## EXAMPLES

Below you can find examples of events/exports that will work with our resource

{% code title="SAVING CURRENT SKIN EVENT (CLIENT SIDE)" %}

```lua
TriggerEvent("17mov_CharacterSystem:SaveCurrentSkin")
```

{% endcode %}

{% code title="ESX VERSION OF OPENING WARDROBE / OUTFITS MENU (CLIENT SIDE)" %}

```lua
TriggerEvent("17mov_CharacterSystem:OpenOutfitsMenu")
```

{% endcode %}

{% code title="QBCORE VERSION OF OPENING WARDROBE / OUTFITS (CLIENT SIDE)" %}

```lua
TriggerEvent("qb-clothing:client:openOutfitMenu")
```

{% endcode %}

{% code title="ESX VERSION OF OPENING SKIN MENU (CLIENT SIDE)" %}

```lua
TriggerEvent("qb-clothing:client:openMenuCommand")
```

{% endcode %}

{% code title="QBCORE VERSION OF OPENING SKIN MENU (CLIENT SIDE)" %}

```lua
TriggerEvent("esx_skin:openSaveableMenu")
```

{% endcode %}


# Overview

## Introduction

Introducing our groundbreaking Advanced HUD script for your servers! :bulb:

Modern aesthetics, extensive player customization options, and top-tier optimization - these are the essentials your server needs to stand out in the FiveM community. Our latest HUD script is a perfect blend of style, functionality, and performance, designed to enhance the gaming experience for both ESX and QBCore servers. Dive into the details below and discover how our script can transform your server into a more engaging, personalized, and efficient gaming hub!

<figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2Fm0nFnFIDjBVb2zQlEAV7%2Fa5d4b5763a7f208632c047adecee04dac7080a50.jpeg?alt=media&amp;token=9ab501a9-42c9-4145-9d7d-c0c6bd5fe59f" alt=""><figcaption></figcaption></figure>


# Usage in other resources

## Toggle Display

You can toggle display of whole HUD if you need this (ex. in clothing shop).

<pre class="language-lua"><code class="lang-lua"><strong>-- With export
</strong><strong>exports["17mov_Hud"]:ToggleDisplay(state)
</strong><strong>
</strong><strong>-- With client event
</strong><strong>TriggerEvent("17mov_Hud:ToggleDisplay", state)
</strong></code></pre>

<table><thead><tr><th>Argument</th><th>Type</th><th>Optional</th><th>Default Value</th><th>Explanation</th><th data-hidden>Type</th><th data-hidden>Explanation</th><th data-hidden>Optional</th><th data-hidden>Default</th></tr></thead><tbody><tr><td>state</td><td>boolean</td><td><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td>-</td><td>Is HUD should be displayied?</td><td>boolean</td><td>Should display or not?</td><td><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td></td></tr></tbody></table>

## Hide Radar

You can force rader to be hidden, no matter of player settings.

<pre class="language-lua"><code class="lang-lua"><strong>exports["17mov_Hud"]:HideRadar(state)
</strong></code></pre>

<table><thead><tr><th>Argument</th><th>Type</th><th>Optional</th><th>Default Value</th><th>Explanation</th><th data-hidden>Type</th><th data-hidden>Explanation</th><th data-hidden>Optional</th><th data-hidden>Default</th></tr></thead><tbody><tr><td>state</td><td>boolean</td><td><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td>-</td><td>Is radar should be hidden?</td><td>boolean</td><td>Should display or not?</td><td><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td></td></tr></tbody></table>

## Notification

It's showing new notification

```lua
-- With export
exports["17mov_Hud"]:ShowNotification(text, type, title, time)

-- With client event
TriggerEvent("17mov_Hud:ShowNotification", text, type, title, time)
```

<table><thead><tr><th>Argument</th><th>Type</th><th>Optional</th><th>Default Value</th><th>Explanation</th><th data-hidden>Type</th><th data-hidden>Explanation</th><th data-hidden>Default</th></tr></thead><tbody><tr><td>text</td><td>string</td><td><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td>-</td><td>Text of notification</td><td>string: any</td><td>The text of notification</td><td></td></tr><tr><td>type</td><td>"info" | "error" |  "success"</td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span></td><td>"info"</td><td>Type of notification</td><td>string: "info" | "error" | "success"</td><td>Type of notification</td><td></td></tr><tr><td>title</td><td>string</td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span></td><td>Config.Lang["DefaultNotification"]</td><td>Title of notification</td><td>string: any</td><td>The title of notification</td><td></td></tr><tr><td>time</td><td>number</td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span></td><td>string.len(text) * 0.09 + 2000</td><td>Time of displaying notification (in miliseconds)</td><td></td><td></td><td></td></tr></tbody></table>

## Help Notification

It's start showing of Help Notification.

You can use controls like `~INPUT_CONTEXT~` and colors like `~g~`

{% hint style="warning" %}
**It should be used without any loop!**

Later you can need to hide it with `HideHelpNotification()`
{% endhint %}

```lua
-- With export
exports["17mov_Hud"]:ShowHelpNotification(text)

-- With client event
TriggerEvent("17mov_Hud:ShowHelpNotification", text)
```

| Argument | Type   | Optional | Default Value | Explanation               |
| -------- | ------ | -------- | ------------- | ------------------------- |
| text     | string | :x:      | -             | Text of help notification |

## Hide Help Notification

When you first use `ShowHelpNotification()` then you need to hide it with this function.

```lua
-- With export
exports["17mov_Hud"]:HideHelpNotification()

-- With client event
TriggerEvent("17mov_Hud:HideHelpNotification")
```

## Help Notification (while)

There is also avabile version of Help Notification to use into while loops. It's hiding automatically when stop hooking.

```lua
-- With export
exports["17mov_Hud"]:ShowHelpNotificationWhile(text)

-- With client event
TriggerEvent("17mov_Hud:ShowHelpNotificationWhile", text)
```

| Argument | Type   | Optional | Default Value | Explanation               |
| -------- | ------ | -------- | ------------- | ------------------------- |
| text     | string | :x:      | -             | Text of help notification |

## Progress Bar

Starting new progress bar

```lua
-- With export
exports["17mov_Hud"]:StartProgress(action, onStart, onTick, onFinish)

-- With client event
TriggerEvent("17mov_Hud:StartProgress", action, onStart, onTick, onFinish)
```

| Argument | Type     | Optional             | Default Value | Explanation                                                                                              |
| -------- | -------- | -------------------- | ------------- | -------------------------------------------------------------------------------------------------------- |
| action   | object   | :white\_check\_mark: | -             | Look bellow                                                                                              |
| onStart  | function | :white\_check\_mark: | -             | It will be hooked when progress is starting                                                              |
| onTick   | function | :white\_check\_mark: | -             | Hooked every frame of progress                                                                           |
| onFinish | function | :white\_check\_mark: | -             | Hooked when progress has been ended. Returns wasCanceled which are talking is progress has been canceled |

#### Argument action object with default values

```lua
local action = {
    duration = 0, -- Type: number (Progress bar time (in ms))
    label = "", -- Type: string (Progress bar text)
    useWhileDead = false, -- Type: boolean (Can be used while player is dead?)
    canCancel = true, -- Type: boolean (Is can be canceled?)
    controlDisables = { -- If you want to disable some controls set it here
        disableMovement = false, -- Type: boolean (Disable movement controls?)
        disableCarMovement = false, -- Type: boolean (Disable car movement controls?)
        disableMouse = false, -- Type: boolean (Disable mouse controls?)
        disableCombat = false, -- Type: boolean (Disable combat controls?)
    },
    animation = { -- Here you can play some animation/scenario
        animDict = nil, -- Type: string (Animation dict)
        anim = nil, -- Type: string (Animation name)
        flags = 0, -- Type: number (Animation flags)
        task = nil, -- Type: string (Scenario name)
    },
    prop = { -- Spawning prop for progress bar
        model = nil, -- Type: number (Model hash)
        bone = nil, -- Type: string (Bone name)
        coords = vec3(0.0, 0.0, 0.0), -- Type: vector3 (Attachment offsets coords)
        rotation = vec3(0.0, 0.0, 0.0), -- Type: vector3 (Attachment rotation)
    },
    propTwo = { -- Spawning prop for progress bar if you need two props
        model = nil, -- Type: number (Model hash)
        bone = nil, -- Type: string (Bone name)
        coords = vec3(0.0, 0.0, 0.0), -- Type: vector3 (Attachment offsets coords)
        rotation = vec3(0.0, 0.0, 0.0), -- Type: vector3 (Attachment rotation)
    },
}
```

## Stop Progress

With this function you can stop progress bar in any moment. Then onFinish will return false.

```lua
-- With export
exports["17mov_Hud"]:StopProgress()

-- With client event
TriggerEvent("17mov_Hud:StopProgress")
```

## Open Settings

You can open HUD Settings using:

```lua
-- With export
exports["17mov_Hud"]:OpenSettings()

-- With client event
TriggerEvent("17mov_Hud:OpenSettings")
```

## Getting HUD colors

You can get current HUD colors as HEX codes to use them for ex. in chat to made everyting fits.

```lua
-- With export
exports["17mov_Hud"]:GetTheme(function(theme)
    -- local lightmode = theme['dark']
    -- print("Lightmode - Primary color:", lightmode['--color-primary'])
    -- print("Lightmode - Secondary color:", lightmode['--color-secondary'])
    -- print("Lightmode - Text Primary color:", lightmode['--color-text-primary'])
    -- print("Lightmode - Text Secondary color:", lightmode['--color-text-secondary'])
    -- print("Lightmode - Transparent color:", lightmode['--color-transparent'])
    -- local darkmode = theme['dark']
    -- print("Darkmode - Primary color:", darkmode['--color-primary'])
    -- print("Darkmode - Secondary color:", darkmode['--color-secondary'])
    -- print("Darkmode - Text Primary color:", darkmode['--color-text-primary'])
    -- print("Darkmode - Text Secondary color:", darkmode['--color-text-secondary'])
    -- print("Darkmode - Transparent color:", darkmode['--color-transparent'])
end)

-- With client event
TriggerEvent("17mov_Hud:GetTheme", function(theme)
    -- local lightmode = theme['dark']
    -- print("Lightmode - Primary color:", lightmode['--color-primary'])
    -- print("Lightmode - Secondary color:", lightmode['--color-secondary'])
    -- print("Lightmode - Text Primary color:", lightmode['--color-text-primary'])
    -- print("Lightmode - Text Secondary color:", lightmode['--color-text-secondary'])
    -- print("Lightmode - Transparent color:", lightmode['--color-transparent'])
    -- local darkmode = theme['dark']
    -- print("Darkmode - Primary color:", darkmode['--color-primary'])
    -- print("Darkmode - Secondary color:", darkmode['--color-secondary'])
    -- print("Darkmode - Text Primary color:", darkmode['--color-text-primary'])
    -- print("Darkmode - Text Secondary color:", darkmode['--color-text-secondary'])
    -- print("Darkmode - Transparent color:", darkmode['--color-transparent'])
end)
```

Or you can also obtain colors with every update from settings with:

```lua
RegisterNetEvent("17mov_Hud:UpdateTheme", function(theme)
--     local lightmode = theme['dark']
--     print("Lightmode - Primary color:", lightmode['--color-primary'])
--     print("Lightmode - Secondary color:", lightmode['--color-secondary'])
--     print("Lightmode - Text Primary color:", lightmode['--color-text-primary'])
--     print("Lightmode - Text Secondary color:", lightmode['--color-text-secondary'])
--     print("Lightmode - Transparent color:", lightmode['--color-transparent'])
--
--     local darkmode = theme['dark']
--     print("Darkmode - Primary color:", darkmode['--color-primary'])
--     print("Darkmode - Secondary color:", darkmode['--color-secondary'])
--     print("Darkmode - Text Primary color:", darkmode['--color-text-primary'])
--     print("Darkmode - Text Secondary color:", darkmode['--color-text-secondary'])
--     print("Darkmode - Transparent color:", darkmode['--color-transparent'])
end)
```

## Getting HUD settings

You can also get current HUD settings.

```lua
-- With export
exports["17mov_Hud"]:GetSettings(function(settings)
    -- print(settings)
end)

-- With client event
TriggerEvent("17mov_Hud:GetSettings", function(settings)
    -- print(settings)
end)
```

You can also obtain settings with every update:

```lua
RegisterNetEvent("17mov_Hud:UpdateSettings", function(settings)
    -- print(settings)
end)
```


# Custom Indicators

Our HUD allows you to easily extend its functionality by registering your own components, such as a stamina bar, a job label, or even a cryptocurrency tracker.

### 1. RegisterComponent

Use this export to initialize a new element on the HUD. You can choose between a status type (circle near health, armour etc.) or a card type (for text and labels by default in top right).

{% code overflow="wrap" lineNumbers="true" %}

```lua
exports["17mov_Hud"]:RegisterComponent(componentName, componentData)
```

{% endcode %}

Parameters:

* `componentName` (string): A unique identifier for your component.
* `componentData` (table): A configuration table containing:
  * `type`: The style of the component (`"status"` or `"card"`).
  * `label`: The text displayed on the component.
  * `icon`: A table containing `type` (`"svg"` or `"image"`) and the `data` (SVG string or file path).
  * `position`: A table with `x` and `y` coordinates (from 0.0 to 1.0).

### 2. UpdateComponentValue

This export is used to dynamically update the data or progress of an existing component.

```lua
exports["17mov_Hud"]:UpdateComponentValue(name, value)
```

Parameters:

* `name` (string): The unique name of the component you wish to update.
* `value` (any): The new value.
  * For status types, this is typically a float between `0.0` and `1.0`.
  * For card types, this can be any string or number value

### 3. ToggleComponentVisibility

Allows you to show or hide a specific component based on game events (e.g., hiding the stamina bar when the player is in a vehicle).

```lua
exports["17mov_Hud"]:ToggleComponentVisibility(name, value)
```

Parameters:

* `name` (string): The unique name of the component.
* `value` (boolean): Set to `true` to show the component, or `false` to hide it.

### 4. Integration Examples

Below you can find a professional implementation of a Stamina status bar and a Job Information card.

```lua
CreateThread(function()
    -- Registration: Stamina Component (Status Type)
    -- Displays a circular progress for the player's stamina
    exports["17mov_Hud"]:RegisterComponent("stamina", {
        type = "status",
        icon = {
            type = "svg",
            data = [[<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14 22V16.9612C14 16.3537 13.7238 15.7791 13.2494 15.3995L11.5 14M11.5 14L13 7.5M11.5 14L10 13M13 7.5L11 7M13 7.5L15.0426 10.7681C15.3345 11.2352 15.8062 11.5612 16.3463 11.6693L18 12M10 13L11 7M10 13L9.40011 16.2994C9.18673 17.473 8.00015 18.2 6.85767 17.8573L4 17M11 7L8.10557 8.44721C7.428 8.786 7 9.47852 7 10.2361V12M14.5 3.5C14.5 4.05228 14.0523 4.5 13.5 4.5C12.9477 4.5 12.5 4.05228 12.5 3.5C12.5 2.94772 12.9477 2.5 13.5 2.5C14.0523 2.5 14.5 2.94772 14.5 3.5Z" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>]],
        },
    })

    -- Registration: Job Component (Card Type)
    -- Displays a static or dynamic label for the player's current occupation
    exports["17mov_Hud"]:RegisterComponent("job_info", {
        type = "card",
        label = "Current Job",
        icon = {
            type = "svg",
            data = [[<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7 8V6C7 4.34315 8.34315 3 10 3H14C15.6569 3 17 4.34315 17 6V8M7 8H3V18C3 19.6569 4.34315 21 6 21H18C19.6569 21 21 19.6569 21 18V8H17M7 8H17M10 12H14" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>]],
        },
        position = { x = 0.5, y = 0.5 },
    })

    exports["17mov_Hud"]:UpdateComponentValue("job_info", "Unemployed")

    while true do
        Wait(500)
        local exhaustionPercent = GetPlayerSprintStaminaRemaining(PlayerId())
        local remainingStamina = (100.0 - exhaustionPercent) / 100.0
        
        exports["17mov_Hud"]:UpdateComponentValue("stamina", remainingStamina)
    end
end)

```


# Installation Guide

This script was made to be **as simple as possible** to install on your server. The installation has only **one step**: adding the items to your framework (and optional images to your inventory).\
Everything you need is already included in the script files.

***

### Step 1: Copy Images (Optional)

Copy the content from the folder: `installation/images`, paste it into the `images` folder of your inventory system.

\
Below you can find the most common paths for popular inventory systems. If you use another system, the path should be similar. If you cannot find it, ask your inventory provider.

| qb-inventory | ox\_inventory | ps-inventory | esx\_inventory  |
| ------------ | ------------- | ------------ | --------------- |
| /html/images | /web/images   | /html/images | /html/img/items |

***

### Step 2: Add Items to Your Framework

Now you need to add the items. Below are instructions for **QBX**, **QBCore**, and **es\_extended**.

If you are using **es\_extended** or a **custom inventory system**, installation can be different. Please ask your inventory provider how to add new items.

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

1. Go to your `qb-core` folder.
2. Open the file: `/shared/items.lua`&#x20;
3. Scroll to the **end of the file**.
4. Before the closing `}` add the following code:

```lua
['mov_basic_wax']                     = { ['name'] = 'mov_basic_wax', ['label'] = 'Basic Wax', ['weight'] = 10, ['type'] = 'item', ['image'] = 'mov_basic_wax.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['description'] = 'After application on the car, the car is resistant to external dirt, such as driving on unpaved roads, etc. It will still get dirty, but much more slowly. It lasts about 3 days on the car, after which time reapplication is required to maintain the effect.'},
['mov_advanced_ceramic']                     = { ['name'] = 'mov_advanced_ceramic', ['label'] = 'Premium Ceramic', ['weight'] = 10, ['type'] = 'item', ['image'] = 'mov_advanced_ceramic.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['description'] = 'After application on the car, dirt does not stick to the bodywork at all, so at the car wash, it only needs to be rinsed off. It lasts about 7 days on the car, after which time reapplication is required to maintain the effect.'},
['mov_advanced_wax']                     = { ['name'] = 'mov_advanced_wax', ['label'] = 'Advanced Wax', ['weight'] = 10, ['type'] = 'item', ['image'] = 'mov_advanced_wax.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['description'] = 'After application on the car, the car is highly resistant to external dirt, such as driving on unpaved roads, etc. It will still get dirty, but much more slowly. It lasts about 7 days on the car, after which time reapplication is required to maintain the effect.'},
['mov_basic_ceramic']                     = { ['name'] = 'mov_basic_ceramic', ['label'] = 'Basic Ceramic', ['weight'] = 10, ['type'] = 'item', ['image'] = 'mov_basic_ceramic.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['description'] = 'After application on the car, dirt does not stick to the bodywork, making it much easier to wash off at the car wash. It lasts about 3 days on the car, after which time reapplication is required to maintain the effect.'},
```

{% endtab %}

{% tab title="QBX\_Core" %}

1. Go to your `qbx-core` folder.
2. Open the file: `/shared/items.lua`&#x20;
3. Scroll to the **end of the file**.
4. Before the closing `}` add the following code:

```lua
['mov_basic_wax']                     = { ['name'] = 'mov_basic_wax', ['label'] = 'Basic Wax', ['weight'] = 10, ['type'] = 'item', ['image'] = 'mov_basic_wax.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['description'] = 'After application on the car, the car is resistant to external dirt, such as driving on unpaved roads, etc. It will still get dirty, but much more slowly. It lasts about 3 days on the car, after which time reapplication is required to maintain the effect.'},
['mov_advanced_ceramic']                     = { ['name'] = 'mov_advanced_ceramic', ['label'] = 'Premium Ceramic', ['weight'] = 10, ['type'] = 'item', ['image'] = 'mov_advanced_ceramic.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['description'] = 'After application on the car, dirt does not stick to the bodywork at all, so at the car wash, it only needs to be rinsed off. It lasts about 7 days on the car, after which time reapplication is required to maintain the effect.'},
['mov_advanced_wax']                     = { ['name'] = 'mov_advanced_wax', ['label'] = 'Advanced Wax', ['weight'] = 10, ['type'] = 'item', ['image'] = 'mov_advanced_wax.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['description'] = 'After application on the car, the car is highly resistant to external dirt, such as driving on unpaved roads, etc. It will still get dirty, but much more slowly. It lasts about 7 days on the car, after which time reapplication is required to maintain the effect.'},
['mov_basic_ceramic']                     = { ['name'] = 'mov_basic_ceramic', ['label'] = 'Basic Ceramic', ['weight'] = 10, ['type'] = 'item', ['image'] = 'mov_basic_ceramic.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['description'] = 'After application on the car, dirt does not stick to the bodywork, making it much easier to wash off at the car wash. It lasts about 3 days on the car, after which time reapplication is required to maintain the effect.'},
```

{% endtab %}

{% tab title="Default ESX" %}

1. Run this SQL query in your database:

```sql
INSERT IGNORE INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
    ('mov_advanced_ceramic', 'Premium Ceramic', 10, 0, 1),
    ('mov_advanced_wax', 'Advanced Wax', 10, 0, 1),
    ('mov_basic_ceramic', 'Basic Ceramic', 10, 0, 1),
    ('mov_basic_wax', 'Basic Wax', 10, 0, 1)
;
```

{% endtab %}
{% endtabs %}

***

### ⚠️ Map Conflicts

If you are using **custom maps**, you might experience some problems.\
We explain more about this in the next chapter.


# MLO Problems

Because there are many different maps you can install on your server, there is a high chance you may have **conflicts**.

This is **not a problem with our map**. It usually happens because of **duplicate files** on your server.

<div align="center"><figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2Fu0essO4JndDonQArLnVc%2FBez%20tytu%C5%82u.jpg?alt=media&amp;token=452e6d79-6bb7-411f-a0f4-88a45e4557d6" alt="" width="563"><figcaption><p>Example of map occlusion conflict</p></figcaption></figure></div>

***

## What is a Conflict?

Sometimes addon maps, even if they are in **different locations**, share the **same files**. Unfortunately, FiveM cannot load multiple copies of the same streamed files.

The way FiveM decides which file to use is **alphabetical order**:

> Last file loaded wins.

If FiveM does not load one of our files (because another file with the same name was already loaded), you may see problems such as:

* Flickering buildings
* Collision issues
* Missing or broken map parts

***

## How to Fix This?

There is no universal fix, because **every server is using different maps and has different conflicts**.\
But here are two possible solutions you can try:

### Solution 1: Manual Conflict Fix

The idea is simple, find the **duplicate files** and manually **merge them**.

1. Use our automatic conflict scanner. It will show you which files are duplicated automatically after your server starts *(see example below)*

<figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FDTPoqywWHrQPKCkZtBOZ%2Fimage.png?alt=media&amp;token=acc621c8-a436-498d-a923-f14baffda394" alt=""><figcaption><p>This image is only an example, by default 17mov_GarbaceCollector does not conflicts with 17mov_VehicleDirtSystem</p></figcaption></figure>

2. Once you know the files, follow [the tutorial made by our partners NT**eam**](https://www.youtube.com/watch?v=XR9oRhCXdxU). They explain step by step how to merge two files into one

***

### Solution 2: Use 17 Movement Conflict Tool

You can also use our **Conflict Tool**, which automatically finds and helps fix map conflicts on your server.

You can check it out [here](https://17movement.net/products/17mov-conflict-tool).


# How To Remove Stations

If you don’t want to use one of the included car wash locations, you can **easily remove it**. Here is how to do it

### Step 1: Remove Files from the `stream` Folder

Check the table below to see **which files** belong to the car wash you want to remove. Delete those files from your `stream` folder.

<figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FyxLWRDG739wheX9I8kzf%2Fimage.png?alt=media&amp;token=93c30264-774f-4ed0-a499-e6a7bfc1a342" alt=""><figcaption></figcaption></figure>

***

### Step 2: Update the Config File

After deleting the files, don’t forget to also **remove the car wash** from `Config.Stations` in your `Config.lua` file.

And that’s it – the car wash location is now removed.


# Managing Stations

On this page, we will explain how you can **edit** or **create** a new custom location for your server. You can do this in your `Config.lua` file and `Config.Stations` table.

***

## 1. Location Structure

Each location consists of only **3 coordinates** and 3 **values**. This is possible because the system calculates the rest of the needed coordinates automatically, based on the origin points of the objects listed below.

<pre class="language-lua" data-title="Example of Station"><code class="lang-lua"><strong>{
</strong>    cable = vec3(175.11, -1738.57458, 33.186),
    nozzle = vec3(174.521362, -1741.59753, 29.78719),
    nozzleRot = vec3(0.0, 45.0, 0.0),
    interface = vec3(177.0171, -1741.91028, 29.8663769),
    interfaceRot = vec3(0.0, 0.0, -90.0),
    cableLength = 5.0  -- Optional. Default is 5.0
    radius = 3.0,
    price = 10,
    requiredJob = nil,
}
</code></pre>

***

{% tabs %}
{% tab title="Cable" %}
Coordinates of the **hose mount point** (bearing). This is the part of the model:<br>

<figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FoTG7yvlzXdkb9dC3TtWw%2Fimage.png?alt=media&amp;token=17a7fe13-1c62-4e02-ac9c-3a775de7e323" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Nozzle" %}
Coordinates of the **resting position of the lance**. This is where players pick up the lance:

<figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2FuH4LwfYv8Fuzdo5gdVEK%2Fimage.png?alt=media&amp;token=1fe74d62-8cba-4e4f-bc4a-29cff39879cf" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Interface" %}
Coordinates of the **interface point**. This is where players **pay** and **start** the car wash:

<figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2F17ZZhFS4nthCliHBgBy1%2Fimage.png?alt=media&amp;token=2e04472b-1f5d-4fbb-9e2b-34599fb3b9be" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Radius" %}
Defines how far from the **cable origin point** the hose should be attached. For the default model, this value is always: `3.0`
{% endtab %}

{% tab title="cableLength" %}
Defines the **length of the hose in meters** connecting the mount to the lance. Useful if the mount is **higher than 3.5m** above the lance. This prevents the hose from looking like a stiff line and instead simulates **gravity** in the hose physics.
{% endtab %}

{% tab title="Price" %}
The cost in dollars that a player must pay to wash their vehicle.
{% endtab %}
{% endtabs %}

### Required Job

Requires a **string value**. This defines which **server job** can use the lance. If set to `nil`, then **everyone** on the server can use it.

***

## 2. Adding a New Location

To add a new car wash location:

1. Copy the template below.
2. Paste it into the `Config.Stations` table in your config.
3. Fill in the coordinates according to the instructions above.

```lua
{
    cable = vec3(),
    nozzle = vec3(),
    nozzleRot = vec3(),
    interface = vec3(),
    interfaceRot = vec3(),
    radius = 3.0,
    cableLength = 5.0  -- Optional. Default is 5.0
    price = 10,
    requiredJob = nil,
}
```


# Understanding Our Structure

Welcome to our Multiplayer Jobs documentation! Here, we'll explain the structure of our scripts, which are designed to be flexible and easily customizable to meet your needs. Our scripts consist of several folders and files:

#### Web Folder

This folder contains all the files related to the User Interface (UI). It's an open-source folder, allowing you to edit and customize the appearance of the scripts as needed.

#### Client Folder

This folder contains three files:

* `client.lua` (encrypted): This file contains the main script code, and it's not accesible
* `target.lua` (open-source): In this file, you can connect your target system if your `Config.useTarget` option is set to `true`.
* `functions.lua` (open-source): This file contains various functions called at different moments during the game. You can use this file to connect your custom framework, notifications, and adapt the script to your needs. With numerous functions available, you can modify the script even from the backend perspective.

#### Server Folder

This folder contains:

* `server.lua` (encrypted): This file contains the main script code.
* `functions.lua` (open-source): Similar to the client-side `functions.lua`, this file contains various functions called at different moments during the game. You can use this file to connect your custom framework, notifications, and adapt the script to your needs. With numerous functions available, you can modify the script even from the backend perspective.

#### &#x20;Config

The config file is a crucial part of our scripts, as it contains the most important and fundamental options for customization. These options are often in the form of integer or boolean values, allowing you to easily tailor the script to your specific needs.

To modify an option, simply edit the value following the equal sign (=). Each option within the config file is thoroughly described, ensuring a clear understanding of its purpose and functionality.

Here's an overview of what you might typically find in a config file:

* **Enable/Disable Features**: You can enable or disable certain features by changing their boolean values (true/false).
* **Default Values**: Set default values for various parameters, such as the maximum number of players, spawn locations, or job payout amounts.
* **Time Settings**: Adjust time-related settings, such as the duration of specific job stages or cooldown periods between tasks.
* **UI Customization**: Choose between two styles of UI
* **Language Settings**: Define the language for in-game messages and notifications.

Remember to save your changes after editing the config file. By carefully adjusting the options in the config file, you can ensure that the script works seamlessly with your FiveM server and provides an optimal experience for your players.

## End words

By understanding the structure and organization of our scripts, you can easily customize and adapt them to your server requirements. This flexibility ensures that our Multiplayer Jobs scripts will be a perfect fit for your FiveM server.


# Most Used Config Options

Welcome to our page dedicated to the most useful options in the Config file! The Config file is an essential part of any FiveM script, as it contains a plethora of settings and variables that determine how your script works. Understanding how to navigate the Config file and make the right adjustments is crucial for a smooth and successful server operation.

On this page, we will guide you through some of the most important and valuable options in the Config file that can enhance the gameplay experience for both you and your players. From adjusting vehicle settings to fine-tuning the economy, we have got you covered. By the end of this page, you will have a better understanding of how to customize and optimize your server to create the best possible experience for your community.

<table><thead><tr><th width="396">Option</th><th>Description</th></tr></thead><tbody><tr><td><code>Config.useModernUI</code></td><td>Enable modern UI for the job system.</td></tr><tr><td><code>Config.splitReward</code></td><td>Enable party payout splitting when using old UI.</td></tr><tr><td><code>Config.UseTarget</code></td><td>Enable the use of a target system.</td></tr><tr><td><code>Config.UseBuiltInNotifications</code></td><td>Use built-in modern notifications.</td></tr><tr><td><code>Config.RequiredJob</code></td><td>Set the required job for players to participate.</td></tr><tr><td><code>Config.RequireJobAlsoForFriends</code></td><td>Require all party members to have the required job.</td></tr><tr><td><code>Config.RequireOneFriendMinimum</code></td><td>Require at least one player to form a team.</td></tr><tr><td><code>Config.letBossSplitReward</code></td><td>Enable boss to manage whole party rewards.</td></tr><tr><td><code>Config.multiplyRewardWhileWorkingInGroup</code></td><td>Enable reward multiplication based on party size.</td></tr><tr><td><code>Config.EnableVehicleTeleporting</code></td><td>Enable vehicle teleportation.</td></tr><tr><td><code>Config.JobVehicleModel</code></td><td>Set the model of the company vehicle.</td></tr><tr><td><code>Config.PenaltyAmount</code></td><td>Set the penalty amount for finishing work without a company vehicle.</td></tr><tr><td><code>Config.DontPayRewardWithoutVehicle</code></td><td>Don't pay rewards to players who finish without a company vehicle.</td></tr><tr><td><code>Config.DeleteVehicleWithPenalty</code></td><td>Delete vehicle even if it's not a company vehicle.</td></tr><tr><td><code>Config.RequireFullJob</code></td><td>Require players to complete 100% of progress before ending the job.</td></tr><tr><td><code>Config.RequireWorkClothes</code></td><td>Require players to wear work clothes.</td></tr><tr><td><code>Config.RequiredItem</code></td><td>Set the required item for players to participate.</td></tr><tr><td><code>Config.RequireItemFromWholeTeam</code></td><td>Require all party members to have the required item.</td></tr><tr><td><code>Config.RestrictBlipToRequiredJob</code></td><td>Hide job blip for players without the required job.</td></tr><tr><td><code>Config.Blips</code></td><td>Configure the company blip.</td></tr><tr><td><code>Config.MarkerSettings</code></td><td>Configure marker settings.</td></tr><tr><td><code>Config.Locations</code></td><td>Change job locations.</td></tr><tr><td><code>Config.SpawnPoint</code></td><td>Set the company car spawn point.</td></tr><tr><td><code>Config.MixerSpawnPoint</code></td><td>Set the company mixer spawn point.</td></tr><tr><td><code>Config.EnableCloakroom</code></td><td>Enable the use of a cloakroom.</td></tr><tr><td><code>Config.Clothes</code></td><td>Configure work clothes for male and female characters.</td></tr><tr><td><code>Config.Lang</code></td><td>Change translations used in the script. Remember that you also have to translate script in HTML file</td></tr><tr><td><code>Config.JobBlipsStyle</code></td><td>Configure job blips.</td></tr><tr><td><code>Config.Reward</code></td><td>Set the reward in Gruppe Sechs and Treasure Hunter</td></tr><tr><td><code>Config.Price</code></td><td>Set the price per every stop/bag in Deliverer, Garbage Collector, Window Cleaning and Postman</td></tr><tr><td><code>Config.OnePercentWorth</code></td><td>Set the payout value for 1% of progress in Builder Job.</td></tr><tr><td><code>Config.Payments</code></td><td>Set the reward for each level in Electrician Job</td></tr></tbody></table>


# Common Issues

Welcome to the "Common Issues" page! This section is dedicated to addressing problems that frequently occur, are not caused by our platform, and can be easily fixed. Our aim is to provide you with quick solutions and guidance to help you overcome these issues and enjoy a seamless experience.\
\
Please note that if a script is not listed here, it means that we have not yet identified any common issues for it. We constantly update this page as new information becomes available, so be sure to check back regularly for the latest tips and solutions.

If you encounter a problem that is not covered on this page, please feel free to contact our support team for further assistance. We're always here to help!<br>

<br>


# Electrician Game Freeze

A common issue encountered in the electrician job is that after interacting with the first minigame, the screen freezes, and players are unable to perform any actions. This problem usually arises when you are not using our official and modified version of the electrician minigame, but instead are using a default version downloaded from the internet.

To avoid this issue, make sure to use the proper version of the script available on the Keymaster. This version has been specifically adapted for the electrician job and is included in the package. The script is called Howdy-Minigame. By using this version of the minigame, you can ensure a smooth and enjoyable experience for players while avoiding the screen freeze problem.


# Black Screen on Start

Are you facing a problem with a black screen on startup while working on your system? If yes, then don't worry, we have a solution for you. This issue usually occurs due to a problem in the fuel system in the /client/functions.lua file.

However, we cannot provide a universal solution for every system, as every fuel system is different. Therefore, we suggest you either connect your own fuel system or remove the existing one.

To remove the fuel system, follow the steps mentioned below:

Original code:

<pre class="language-lua"><code class="lang-lua">function SetVehicle(vehicle)
<strong>    -- Setup here your vehicle keys, fuel etc..
</strong>    
    if Config.Framework == "QBCore" then
        exports['LegacyFuel']:SetFuel(vehicle, 100.0)
        TriggerEvent("vehiclekeys:client:SetOwner", Core.Functions.GetPlate(vehicle))
    elseif Config.Framework == "ESX" then
    
    else
    
    end
end
</code></pre>

Modified code:

```lua
function SetVehicle(vehicle)

    -- Setup here your vehicle keys, fuel etc..

    if Config.Framework == "QBCore" then
        TriggerEvent("vehiclekeys:client:SetOwner", Core.Functions.GetPlate(vehicle))
    elseif Config.Framework == "ESX" then

    else

    end
end
```

By following these steps, you can remove the fuel system and solve the black screen issue on startup.

<br>


# Job Clothes Issue

### Bad Looking Clothes

If you're experiencing issues with your job clothing our scripts, such as clothes looking different than intended or having holes, it's likely caused by modified clothing packs that change the order of the clothing items. To fix this issue, you need to adjust the numbers of your clothes in the Config.Clothes file for both male and female clothing. The quickest way to do this is to first wear the clothes in-game, then copy the numbers of your clothes into the configuration file.

### Not working clothes at all

If you're using a framework other than ESX and QBCore, you'll need to configure your entire clothing system in the /client/functions.lua file to ensure that it works with the script's ability to restore your original clothing. Unfortunately, we can't provide universal guidance as every clothing system is different. However, the goal of this modification is to adapt the clothing restoration to the defaults.

After properly connecting your clothing system to our script, don't forget to remove the following code that cancels the function execution at the top of the file:

{% code overflow="wrap" %}

```lua
if Config.Framework ~= "QBCore" and Config.Framework ~= "ESX" then
    print("CANNOT CHANGE CLOTHES, PLEASE CONFIGURE UR CLOTHES SYSTEM IN /Client/Functions.lua file.")
    return
end
```

{% endcode %}


# Overview

## Available Languages

You can check the available languages [here](/speech-recognition/available-languages)

## Debug

```lua
Config.Debug = true
```

By enabling this the speech which is detected printed into F8 Console

## Hud Indicator UI

```lua
Config.EnableHudIndicators = true
```

&#x20;Set to false, if you want to delete the red/orange/green microphones icon in corner which indicates the speech recognition to the player and looks like this

<figure><img src="https://3673882971-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFxWJGV2Spk1WhY0R0XVU%2Fuploads%2F4SmMohRuzQmLAFRX1ytH%2FScreenshot%20(11).png?alt=media&amp;token=4949f3be-7914-4b05-a5e6-51f11a956607" alt=""><figcaption><p>ONLY ICON IS SHOWN ON SCREEN NOT THE TEXT [ TEXT IS TO SHOW THE DIFFERENCE BETWEEN THE ICONS ]</p></figcaption></figure>


# Available Languages

```lua
    Afrikaans (south africa)	af-za
    afrikaans (south africa)	af-za
    albanian (albania)	        sq-al
    amharic (ethiopia)	        am-et
    arabic (algeria)	        ar-dz
    arabic (bahrain)	        ar-bh
    arabic (egypt)	        ar-eg
    arabic (israel)	        ar-il
    arabic (jordan)	        ar-jo
    arabic (kuwait)	        ar-kw
    arabic (lebanon)	        ar-lb
    arabic (mauritania)	        ar-mr
    arabic (morocco)	        ar-ma
    arabic (oman)	        ar-om
    arabic (qatar)	        ar-qa
    arabic (saudi arabia)	ar-sa
    arabic (state of palestine)	ar-ps
    arabic (tunisia)	        ar-tn
    arabic (UAE)	        ar-ae
    arabic (yemen)	        ar-ye
    armenian (armenia)	        hy-am
    azerbaijani (azerbaijan)	az-az
    basque (spain)	        eu-eswhat 
    bengali (bangladesh)	bn-bd
    bengali (india)	        bn-in
    bosnian (herzegovina)       bs-ba
    bosnian (herzegovina)       bs-ba
    bulgarian (bulgaria)	bg-bg
    burmese (myanmar)	        my-mm
    catalan (spain)	        ca-es
    croatian (croatia)	        hr-hr
    czech (czech republic)	cs-cz
    danish (denmark)	        da-dk
    dutch (belgium)	        nl-be
    dutch (netherlands)	        nl-nl
    english (australia)	        en-au
    english (canada)	        en-ca
    english (ghana)	        en-gh
    english (hong kong)	        en-hk
    english (india)	        en-in
    english (ireland)	        en-ie
    english (kenya)	        en-ke
    english (new zealand)	en-nz
    english (nigeria)	        en-ng
    english (pakistan)	        en-pk
    english (philippines)	en-ph
    english (singapore)	        en-sg
    english (south africa)	en-za
    english (tanzania)	        en-tz
    english (united kingdom)	en-gb
    english (united states)	en-us
    estonian (estonia)	        et-ee
    filipino (philippines)	fil-ph
    finnish (finland)	        fi-fi
    french (french)	        fr-fr
    french (belgium)	        fr-be
    french (canada)	        fr-ca
    french (switzerland)	fr-ch
    galician (spain)	        gl-es
    georgian (georgia)	        ka-ge
    german (austria)	        de-at
    german (germany)	        de-de
    german (switzerland)	de-ch
    greek (greece)	        el-gr
    gujarati (india)	        gu-in
    hebrew (israel)	        iw-il
    hindi (india)	        hi-in
    icelandic (iceland)	        is-is
    indonesian (indonesia)	id-id
    italian (italy)	        it-it
    italian (switzerland)	it-ch
    japanese (japan)	        ja-jp
    javanese (indonesia)	jv-id
    kannada (india)	        kn-in
    kazakh (kazakhstan)	        kk-kz
    khmer (cambodia)	        km-kh
    korean (south korea)	ko-kr
    lao (laos)	lo-la
    latvian (latvia)	        lv-lv
    lithuanian (lithuania)	lt-lt
    macedonian(north macedonia) mk-mk
    malay (malaysia)	        ms-my
    malayalam (india)	        ml-in
    marathi (india)	        mr-in
    mongolian (mongolia)	mn-mn
    nepali (nepal)	        ne-np
    norwegian bokmal (norway)	no-no
    persian (iran)	        fa-ir
    polish (poland)	        pl-pl
    portuguese (brazil)	        pt-br
    portuguese (portugal)	pt-pt
    punjabi (gurmukhi india)	pa-guru-in
    romanian (romania)	        ro-ro
    russian (russia)	        ru-ru
    kinyarwanda (rwanda)	rw-rw
    serbian (serbia)	        sr-rs
    sinhala (sri lanka)	        si-lk
    slovak (slovakia)	        sk-sk
    swati (south africa)	ss-latn-za
    southernsotho(south africa) st-za
    spanish (argentina)	        es-ar
    spanish (bolivia)	        es-bo
    spanish (chile)	        es-cl
    spanish (colombia)	        es-co
    spanish (costa rica)	es-cr
    spanish (dominican)	        es-do
    spanish (ecuador)	        es-ec
    spanish (el salvador)	es-sv
    spanish (guatemala)	        es-gt
    spanish (honduras)	        es-hn
    spanish (mexico)	        es-mx
    spanish (nicaragua)	        es-ni
    spanish (panama)	        es-pa
    spanish (paraguay)	        es-py
    spanish (peru)	        es-pe
    spanish (puerto rico)	es-pr
    spanish (spain)	        es-es
    spanish (united states)	es-us
    spanish (uruguay)	        es-uy
    spanish (venezuela)	        es-ve
    sundanese (indonesia)	su-id
    swahili (kenya)	        sw-ke
    swahili (tanzania)	        sw-tz
    swedish (sweden)	        sv-se
    tamil (india)	        ta-in
    tamil (malaysia)	        ta-my
    tamil (singapore)	        ta-sg
    tamil (sri lanka)	        ta-lk
    telugu (india)	        te-in
    thai (thailand)	        th-th
    setswana (south africa)	tn-latn-za
    turkish (turkey)	        tr-tr
    tsonga (south africa)	ts-za
    ukrainian (ukraine)        	uk-ua
    urdu (india)	        ur-in
    urdu (pakistan)        	ur-pk
    uzbek (uzbekistan)	        uz-uz
    venda (south africa)	ve-za
    vietnamese (vietnam)	vi-vn
    isixhosa (south africa)	xh-za
    zulu (south africa)	        zu-za
    cantonese (hong kong)	yue-hant-hk
    mandarin (china)	        zh (cmn-hans-cn)
    mandarin (taiwan)	        zh-tw (cmn-hant-tw)
```


# Basic Stuff

## Player Loaded Event

```lua
Config.PlayerLoadedEvents = {
    ["QBCore:Client:OnPlayerLoaded"] = true,
    ["esx:playerLoaded"] = true,
    ["playerSpawned"] = true,
}
```

If you are using any other framework like vRP or Standalone Server you should add the event here based on the Framework

## On First Talk

```lua
Config.ApprovalRequestMode = "onFirstTalk"
```

**Approval Request Modes:**\
\
"onFirstTalk" - When player will want to talk something first time on server (only after loading)

"onFirstJoin" - When player will first join ur server (after ur Config.PlayerLoadedEvents event)

"onEvent" - You can open the apporval menu anytime you want. Select this option, and then trigger the "17mov\_SpeechRecognition:OpenApprovalMenu" event




---

[Next Page](https://docs.17movement.net/llms-full.txt/1)

