# Configuration

All options live in `shared/config.lua`, spawn points in `shared/spawns.lua` and webhooks in `server/config.lua`.

## General

| Key | Default | Description |
| --- | --- | --- |
| `Config.Debug` | `false` | Console logs |
| `Config.Locale` | `'en'` | `en` or `pl`. Add your own file in `locales/` |
| `Config.ServerName` | `'KOJA ROLEPLAY'` | Shown in the top bar |
| `Config.Logo` | `nil` | Image URL for the top bar |
| `Config.Currency` | `'$'` | Money prefix |
| `Config.ShowMoney` | `true` | Shows cash and bank on the character card |
| `Config.Theme` | see below | UI colours and corners, see [Theme / UI colours](#theme--ui-colours) |

## Theme / UI colours

```lua title="shared/config.lua"
Config.Theme = {
    accent = '#FF9B00',     -- primary colour: buttons, highlights, selected slot, sliders
    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 / online
    danger = '#FF5A4F',     -- negative states: delete, errors, unavailable
    corners = 'rounded',    -- 'rounded' (chamfered) or 'sharp'
}
```

| Key | Default | What it colours |
| --- | --- | --- |
| `accent` | `'#FF9B00'` | The primary colour: buttons, highlights, the selected slot and sliders |
| `background` | `'#121212'` | The base colour of the panels. The UI tints it warm |
| `text` | `'#F1ECE6'` | The main text colour. Muted text and borders follow it |
| `success` | `'#9BE22D'` | Positive states: free, available, online |
| `danger` | `'#FF5A4F'` | Negative states: delete, errors, unavailable |
| `corners` | `'rounded'` | `'rounded'` (chamfered corners) or `'sharp'` |

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. Any `corners` value other than `'sharp'` gives `'rounded'`.

## Slots

```lua title="shared/config.lua"
Config.Slots = {
    Default = 3,
    Max = 8,
    ShowLocked = true,
    ShopUrl = nil,
    ImportEsxSlots = true,
    Tiers = {
        { ace = 'koja.multicharacter.slots.vip', slots = 5 },
        { ace = 'koja.multicharacter.slots.staff', slots = 8 },
    },
    DiscordRoles = {},
}
```

| Key | Description |
| --- | --- |
| `Default` | Slots for every player |
| `Max` | How many slots the UI shows. Extra ones are drawn as locked |
| `ShowLocked` | Show locked slots up to `Max` |
| `ShopUrl` | Link shown on locked slots, for example your Tebex |
| `ImportEsxSlots` | Reads `multicharacter_slots` from the old esx_multicharacter when the player has no own override |
| `Tiers` | `{ ace, slots }`. The highest matching tier wins. Overrides from `/charslots` win over tiers |
| `DiscordRoles` | `['role id'] = slots`. Needs `kmc_discord_token` and `kmc_discord_guild` in `server.cfg`; the bot must be a member of the guild. Roles are cached for 10 minutes |

## Identity

| Key | Default | Description |
| --- | --- | --- |
| `MinNameLength` / `MaxNameLength` | `2` / `16` | Length of first and last name |
| `Capitalize` | `true` | Upper-cases the first letter |
| `MinAge` / `MaxAge` | `18` / `90` | Allowed age |
| `Height`, `MinHeight`, `MaxHeight`, `DefaultHeight` | `true`, `150`, `210`, `178` | Height field in cm |
| `Nationality`, `Nationalities` | `true`, 20 entries | Nationality field. An empty list turns it into free text |
| `Backstory.Enabled / Required / MaxLength` | `true` / `false` / `600` | Backstory field |
| `UniqueNames` | `true` | Rejects a first and last name that already exists |
| `BlacklistedNames` | `admin`, `staff`, ... | Rejected names (first, last or full, case-insensitive) |
| `EsxDateFormat` | `'DD/MM/YYYY'` | Format of `users.dateofbirth` on ESX: `DD/MM/YYYY`, `MM/DD/YYYY` or `YYYY-MM-DD` |

Names allow letters (including accented and Cyrillic), spaces, apostrophes and hyphens.

## Delete, trash, CK and presets

| Key | Default | Description |
| --- | --- | --- |
| `Config.Delete.Enabled` | `true` | Players can delete their characters |
| `Config.Delete.ConfirmName` | `true` | Players must type the full name to delete |
| `Config.Trash.Days` | `7` | Days a deleted character stays restorable. `0` = delete is permanent |
| `Config.Trash.PurgeInterval` | `600` | Seconds between purge runs |
| `Config.CK.AllowDelete` | `true` | Players can delete a dead (memorial) character |
| `Config.CK.ReasonRequired` | `true` | Staff must give a reason for a CK |
| `Config.Presets.Enabled` | `true` | Appearance presets |
| `Config.Presets.Max` | `6` | Presets per account |

## Approval and name changes

| Key | Default | Description |
| --- | --- | --- |
| `Config.Approval.Enabled` | `false` | New characters wait for staff approval before the first spawn |
| `Config.Approval.NotifyStaff` | `true` | Online staff get a notification |
| `Config.NameChange.Enabled` | `true` | Players can request a new name from the character card |
| `Config.NameChange.ReasonRequired` / `MaxReason` | `true` / `300` | Reason field |
| `Config.NameChange.ShowDeniedDays` | `7` | How long a denied request stays visible |

`set kmc_webhook_approval "https://discord.com/api/webhooks/..."` posts new characters waiting for approval.

## Appearance

| Key | Default | Description |
| --- | --- | --- |
| `Resource` | `'auto'` | Tries koja-appearance / illenium-appearance, fivem-appearance, then qb-clothing and esx_skin. Or set a resource name |
| `AllowPedChange` | `false` | New characters can pick any ped model in the editor |
| `ForceSave` | `true` | The editor reopens until the player saves |
| `Menu` | all `true` | Editor sections for new characters: `headBlend`, `faceFeatures`, `headOverlays`, `components`, `props`, `tattoos` |

## Scene

| Key | Default | Description |
| --- | --- | --- |
| `Random` | `true` | Random location each time, otherwise the first one |
| `Lineup` | `false` | All characters stand next to each other and the camera slides between them |
| `LineupSpacing` | `1.15` | Distance between characters in the line-up |
| `Hour`, `Weather` | `19`, `'EXTRASUNNY'` | Default time and weather. A location can override them |
| `Camera` | | `Distance`, `MinDistance`, `MaxDistance`, `Height`, `Side`, `LookHeight`, `Fov`, `Interp` (ms) |
| `Scenarios` | 5 scenarios | Random idle scenarios for the preview characters |
| `Locations` | Vinewood Hills, Michael's House | `{ label, ped = vec4(...), hour?, weather? }`. `ped` is where the character stands, the camera is placed in front of it |
| `AutoFrame` | `true` | Snaps the character to the ground and turns it in 45° steps when a wall blocks the camera or the walk path |

### Opening shot

```lua title="shared/config.lua"
Intro = {
    Enabled = true,
    Duration = 3200,
    Distance = 7.5,
    Side = 2.5,
    Height = 2.2,
},
```

The camera starts wide (`Distance`, `Side` and `Height` are added to the normal pose) and glides in over `Duration` ms.

### Character switch cutscene

```lua title="shared/config.lua"
Transition = {
    Enabled = true,
    WalkDistance = 2.8,
    WalkSpeed = 1.0,
    ExitTime = 2600,
    CameraOut = 700,
    CameraIn = 1900,
    CameraWide = 1.2,
    CameraSwing = 1.1,
    CameraRise = 0.2,
    Gestures = {
        { dict = 'clothingshirt', anim = 'try_shirt_positive_d' },
        { dict = 'clothingtie', anim = 'try_tie_positive_a' },
        { dict = 'clothingspecs', anim = 'try_glasses_positive_a' },
        { dict = 'anim@mp_player_intupperthumbs_up', anim = 'idle_a' },
    },
},
```

| Key | Description |
| --- | --- |
| `Enabled` | `false` swaps characters instantly |
| `WalkDistance`, `WalkSpeed` | How far and how fast the new character walks in |
| `ExitTime` | Ms before the old character is removed |
| `CameraOut`, `CameraIn` | Ms for the camera to swing out and back |
| `CameraWide`, `CameraSwing`, `CameraRise` | How far the camera pulls back, moves sideways and rises during the swing |
| `Gestures` | Animations the new character plays on arrival, one picked at random. A missing animation is skipped |

### Scene per character

```lua title="shared/config.lua"
PerCharacter = {
    Enabled = true,
    Fade = 450,
    Jobs = {
        police = { label = 'Mission Row PD', ped = vec4(428.23, -984.28, 30.71, 3.5), hour = 21 },
        ambulance = { label = 'Pillbox Hospital', ped = vec4(298.6, -584.5, 43.26, 70.0), hour = 18 },
        mechanic = { label = 'Benny\'s', ped = vec4(-205.62, -1310.66, 31.29, 0.0), hour = 17 },
    },
    Gangs = {},
},
```

Single mode only; the line-up keeps one location. A `sceneLocation` hook (houses, motels) wins over `Jobs` and `Gangs`. Characters without a match use `Locations`. `Fade` is the fade time in ms when the scene moves.

### Car in the scene

| Key | Default | Description |
| --- | --- | --- |
| `Vehicle.Enabled` | `true` | Shows one of the character's owned vehicles behind them (`owned_vehicles` on ESX, `player_vehicles` on QB) |
| `Vehicle.Pick` | `'best'` | `'best'` (class, then top speed) or `'first'` |
| `Vehicle.Candidates` | `6` | How many owned vehicles are sent to the client to pick from |
| `Vehicle.Back`, `Vehicle.Side` | `3.4`, `1.3` | Position behind the character in metres. Skipped when the spot is blocked |

### Seasons

| Key | Description |
| --- | --- |
| `Seasons.Enabled` | Seasonal scenes |
| `Seasons.Force` | Season name to force (for testing), `nil` = by date |
| `Seasons.List` | `{ name, from = 'MM-DD', to = 'MM-DD', weather, hour?, snow?, music?, props = { { model, offset = vec3(right, forward, up), heading } } }`. Winter (`12-01` to `01-06`) and Halloween (`10-20` to `11-03`) ship by default |

:::hint{type="info" title="Tight locations"}
If a character clips a wall or railing while walking in, lower `WalkDistance` or move the location. `AutoFrame` already turns the character when the path is blocked.
:::

## Audio

| Key | Default | Description |
| --- | --- | --- |
| `Config.Audio.Enabled` | `true` | All sounds and music |
| `Volume`, `MusicVolume` | `0.6`, `0.22` | 0..1 |
| `Music` | `true` | Default for players who never touched the music toggle |
| `Sounds` | 15 sounds | Sound name to file in `web/build/sounds` (`false` turns one off) |
| `Tracks` | `default`, `winter`, `halloween` | Music per season `music` key |

Players mute sounds and music with the two buttons in the top bar. The choice is saved on their PC.

## Arrival

| Key | Description |
| --- | --- |
| `Config.Arrival.Enabled` | Plays the arrival cutscene on a new character's first spawn at `NewCharacterSpawn` |
| `Mode` | `'plane'` or a name registered with `RegisterArrival` |
| `Skippable` | SPACE / ENTER skips |
| `Plane` | `Model`, flight `Heading`, `Length`, `StartHeight` and `EndHeight` above the spawn, `Duration` ms, `CameraBack`, `CameraHeight` |
| `Walk` | The character walks `Distance` m to the spawn point over `Duration` ms. Camera `CameraDistance` in front, `CameraHeight` up |

## Party

| Key | Default | Description |
| --- | --- | --- |
| `Config.Party.Enabled` | `true` | Party button in the selection |
| `MaxSize` | `6` | Members per party |
| `CodeLength` | `5` | Length of the join code |
| `SpawnDistance` | `1.6` | Distance from the party member when spawning next to them |

## Spawn

| Key | Default | Description |
| --- | --- | --- |
| `Selector` | `true` | Show the spawn selector, otherwise spawn at the last location |
| `UseKojaSpawnSelector` | `true` | When koja-spawnselector is started, use its map instead of the built-in screen |
| `NewCharacterSelector` | `false` | New characters also pick a spawn, otherwise they go to `NewCharacterSpawn` |
| `NewCharacterSpawn` | airport | Where new characters arrive |
| `LastLocation` | `true` | Offer the last location |
| `AllowBack` | `true` | Back button from the spawn selector to the characters |
| `TriggerPlayerSpawned` | `false` | Also fires the classic `playerSpawned` event |
| `Camera` | | Sky camera `Height`, `Back`, `Interp` and `Fov` |
| `Protection` | 10 s | `Enabled`, `Duration` (s), `Ghost`, `Invincible`, `DisableFiring`, `Alpha` |

Spawn points in `shared/spawns.lua` use the same fields as the [`RegisterSpawn`](/new/multicharacter/developer-api) export.

## Relog

| Key | Default | Description |
| --- | --- | --- |
| `Enabled`, `Command` | `true`, `'relog'` | The switch character command |
| `Cooldown` | `60` | Seconds after spawning before switching is allowed |
| `CombatSeconds` | `30` | Blocks switching for this long after damage was dealt or taken |
| `BlockWhenDead`, `BlockInVehicle` | `true`, `true` | Extra checks |
| `Progress` | `3000` | Progress bar length in ms. `0` is instant |

## Other

| Key | Default | Description |
| --- | --- | --- |
| `Config.Bucket.Enabled / Offset` | `true` / `5000` | Private routing bucket (`Offset + server id`) while choosing |
| `Config.Playtime.SaveInterval` | `300` | Seconds between playtime saves |
| `Config.StarterItems` | phone, water, bread | Items given to a new character on first spawn |
| `Config.Esx.Prefix` | `'char'` | ESX character prefix, `char1:license` |
| `Config.Esx.AutoResizeColumns` | `true` | Widens `identifier` / `owner` columns so the prefix fits |
| `Config.Admin` | | Groups, ACE and command names, see [Commands](/new/multicharacter/commands) |
