# 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` | accent `#FF9B00`, background `#121212`, corners `rounded` | UI colours. `corners` is `rounded` or `sharp` |

## 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) |
