> For the complete documentation index, see [llms.txt](https://docs.lgmods.co.uk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.lgmods.co.uk/scripts/lg-dispatch.md).

# LG Dispatch

## Exports & Integration

LG Dispatch exposes a small set of exports so other resources can talk to the dispatch system.

You can use these to:

* Create dispatch calls from robbery, alarm, or shooting scripts
* Trigger officer panic alerts
* Read active calls from another resource

{% hint style="danger" %}
**Resource folder name matters**

The resource folder **must** be named exactly `LG_Dispatch`.

If you rename it (e.g. `lg-dispatch` or `lg_dispatch`), every example on this page will fail unless you also change the export name to match your folder.
{% endhint %}

{% hint style="warning" %}
**Server vs client**

Calling a server export from a client script will not work.
{% endhint %}

| Export               | Side            |
| -------------------- | --------------- |
| `CreateDispatchCall` | **Server only** |
| `GetActiveCalls`     | **Server only** |
| `TriggerPanic`       | **Client only** |

***

### CreateDispatchCall

**Side:** Server only

Creates a new call and broadcasts it to all on-duty emergency units.

```lua
exports['LG_Dispatch']:CreateDispatchCall(callData)
```

#### Example

```lua
-- server.lua (YOUR resource)
local callId = exports['LG_Dispatch']:CreateDispatchCall({
    type        = 'robbery',
    priority    = 1,
    coords      = { x = 215.3, y = -810.5, z = 30.7 },
    street      = 'Strawberry Ave',
    description = 'Armed robbery at Fleeca Bank',
    suspectInfo = 'IC1 male wearing black hoodie',
    vehicleInfo = 'Red Dominator, plate ABC123',
    createdBy   = 'Store Alarm',
})

if callId then
    print('Dispatch call created: #' .. callId)
else
    print('Call was not created (likely deduplicated)')
end
```

#### Returns

| Value    | Meaning                                                                    |
| -------- | -------------------------------------------------------------------------- |
| `number` | The new call ID                                                            |
| `nil`    | Call was blocked by deduplication (same type, within radius + time window) |

#### Fields

**`type` (string, optional)**

Call type key from `Config.CallTypes`.

Defaults to `'general'` if missing or unknown.

```lua
type = 'robbery'
```

Built-in types: `999`, `panic`, `robbery`, `shooting`, `traffic`, `medical`, `fire_call`, `general`

**`priority` (number, optional)**

Grade / priority level (`1`–`4`).

If omitted, uses the call type’s `defaultPriority`.

```lua
priority = 1
```

**`coords` (table, optional)**

Incident world coordinates.

**Must use named keys** `x`, `y`, `z`:

```lua
coords = { x = 215.3, y = -810.5, z = 30.7 }
```

`vector3` also works:

```lua
coords = vector3(215.3, -810.5, 30.7)
```

{% hint style="info" %}
Do **not** pass array-style coords like `{ 215.3, -810.5, 30.7 }` — map markers and dedupe will break.
{% endhint %}

**`street` (string, optional)**

Location / street name.

Defaults to `'Unknown Location'`.

```lua
street = 'Strawberry Ave'
```

**`description` (string, optional)**

Main incident description.

Defaults to the call type label.

```lua
description = 'Armed robbery at Fleeca Bank'
```

**`callerNotes` (string, optional)**

Extra notes from the caller / system.

```lua
callerNotes = 'Caller reports suspect still inside'
```

**`suspectInfo` (string, optional)**

Suspect description.

```lua
suspectInfo = 'IC1 male wearing black hoodie'
```

**`vehicleInfo` (string, optional)**

Vehicle / plate info.

```lua
vehicleInfo = 'Red Dominator, plate ABC123'
```

**`postal` (string, optional)**

Postal code (if your server uses postals).

```lua
postal = '123'
```

**`createdBy` (string, optional)**

Who/what created the call.

Defaults to `'System'`.

```lua
createdBy = 'Store Alarm'
```

***

### Creating a call from a client script

Most robbery / alarm scripts run logic on the **client**.\
`CreateDispatchCall` is **server-only**, so bridge it through your own server event.

#### Client

```lua
-- client.lua (YOUR resource)
RegisterNetEvent('my-robbery:client:alertPolice', function()
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    local streetHash = GetStreetNameAtCoord(coords.x, coords.y, coords.z)
    local street = GetStreetNameFromHashKey(streetHash)

    TriggerServerEvent('my-robbery:server:createDispatch', {
        type        = 'robbery',
        priority    = 1,
        coords      = { x = coords.x, y = coords.y, z = coords.z },
        street      = street,
        description = 'Armed robbery in progress',
        createdBy   = 'Robbery Script',
    })
end)
```

#### Server

```lua
-- server.lua (YOUR resource)
RegisterNetEvent('my-robbery:server:createDispatch', function(data)
    if GetResourceState('LG_Dispatch') ~= 'started' then return end

    local callId = exports['LG_Dispatch']:CreateDispatchCall(data)
    if not callId then
        print('[my-robbery] Dispatch call skipped (duplicate or failed)')
    end
end)
```

{% hint style="success" %}
Make sure `LG_Dispatch` is started **before** your integrating resource in `server.cfg`.
{% endhint %}

***

### GetActiveCalls

**Side:** Server only

Returns all currently active (non-closed) dispatch calls.

```lua
exports['LG_Dispatch']:GetActiveCalls()
```

#### Example

```lua
-- server.lua
local calls = exports['LG_Dispatch']:GetActiveCalls()

for _, call in ipairs(calls) do
    print(call.id, call.type, call.street)
end
```

#### Returns

`table` — array of call objects.

Useful fields on each call:

| Field           | Type   | Notes                              |
| --------------- | ------ | ---------------------------------- |
| `id`            | number | Call ID                            |
| `type`          | string | Call type key                      |
| `typeLabel`     | string | Display label                      |
| `priority`      | number | Grade 1–4                          |
| `status`        | string | `'active'` / `'closed'`            |
| `location`      | table  | `{ x, y, z }` (not named `coords`) |
| `street`        | string | Location name                      |
| `postal`        | string | Postal                             |
| `description`   | string | Description                        |
| `callerNotes`   | string | Notes                              |
| `suspectInfo`   | string | Suspects                           |
| `vehicleInfo`   | string | Vehicle info                       |
| `createdBy`     | string | Creator                            |
| `createdAt`     | number | Unix timestamp                     |
| `attachedUnits` | table  | Units attached to the call         |

***

### TriggerPanic

**Side:** Client only

Triggers a Grade 1 officer assistance (`panic`) call at the local player’s current location.

```lua
exports['LG_Dispatch']:TriggerPanic()
```

#### Example

```lua
-- client.lua
exports['LG_Dispatch']:TriggerPanic()
```

#### Requirements

* The player must be an eligible emergency job
* The player must be **registered / on duty** in LG Dispatch

If those are not met, the export returns silently and **no call is created**.

#### What happens

1. A Grade 1 `panic` call is created
2. The officer’s current coords + street are used
3. The call is broadcast to eligible units
4. The panic sound plays if `Config.Sounds.panic = true`

Built-in command / keybind:

* Command: `/panic`
* Default key: `F9`

***

### Deduplication

LG Dispatch ignores similar calls to stop spam.

Defaults:

```lua
Config.DedupeRadius = 50.0  -- game units
Config.DedupeTime   = 30    -- seconds
```

A new call is skipped (export returns `nil`) when:

* an active call already exists with the **same type**
* within `DedupeRadius`
* created within the last `DedupeTime` seconds

This is intentional. If your script “sometimes doesn’t create a call”, check dedupe first.

***

### Common mistakes

| Problem                                | Cause                               | Fix                                                              |
| -------------------------------------- | ----------------------------------- | ---------------------------------------------------------------- |
| Export does nothing / errors on client | Calling server export from client   | Create a server event and call the export there                  |
| `No such export` / nil export          | Resource renamed                    | Keep folder name as `LG_Dispatch` or update export name          |
| Call returns `nil`                     | Deduplication                       | Wait, move further away, or adjust `DedupeRadius` / `DedupeTime` |
| Panic does nothing                     | Player not on duty / not registered | Ensure unit is registered in dispatch first                      |
| Marker at `0,0,0`                      | Missing/invalid `coords`            | Pass `{ x = ..., y = ..., z = ... }`                             |
| Wrong call label                       | Unknown `type`                      | Use a key from `Config.CallTypes`, or add your own               |

***

### Default Configuration

Below is the default-style configuration included with LG Dispatch.

{% hint style="info" %}
Edit `config.lua` only. Do not edit escrowed files.
{% endhint %}

```lua
Config = {}

-- Framework detection: 'auto', 'qbcore', 'esx', 'tmc', 'qbox'
Config.Framework = 'auto'

Config.UseLGModsCAD = false -- leave false... future script.

-- Keybind to open dispatch UI
Config.OpenKey = 'F6'

-- Jobs considered emergency services
Config.EligibleJobs = {
    ['police']    = { label = 'Police',    type = 'police', icon = 'shield-halved', color = '#3b82f6' },
    ['ambulance'] = { label = 'Ambulance', type = 'Ambulance', icon = 'heart-pulse',  color = '#22c55e' }
}

-- Unit statuses
Config.Statuses = {
    { id = 'available',  label = 'Available',  color = '#22c55e' },
    { id = 'busy',       label = 'Busy',       color = '#f59e0b' },
    { id = 'enroute',    label = 'En Route',   color = '#3b82f6' },
    { id = 'onscene',    label = 'On Scene',   color = '#8b5cf6' },
    { id = 'atstation',  label = 'At Station', color = '#64748b' },
    { id = 'offduty',    label = 'Off Duty',   color = '#6b7280' },
}

-- Call priorities
Config.Priorities = {
    { id = 1, label = 'Grade 1 - Immediate', color = '#ef4444' },
    { id = 2, label = 'Grade 2 - Prompt',    color = '#f59e0b' },
    { id = 3, label = 'Grade 3 - Standard',  color = '#3b82f6' },
    { id = 4, label = 'Grade 4 - Scheduled', color = '#64748b' },
}

-- Call types with defaults
Config.CallTypes = {
    ['999']       = { label = '999 Emergency Call',  defaultPriority = 2, blip = true,  blipSprite = 480, blipColor = 1,  blipDuration = 30, autoExpire = 1800 },
    ['panic']     = { label = 'Officer Assistance',  defaultPriority = 1, blip = true,  blipSprite = 526, blipColor = 1,  blipDuration = 60, autoExpire = 900  },
    ['robbery']   = { label = 'Robbery in Progress', defaultPriority = 1, blip = true,  blipSprite = 156, blipColor = 1,  blipDuration = 45, autoExpire = 1200 },
    ['shooting']  = { label = 'Firearms Discharge',  defaultPriority = 1, blip = true,  blipSprite = 110, blipColor = 1,  blipDuration = 45, autoExpire = 1200 },
    ['traffic']   = { label = 'Vehicle Stop',        defaultPriority = 3, blip = true,  blipSprite = 326, blipColor = 38, blipDuration = 20, autoExpire = 1800 },
    ['medical']   = { label = 'Medical Assistance',  defaultPriority = 2, blip = true,  blipSprite = 153, blipColor = 2,  blipDuration = 30, autoExpire = 1200 },
    ['fire_call'] = { label = 'Fire Incident',       defaultPriority = 2, blip = true,  blipSprite = 436, blipColor = 1,  blipDuration = 30, autoExpire = 1200 },
    ['general']   = { label = 'Incident Log',        defaultPriority = 3, blip = false, blipSprite = 1,   blipColor = 0,  blipDuration = 0,  autoExpire = 3600 },
}

-- GPS update interval (ms) for live unit tracking
Config.GPSUpdateInterval = 2000

-- Ignore similar calls within this radius (game units) and time (seconds)
Config.DedupeRadius = 50.0
Config.DedupeTime = 30

-- Sounds
Config.Sounds = {
    newCall = true,
    panic   = true,
}

-- Permissions (minimum job grade required per job)
-- Keys must match the job names in Config.EligibleJobs.
-- Use ['*'] as a fallback for any job not explicitly listed.
Config.Permissions = {
    closeCalls = {
        police    = 0,
        ambulance = 0,
    },
    editPriority = {
        police    = 2,
        ambulance = 2,
    },
    addNotes = {
        police    = 3,
        ambulance = 2,
    },
    createCalls = {
        police    = 0,
        ambulance = 0,
    },
    manageUnits = {
        police    = 2,
    },
    mergeCalls = {
        police    = 2,
    },
    placeMarkers = {
        police    = 2,
    },
}

-- Callsign validation Lua pattern (empty string = no validation)
-- Example: '^%u%u%d%d$' requires two uppercase letters + two digits
Config.CallsignRegex = ''

-- Show off-duty units on map
Config.ShowOffDutyOnMap = false

-- Default call expiry in seconds (0 = never)
Config.DefaultCallExpiry = 3600

-- Notification style: 'toast' | 'chat'
Config.NotificationStyle = 'toast'

-- Unit trail breadcrumb settings (stored in memory, reset on restart)
Config.UnitTrailLength = 50
Config.UnitTrailInterval = 3

-- Discord webhook for call notifications (leave empty to disable)
Config.DiscordWebhook = ''
Config.DiscordBotName = 'LG Dispatch'
Config.DiscordBotAvatar = 'https://media.discordapp.net/attachments/1238207587887874118/1384714689350729808/lg_logo_solo.png'
```
