# Configuration

Everything lives in `shared/config.lua`. Restart the resource after a change.

## General

| Option | Default | What it does |
| --- | --- | --- |
| `Config.Locale` | `'pl'` | Language, a file name from `shared/locales/` |
| `Config.Debug` | `false` | Prints selections, combat logs and failing checks to the console |
| `Config.ServerName` | `'KOJA ROLEPLAY'` | Shown in the panel header |
| `Config.Theme` | see below | UI colours, see [Theme / UI colours](#theme--ui-colours) |
| `Config.Fallback` | Legion Square | Where a player spawns when no location is available |

## Theme / UI colours

```lua title="shared/config.lua"
Config.Theme = {
    accent = '#FF9B00',     -- primary colour: buttons, selected spawn, map pins and route
    background = '#121212', -- base colour of the panels (tinted warm by the UI)
    text = '#F1ECE6',       -- main text colour (muted text and borders follow it)
    success = '#9BE22D',    -- positive states: free / available
    danger = '#FF5A4F',     -- negative states: errors, locked, live view
}
```

| Key | Default | What it colours |
| --- | --- | --- |
| `accent` | `'#FF9B00'` | The primary colour: buttons, the selected spawn, the map pins and the route |
| `background` | `'#121212'` | The base colour of the panels. The UI tints it warm, like Koja Appearance |
| `text` | `'#F1ECE6'` | The main text colour. Muted text and borders follow it |
| `success` | `'#9BE22D'` | Positive states: free, available |
| `danger` | `'#FF5A4F'` | Negative states: errors, locked spots, the live view |

Colours are hex only, `#RGB` or `#RRGGBB`. They are applied as CSS variables when the UI opens, so you do not need to rebuild the UI. Restart the resource after changing them.
An invalid colour is ignored and the default from the table is used instead.

:::hint{type="info" title="The 3D map keeps its own colours"}
The terrain, buildings and sky of the 3D map are not themed. Only the map pins and the route follow `accent`.
:::

## Opening

```lua title="shared/config.lua"
Config.Open = {
    OnPlayerLoaded = true,
    Delay = 250,
    CanCancel = false,
    ShowLocked = true,
    RememberChoice = true,
}
Config.QbSpawnCompat = true
```

| Option | What it does |
| --- | --- |
| `OnPlayerLoaded` | ESX: open on `esx:playerLoaded`. Skipped while Koja Multicharacter runs |
| `Delay` | ms to wait after `esx:playerLoaded` before opening |
| `CanCancel` | Default for ESC / the exit button. Every `Open` call can override it with `canCancel`. Never allowed while a restriction is active |
| `ShowLocked` | `true` shows locked spots with their lock label, `false` hides them |
| `RememberChoice` | Preselect the location the player picked last time |
| `Config.QbSpawnCompat` | Listen to the `qb-spawn` events from qb-multicharacter |

## Admin

```lua title="shared/config.lua"
Config.Command = { Enabled = true, Name = 'spawnselector', Editor = 'spawneditor', Event = 'spawnevent' }
Config.Admin = { Groups = { 'admin', 'superadmin', 'god' }, Ace = 'command' }
```

The check goes through `KOJA.Server.IsAdmin`; change it in `ServerEditable.IsAdmin`. See [Admin tools](/new/spawn-selector/admin-tools).

## Last location

```lua title="shared/config.lua"
Config.LastLocation = {
    Enabled = true,
    SaveInterval = 60,
    FrameworkFallback = true,
    IgnoreBuckets = true,
}
```

| Option | What it does |
| --- | --- |
| `Enabled` | Show "Last location" and save positions |
| `SaveInterval` | Seconds between saves of every spawned player (minimum 10). Positions are also saved on logout, disconnect and resource stop |
| `FrameworkFallback` | No saved row yet: use `users.position` (ESX) or `players.position` (QBCore) |
| `IgnoreBuckets` | Do not save players in a routing bucket other than 0 (interiors, instances) |

## Anti combat log, death and jail

`Config.AntiCombatLog`, `Config.Death` and `Config.Jail` are explained with the API on [Anti combat log & restrictions](/new/spawn-selector/anti-combat-log).

## New characters

```lua title="shared/config.lua"
Config.NewCharacter = {
    UseSelector = false,
    Spawns = {
        { id = 'start_airport', label = 'Los Santos International', description = 'Arrivals', category = 'start', coords = vec4(-1037.74, -2737.89, 20.17, 327.9) },
    },
}
```

`UseSelector = false` leaves new ESX characters to your character creator. When a new character opens the selector (`isNew = true`), only these spawns are listed.

## Camera flight

```lua title="shared/config.lua"
Config.Cinematic = {
    Enabled = true,
    Height = 650.0,
    TopFov = 45.0,
    HoldTime = 900,
    DescendTime = 3200,
    ApproachDistance = 22.0,
    ApproachHeight = 14.0,
    SettleTime = 1800,
    FinalDistance = 3.4,
    FinalHeight = 0.85,
    BlendOut = 1100,
    LoadTimeout = 6000,
}
```

| Option | What it does |
| --- | --- |
| `Enabled` | `false` = a plain fade and teleport |
| `Height`, `TopFov`, `HoldTime` | The top-down shot: metres above the spot, field of view, how long it holds (ms) |
| `DescendTime` | ms of the descent to the street |
| `ApproachDistance`, `ApproachHeight` | Where the descent ends: metres behind and above the ped |
| `SettleTime`, `FinalDistance`, `FinalHeight` | The last move to an over-the-shoulder shot |
| `BlendOut` | ms of the blend into the gameplay camera |
| `LoadTimeout` | Longest wait (ms) for collision and streaming at the spot |

## Map

```lua title="shared/config.lua"
Config.Map = {
    FocusDistance = 2600,
    OnlineDetail = true,
    ShowWorld = true,
    BlipImageUrl = 'https://docs.fivem.net/blips/%s.png',
    GameBlips = {
        Enabled = true,
        Sprites = { [60] = 'radar_police_station', [61] = 'radar_hospital' },
    },
}
```

| Option | What it does |
| --- | --- |
| `FocusDistance` | Camera distance in metres when a location is selected |
| `OnlineDetail` | Up close, stream the sharper zoom 7 tiles from the Rockstar map CDN. Offline the shipped zoom 6 tiles are used |
| `ShowWorld` | Time of day, weather, rain and snow on the map |
| `BlipImageUrl` | Where blip icons come from, `%s` = the blip name |
| `GameBlips` | Game blips shown on the map: sprite id = blip image name |

```lua title="shared/config.lua"
Config.Roads = { enabled = true, file = 'data/roads.json', cellSize = 150.0, maxSnapRings = 12, zWeight = 0.5, speedFactor = { [0] = 0.6, 0.9, 1.15, 1.45 }, maxExpansions = 20000, cacheSize = 64 }
```

The road graph for the route line. `enabled = false` turns the route off; the other values are the koja-dispatch router settings.

## Live preview

```lua title="shared/config.lua"
Config.LivePreview = { Enabled = true, Distance = 9.0, Height = 3.2, Fov = 45.0, Orbit = 6.0 }
```

The camera circling the selected spot in the card: metres behind, metres up, field of view and degrees per second.

## Sounds

```lua title="shared/config.lua"
Config.Sound = { Enabled = true, Volume = 0.45, Ambient = true, Music = nil }
```

`Music` takes any URL the NUI can play (`https://...` or `nui://your-resource/music.mp3`) and replaces the synthesised ambient.

## Housing

```lua title="shared/config.lua"
Config.Housing = {
    ['qb-houses'] = 'auto',
    ['qs-housing'] = 'auto',
    ['ps-housing'] = 'auto',
    ['esx_property'] = 'auto',
    ['nolag_properties'] = 'auto',
    ['bcs_housing'] = 'auto',
}
```

`'auto'` lists owned properties when that resource is started, `true` always tries, `false` turns the provider off.

## Stats

```lua title="shared/config.lua"
Config.Stats = { Enabled = true, KeepDays = 90 }
```

## Categories

```lua title="shared/config.lua"
Config.Categories = {
    { id = 'event', icon = 'star', order = 0 },
    { id = 'last', icon = 'footprints', order = 1 },
    { id = 'start', icon = 'plane', order = 2 },
    { id = 'property', icon = 'home', order = 3 },
    { id = 'job', icon = 'briefcase', order = 4 },
    { id = 'gang', icon = 'skull', order = 5 },
    { id = 'city', icon = 'map-pin', order = 6 },
}
```

The label comes from the locale key `category_<id>`, or set `label` yourself. Other resources can add categories with [`RegisterCategory`](/new/spawn-selector/developer-api).

Icons: `anchor`, `briefcase`, `building`, `car`, `footprints`, `home`, `hospital`, `lock`, `map-pin`, `mountain`, `plane`, `shield`, `skull`, `star`, `tent`, `trees`, `waves`. Unknown names show a map pin.

## Spawns

`Config.Spawns` is the list everyone can see. A spawn is a table:

```lua title="Spawn definition"
{
    id = 'mrpd',
    label = 'Mission Row',
    description = 'Police Department',
    category = 'job',
    coords = vec4(428.23, -984.28, 29.76, 3.5),
    icon = 'shield',
    blip = 'radar_police_station',
    image = 'https://example.com/mrpd.webp',
    jobs = { police = 0, sheriff = 2 },
    onDuty = false,
    gangs = { ballas = 0 },
    ace = 'group.vip',
    lockedLabel = 'VIP only',
    hideLocked = false,
    vehicle = { model = 'police', plate = 'LSPD01', fuel = 100 },
    cinematic = true,
    order = 0,
    data = {},
}
```

| Field | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique string or number. `last` is reserved for the last location |
| `label` | Yes | Name in the list and on the map |
| `coords` | Yes | `vec4`, `vec3` or `{ x, y, z, w }` (also `h` / `heading`) |
| `description` | No | Second line |
| `category` | No | Category id, default `city` |
| `icon` | No | Overrides the category icon |
| `blip` | No | GTA blip image name, used instead of the icon |
| `image` | No | Picture for the card. Without one, the card shows the live preview or the map. Photos taken in the [editor](/new/spawn-selector/admin-tools) are used automatically |
| `jobs` | No | `'police'`, `{ 'police', 'sheriff' }` or `{ police = minGrade }` |
| `onDuty` | No | The job must be on duty |
| `gangs` | No | Same format as `jobs`, QBCore gangs |
| `ace` | No | ACE permission the player needs |
| `lockedLabel` | No | Text when locked. Default: "Police only" from the job label |
| `hideLocked` | No | Hide instead of showing it locked |
| `vehicle` | No | Model name, or `{ model, plate, props, fuel, keys }`: spawn in the driver seat |
| `cinematic` | No | Turns the camera flight on or off for this spot |
| `order` | No | Order inside its category |
| `data` | No | Anything. You get it back in `onSpawn` and the events |

## Editable files

**`client/editable.lua`**

| Function | Called when |
| --- | --- |
| `ClientEditable.Notify(desc, type)` | The selector shows a message |
| `ClientEditable.OnOpen(options)` | The map opens. Hide your HUD here |
| `ClientEditable.OnClose(spawn, options)` | The map closes (`spawn` is `nil` when cancelled). Show your HUD again |
| `ClientEditable.OnSpawned(spawn, options)` | The player landed |
| `ClientEditable.OnVehicleSpawned(vehicle, data)` | A spawn vehicle was created |
| `ClientEditable.CanOpen(options)` | Return `false` to block opening |

**`server/editable.lua`**

| Function | What it does |
| --- | --- |
| `ServerEditable.GetJob(source)` | Job used for locks: `{ name, label, grade, onDuty }` |
| `ServerEditable.GetGang(source)` | Gang used for locks (QBCore by default) |
| `ServerEditable.CanAccess(source, spawn, job)` | Return `false, 'Reason'` to lock a spot |
| `ServerEditable.GetSpawns(source, identifier)` | Return extra spawns for this player |
| `ServerEditable.IsAdmin(source)` | Who can use the admin commands and tabs |
| `ServerEditable.IsDead(source)` | Dead right now (QB metadata by default) |
| `ServerEditable.IsDeadOnLogin(source, identifier)` | Dead when the selector opens (QB metadata, ESX `users.is_dead`) |
| `ServerEditable.IsJailed(source, identifier)` | In jail (QB `injail`, ESX `users.jail_time` / `users.jail`) |
| `ServerEditable.OnCombatLog(identifier, record)` | A player left during combat |
| `ServerEditable.OnSpawned(source, spawn, isNew)` | The player landed |
