Getting started and guides: what holds for any language - the server, a game mode and a client script, setting up, every server.toml key, the docs for AI assistants; exporting the game's tables, terrain, navigation mesh and collision geometry, putting a server on the public list, a database for the game mode, cart routes, building on the server's map with the in-game editor, players' own carts, a character creation screen and the interface
# Getting started
> What a KCD:MP server, a game mode and a client script are, which languages write them, and the order to read this documentation in.
A KCD:MP **server** is one program that the players' games connect to. It runs one **game mode** - the rules of that server:
where players spawn, what the chat and the commands do, who may hurt whom, what stands in the world. A game mode is a script
or a plugin you write; the server calls it when something happens (a player joins, dies, types a command) and the script calls
the server back (send a message, move a player, spawn a horse). A mode may also have a **client script**: its own half that
runs inside each player's game, for the things only the player's screen can show.
| Piece | What it is | Written in |
|---|---|---|
| **The server** | the dedicated server program, a folder you run on Windows or Linux: `server.toml`, the game modes, the data it keeps | - |
| **A game mode** | the rules of one server; one per server, reloaded without a restart | [Lua](/lua/server/) or [C#](/csharp/) |
| **A client script** | the mode's half on the player's game: a marker, a sound, a bit of UI, a value read from the game; sent to the players by the server | [Lua](/lua/client/) |
| **A web interface** | the mode's pages drawn over the game: menus, shops, a login screen, its own chat, scoreboard or loading screen; sent to the players by the server with the client scripts | HTML, CSS and [JavaScript](/js/web/) ([the guide](/guides/web-interface/)) |
| **The world data** | the level's tables, terrain, navigation mesh and collision geometry, exported from your own copy of the game | - |
The server works out of the box: the modes that ship with it - `freeroam.lua`, `duel_arena.lua` and `basicrp.lua` (role-play
on a database of your own) - are complete servers,
and a mode that defines nothing is a valid mode - the server spawns everyone at its default point and answers its own chat
commands. Everything on these pages is what you add.
## The pages, in order
1. [Setting up](/getting-started/setting-up/) - the server folder, the first run, joining it, picking a mode, giving the server the game's
data, your editor. Step by step, whatever language you script in.
2. [AI assistants](/getting-started/ai-assistants/) - this documentation as plain text and as language-server definitions, and a
`CLAUDE.md` that starts a game-mode project with a coding agent.
3. [Server configuration](/getting-started/server-config/) - every key of `server.toml`, with its default.
4. [Guides](/guides/) - one job per page: exporting the game's tables, the terrain, the navigation mesh and the
collision geometry from your game; putting a server on the public list; connecting a database; cart routes and players' own
carts; a character creation screen; knocking players out and carrying them; picking pockets; throwing riders and pulling them
from the saddle; keeping what a player discovered, and a character's stats, skills and perks; changing the interface and
making a web interface of your own.
5. The API for your language: [Lua](/lua/) (the server and the client side, every callback and function on its
own page) or [C#](/csharp/) (the same server API as typed interfaces); for a web interface's pages, the
[web interface SDK](/js/web/) (the `KcdMp` object every page gets).
6. [The reference](/reference/) - the game's lists: every item, soul, buff, perk, mesh, preset, animation clip, door and
container, and the stats and skills, by the keys the functions take; and the built-in chat commands.
## Conventions
The same everywhere, whatever the language:
- **metres** in the level's world space, as the server's `/pos` command reports positions; `z` below `0` when spawning or
teleporting means "on the terrain";
- **degrees** for a yaw, `0` = facing +Y, counter-clockwise;
- **`0xRRGGBBAA`** colors - `0xFF0000FF` is opaque red;
- **milliseconds** for times and timers; the world clock in hours (`13.5` = 13:30);
- a **player** is a `pid` (`0` to the slot count minus one, reused after a disconnect); a **world entity** - a horse, a
pickup, a prop, an NPC actor, a dog - is an `id`, unique while it lives;
- the **keys of game things** - items, souls, buffs, meshes - are an id, a name or a GUID from [the reference](/reference/);
a call given an unknown key fails quietly (`nil`, `false`) and the server log says why.
# Setting up
> Step by step from the server folder to a running server with your own game mode - the first run, joining it, picking and writing a mode, the game's data, admins, and an editor that knows the API.
Everything on this page holds whatever language you script in; the language pages start where this one ends.
## 1. What you need
- **The game** - Kingdom Come: Deliverance II on Steam, from the Microsoft Store (the Xbox app, PC Game Pass) or from the Epic Games Store, the build
the current KCD:MP release was made for (the release notes say which). The server itself does not need the game, but the [world data](#5-give-the-server-the-games-data)
it wants is exported from a copy of it.
- **The KCD:MP client** - the launcher and the client, from the download page at [kcd-mp.com](https://kcd-mp.com/download),
on every machine that plays. The launcher starts the game with KCD:MP inside it and connects it to a server. Unpack
it anywhere once; from then on the launcher tells you when a newer release is out and updates itself on your say-so.
- **The server folder** - the dedicated server, from the same [download page](https://kcd-mp.com/download) under
*Host your own*: one archive with a `windows/` and a `linux/` folder inside; unpack it and take the folder for your
system. It is a .NET 10 program: the machine that runs it installs the .NET 10 runtime once. A server needs one UDP
port (7777 by default).
## 2. The server folder
The folder is the same on both systems:
```
KcdMp.Server (.exe) the server
server.toml.example every setting, every key documented; the first start copies it to server.toml, the file you edit
gamemodes/ the game modes: freeroam.lua, basicrp.lua, duel_arena.lua, the marker example (a mode with a client half) and a README
sdk/ what your editor and a C# project use: KcdMp.Api.dll + .xml, KcdMp.d.lua, KcdMp-client.d.lua, KcdMp-web.d.ts -
and web/: example web interfaces and a React + TypeScript starter
data/ what the server keeps (the world's doors, chests and bans) and reads (the game's tables, the world data) - with a README
tools/ Windows only: the exporters that make the game's data from your own copy of the game
```
`server.toml` is read from the working directory (or `--config `); every key is optional and
[Server configuration](/getting-started/server-config/) lists them all with their defaults. A command-line flag overrides the file
(`--max-players 64` beats `[server] max_players`).
The settings ship as `server.toml.example`. The first start copies it to `server.toml` (you can also copy it yourself
before that), and `server.toml` is the file to edit: a new version unpacked over the folder replaces only the example,
so your settings survive every update. After an update, compare the two files to see the keys that are new.
## 3. The first run
Run the server in its folder - `KcdMp.Server.exe` on Windows, `./KcdMp.Server` on Linux - and read its first lines: the
version and the protocol number, the tick rate, which game mode it loaded, and one line per data file it found or could not
use (`combat tables: ...`, `heightmap: ...`, `navmesh: ...`, `collision: ...`). A missing file turns one thing off and
nothing else; the server runs without any of them. `Ctrl+C` stops it; `--version` prints the version and exits. A server
that gets stuck - unable to send or receive for 30 seconds - ends itself with exit code `3`
([`[server] stall_exit`](/getting-started/server-config/#server)), so have whatever starts it start it again when it stops.
Two settings are worth a look before anyone else joins:
```toml
[server]
name = "KCD:MP dev server" # what the players see in the launcher
password = "" # "" = anyone may join
[master]
url = "https://kcd-mp.com/server-list" # the public list; "" = announce nowhere, a private server
```
A server announces itself to the public list by default, so a test server is best made private (`url = ""`) or
passworded until it is meant to be found. [Putting a server on the list](/guides/public-server/) has the rest -
the port, the address the list should show, what players see.
## 4. Join it
Open the launcher (with Steam running for the Steam copy of the game), type the name the other players will see, and connect: **Servers** lists the
public list, **Direct connect** takes an address - `127.0.0.1:7777` for a server on the same machine, the machine's
address on a LAN. The launcher asks the server for its level and boots the game onto it; the first load takes a little
longer than usual. In the game, **Enter** opens the chat and **Page Up** / **Page Down** scroll back through it (the
launcher's Options change those two keys), **Tab** the scoreboard, **Esc** the menu, **G** the emote wheel (hold **T** to
point); `/help` in the chat lists the commands the server answers.
## 5. Give the server the game's data
The server does not have the game. Four files, each made once from your own copy of it with the tools in `tools/`
(Windows - the machine the game is installed on) and put under `data/`, give it what it lacks; a server on Linux gets
the files copied over. Each is one job, one guide:
| File | What it turns on | Guide |
|---|---|---|
| `data/combat/items.json` | damage from the game's own weapon and armour tables; items, souls, buffs and meshes by **name** (`/give shortswordBroad`, a horse breed by name, a potion the server owns); the reference lists as the server resolves them; what the audit tells the items apart by - without it the item check is passive | [Exporting the game's tables](/guides/game-tables/) |
| `data/heightmaps/.hmap` | the terrain check - a player far under or over the ground is pulled back - and the terrain height for a game mode | [Exporting the terrain](/guides/terrain/) |
| `data/navmesh/.knav` | NPC actors that walk around walls instead of into them; paths and reachability for a game mode | [Exporting the navigation mesh](/guides/navigation/) |
| `data/collision/.kcol` | line of sight on hits and shots, the floor under a player as the ground, rays for a game mode | [Exporting the collision geometry](/guides/collision/) |
Do the tables first: without them the server knows items, souls and buffs by their GUIDs only, takes the damage the
players' games report and cannot judge what players carry (the item check follows what their games say and removes
nothing). Export them again after every update of the server. The other three are per level and can wait until the server
needs them.
## 6. Pick a game mode
`[gamemode] script` names it; the command line (`--gamemode gamemodes/duel_arena.lua`) wins over the file:
```toml
[gamemode]
script = "gamemodes/freeroam.lua" # everyone in one world around the spawn, global chat, parties, the NPC actor demo
# script = "gamemodes/basicrp.lua" # role-play on your own database: accounts, characters kept, local chat - needs [gamemode.database]
# script = "gamemodes/duel_arena.lua" # /duel queues you; two at a time fight in the arena, nobody else can be hurt
# script = "gamemodes/marker/marker.lua" # the example with a client half: a checkpoint run drawn in each player's game
# script = "gamemodes/arena/Arena.dll" # a C# plugin, in a folder of its own
# script = "" # the built-in freeroam
watch = false # true: a changed script is reloaded by itself (a development server)
```
A mode is reloaded without a restart by `/reload` in the chat (admins), by the mode itself, or by `watch = true` whenever
its file changes on disk.
## 7. Write your own
- **In Lua**: a file next to the shipped modes, named in `server.toml`. [Getting started on the server](/lua/server/getting-started/)
is the first script, the tick, ids and units and what happens on an error; the [Server API index](/lua/server/)
has every callback and function. A mode that wants something on the players' screens adds a
[client script](/lua/client/getting-started/) in a `client/` folder next to itself.
- **In C#**: a class library compiled against `sdk/KcdMp.Api.dll`, published into a folder of its own under `gamemodes/`.
[The C# server API](/csharp/server/) has the project file, the loading, a database behind a plugin and every
interface. A C# mode's client half is a Lua client script all the same.
Whichever it is, the mode runs on the server's simulation thread, one tick at a time, 30 times a second: a slow callback
stalls every player, and the server log says so when it happens.
## 8. Admins and accounts
The server's own commands - `/give`, `/tp`, `/horse`, `/time`, `/weather`, `/kick`, `/reload`, `/objectinfo` and the rest -
answer **admins** only, and admins are the game mode's to name: the server keeps no accounts and no admin list, and remembers
nothing about a player between visits. The shipped freeroam mode promotes the names of the `KCDMP_ADMINS` environment
variable, so on the machine that runs the server:
```
KCDMP_ADMINS=Henry,Hans
```
A mode of your own promotes whoever it likes with [`SetPlayerAdmin`](/lua/server/functions/setplayeradmin/) - from a list in
the script, or from its own accounts. Accounts (a `/register` and a `/login`, a player put back where they left, factions,
scores) are what a mode builds on a **database** of its own once you connect one: `[gamemode.database]` in `server.toml`,
MySQL / MariaDB or PostgreSQL, the [databases guide](/guides/databases/). Without one the freeroam's `/register` says
this server keeps no accounts, and everything else works as before. [Chat commands](/reference/chat-commands/) lists every
built-in and who may use it.
## 9. Your editor
The server folder carries the whole API in the two forms editors read, so completion, documentation on hover and a warning
for a wrong name or argument are there before the server ever runs a line:
- **Lua** - `sdk/KcdMp.d.lua` (the game mode API) and `sdk/KcdMp-client.d.lua` (the client script API) are definitions for
the **Lua language server** (LuaLS). In VS Code install the "Lua" extension (sumneko) and open the `gamemodes` folder: its
`.luarc.json` already points the language server at `../sdk`. Any other editor that runs the Lua language server takes
the same file. For a mode in a folder of its own, put a `.luarc.json` next to the script:
```json
{ "runtime.version": "Lua 5.4", "workspace.library": ["../../sdk"], "workspace.checkThirdParty": false }
```
or copy the `.d.lua` file into the folder and point `workspace.library` at `"."`. `lua-language-server --check .` in
that folder is the same check from a terminal: an unknown name or a wrong argument type is a diagnostic there and a silent
`nil` on the server. The files are also downloadable from this site:
[KcdMp.d.lua](https://docs.kcd-mp.com/KcdMp.d.lua), [KcdMp-client.d.lua](https://docs.kcd-mp.com/KcdMp-client.d.lua).
- **C#** - `sdk/KcdMp.Api.dll` with `KcdMp.Api.xml` next to it: a project that references the DLL gets IntelliSense with
every member's description in Visual Studio, Rider or VS Code with the C# extension. The project file is on
[the C# page](/csharp/server/#the-project).
The same files are what a coding assistant reads: [AI assistants](/getting-started/ai-assistants/) says how to give one the whole
reference.
# AI assistants
> This documentation as plain text for AI assistants - llms.txt, one file per API and per reference list, the Markdown behind every page, the API as language-server definitions and TypeScript types - and a CLAUDE.md that starts a game-mode project with Claude Code or another coding agent.
Every page here is written once, as Markdown, and published twice: as the page you are reading and as plain text an
assistant can read whole. Point a coding agent at the files below and it works from the same reference you do - the real
syntax of every function, the real keys of every item - instead of what it remembers of some other game's API.
## The files
- [`/llms.txt`](https://docs.kcd-mp.com/llms.txt) - the index: what KCD:MP is, how to read the rest, links to every file
below. A few KB.
- [`/llms-small.txt`](https://docs.kcd-mp.com/llms-small.txt) - everything a game mode is written from, in one file:
these getting-started pages, the guides, every server callback and function, the client API, the web interface SDK, the
map of the reference lists. About 1.9 MB, ~475k tokens - more than most assistants hold at once: give it whole to one
with a very large context, or keep a copy an agent searches. An agent working in a project does better with the
definitions below and the pages as Markdown, one at a time ([the CLAUDE.md](#a-game-mode-with-claude-code) says how).
- [`/_llms-txt/getting-started-and-guides.txt`](https://docs.kcd-mp.com/_llms-txt/getting-started-and-guides.txt) - these
getting-started pages and the guides, for any language. About 300 KB.
- [`/_llms-txt/lua-server-api.txt`](https://docs.kcd-mp.com/_llms-txt/lua-server-api.txt) - the Lua server API alone:
the Lua guides, every callback and function. About 1.4 MB.
- [`/_llms-txt/lua-client-api.txt`](https://docs.kcd-mp.com/_llms-txt/lua-client-api.txt) - the client API alone. About 140 KB.
- [`/_llms-txt/web-interface-sdk.txt`](https://docs.kcd-mp.com/_llms-txt/web-interface-sdk.txt) - the JavaScript a web
interface's pages call: every function, handler and property of the `KcdMp` object. About 90 KB.
- [`/_llms-txt/reference-overview.txt`](https://docs.kcd-mp.com/_llms-txt/reference-overview.txt) - the map of the reference
lists, the weather presets, the built-in chat commands, stats and skills. About 35 KB.
- `/_llms-txt/reference-.txt` - one file per reference list, 60 KB to 2.7 MB each:
[`items`](https://docs.kcd-mp.com/_llms-txt/reference-items.txt), [`souls`](https://docs.kcd-mp.com/_llms-txt/reference-souls.txt),
[`looks`](https://docs.kcd-mp.com/_llms-txt/reference-looks.txt),
[`buffs`](https://docs.kcd-mp.com/_llms-txt/reference-buffs.txt), [`meshes`](https://docs.kcd-mp.com/_llms-txt/reference-meshes.txt),
[`clothing-presets`](https://docs.kcd-mp.com/_llms-txt/reference-clothing-presets.txt),
[`animations`](https://docs.kcd-mp.com/_llms-txt/reference-animations.txt),
[`particle-effects`](https://docs.kcd-mp.com/_llms-txt/reference-particle-effects.txt),
[`audio-triggers`](https://docs.kcd-mp.com/_llms-txt/reference-audio-triggers.txt),
[`levels`](https://docs.kcd-mp.com/_llms-txt/reference-levels.txt),
[`voice-lines`](https://docs.kcd-mp.com/_llms-txt/reference-voice-lines.txt). The shorter lists - consumables,
weapon presets, perks, decal materials, activities - are single pages: fetch them as Markdown (below).
- [`/_llms-txt/c-plugins.txt`](https://docs.kcd-mp.com/_llms-txt/c-plugins.txt) - the C# tier. About 120 KB.
- [`/llms-full.txt`](https://docs.kcd-mp.com/llms-full.txt) - everything, the reference lists included. About 11 MB -
more than a context window holds; use the sets.
- [`/KcdMp.d.lua`](https://docs.kcd-mp.com/KcdMp.d.lua) and [`/KcdMp-client.d.lua`](https://docs.kcd-mp.com/KcdMp-client.d.lua) -
**the API as Lua language server definitions**: every function, callback and constant with its types and documentation,
in LuaLS `---@param` / `---@return` form. About 700 KB and 60 KB.
- [`/KcdMp-web.d.ts`](https://docs.kcd-mp.com/KcdMp-web.d.ts) - **the web interface SDK as TypeScript types**: the
`KcdMp` object a web interface's pages call, with its documentation, for an editor's completion and type checks in
plain JavaScript or TypeScript. About 30 KB.
The three definition files ship in the server folder's `sdk/` too ([Setting up](/getting-started/setting-up/#9-your-editor)).
**Every page has a Markdown twin.** Replace the trailing `/` of a page's address with `.md`:
`/lua/server/functions/setspawninfo/` is also `/lua/server/functions/setspawninfo.md` - the source of the page, without
the navigation, in one request. Names are lowercase in addresses (`SetSpawnInfo` → `setspawninfo`). An agent that looks
pages up as it works should fetch these, not the HTML.
The files follow [llmstxt.org](https://llmstxt.org/) and are built from the same source as the site on every publish, so
they cannot fall behind it. Nothing here is behind a login or a rate limit; a plain `curl` gets any of them.
## A game mode with Claude Code
A game mode is one Lua file next to the server, named in its `server.toml` ([Setting up](/getting-started/setting-up/)). Two files in
the folder the mode lives in - the server's `gamemodes/` folder or a repository of its own - turn a coding agent from a
guesser into a colleague who has read the manual:
**The definitions.** The server's `gamemodes/` folder already carries a `.luarc.json` that points the Lua language
server at `sdk/KcdMp.d.lua`. For a mode in a folder of its own, copy the file (or download it, above) next to the script
and add:
```json
{ "runtime.version": "Lua 5.4", "workspace.library": ["."], "workspace.checkThirdParty": false }
```
An editor with the "Lua" extension (sumneko / LuaLS) then completes every name and flags one that does not exist; an
agent working in the folder reads the same file and can run the same check.
**The instructions.** Save this as `CLAUDE.md` - Claude Code reads it at the start of every session; the same file as
`AGENTS.md` serves Codex, Cursor and the others. It tells the agent where the API is, what it must not invent, and the
handful of rules a mode lives by.
```md
# - a KCD:MP game mode
This is a game mode for KCD:MP (KCD Multiplayer), the multiplayer platform for Kingdom Come: Deliverance II:
one Lua 5.4 script the dedicated server runs. The server calls the script's callbacks (`OnPlayerConnect`,
`OnPlayerDeath`, `OnTick` ...) and the script calls the server's functions (`SendClientMessage`, `SetSpawnInfo`,
`CreateActor` ...).
## The API is documented, never guessed
- `KcdMp.d.lua` in this folder is the whole game mode API - every function, callback and constant with its types
and documentation, in Lua language server form. Look every name up there before using it; it is large, so search
it rather than reading it whole. If it is missing, fetch https://docs.kcd-mp.com/KcdMp.d.lua.
- Before the first change of a session read https://docs.kcd-mp.com/lua/server/getting-started.md - how the server
runs a script, the tick, ids and units, errors. The index by topic, https://docs.kcd-mp.com/lua/server.md, lists
every callback and function in a line each: the way to find the function for a job.
- For a job a guide covers - building on the map, a web interface, carts, saving progress, the interface - read the
guide as Markdown; https://docs.kcd-mp.com/guides.md lists them.
- For one function or callback fetch its page as Markdown: https://docs.kcd-mp.com/lua/server/functions/.md
or https://docs.kcd-mp.com/lua/server/callbacks/.md, the name in lowercase (`SetSpawnInfo` → `setspawninfo.md`).
- Only the functions and callbacks the documentation lists exist. There is no `Player` object, no event emitter,
no `require("KcdMp")`, no API from any other game. If a function is not on https://docs.kcd-mp.com/lua/server/
it does not exist - say so rather than inventing one.
- Keys of game things - items, souls, looks, buffs, meshes, outfits, animation clips, sounds, doors, voice lines -
come from the reference lists (https://docs.kcd-mp.com/reference.md; one file per list at
https://docs.kcd-mp.com/_llms-txt/reference-.txt - they are big, fetch the one you need). Never make a
key up. Keep the name or the GUID in the script, not the numeric id: ids shift with game patches.
- A web interface - the mode's own pages drawn over the game - is HTML and JavaScript calling the `KcdMp` object:
the guide is https://docs.kcd-mp.com/guides/web-interface.md, the types https://docs.kcd-mp.com/KcdMp-web.d.ts.
## How the server runs the script
- Plain Lua 5.4 with the standard library; one file, or a folder of files joined by `require` (relative to the
script). The API is globals the server registers before the script runs; the callbacks are globals the
script defines.
- Everything runs on the simulation thread, one tick at a time, 30 times a second. No threads, no locks, no
races - and a slow callback stalls every player. Heavy work goes on a timer (`SetTimer`), not in `OnTick`.
- `pid` is a player, `0` to `GetMaxPlayers() - 1`, reused after a disconnect: keep the pid while a player is on,
check `IsPlayerConnected(pid)` before trusting one kept across ticks. `id` is a world entity (a horse, a
prop, an NPC actor), `nil` from every getter once it is gone.
- Positions are metres in the level's world space (`z < 0` = on the terrain), yaw is degrees, colors are
`0xRRGGBBAA` (the constants name the usual ones), times are milliseconds, the world clock is hours.
- A bad key or id never raises: the call returns `nil` or `false` and the server log says why. Check returns.
- A runtime error inside a callback is logged and that callback's effect is skipped; a syntax error stops the
server at start (after a `/reload` it leaves the built-in freeroam in charge until the next reload).
- Chat commands arrive in `OnPlayerCommandText(pid, cmd, args)`; parse `args` with `sscanf(args, "ud")`
(`u` a player, `d` a whole number, `f` a number, `s` a word, `z` the rest of the line, `?` optional). Return
`true` when handled, `false` to let the server's built-in commands have it.
- What a player carries, their purse, their levels and their perks are records the server checks (`[audit]` in
`server.toml`): give and take only through `GivePlayerItem`, `GivePlayerMoney`, `SetPlayerInventory` and
`SetPlayerProgress` - they count at once, and `GetPlayerInventory` reads them back - and save on
`OnPlayerInventoryChange`. An item that reaches a game any other way is removed once the server runs `[audit] mode =
"enforce"` (it starts in `observe`, which only logs); a mode that hands out in another way vouches in
`OnPlayerNativeItem`, `OnPlayerAuditViolation` or `OnPlayerProgressViolation`.
- `Log(...)` writes to the server log with a `[lua]` prefix.
## Working here
- The mode is `.lua` in this folder. `server.toml` names it under `[gamemode] script`; `watch = true` there
reloads it on save, `/reload` in chat does the same by hand.
- Before calling a change done, run the Lua language server's check over the folder
(`lua-language-server --check .` - `.luarc.json` points it at `KcdMp.d.lua`): an unknown name or a wrong
argument type is a diagnostic there and a silent `nil` on the server. Zero diagnostics is the bar.
- Run a local server with `KcdMp.Server --gamemode .lua` and read its log for `[lua]` lines.
- The modes that ship with the server - `freeroam.lua`, `basicrp.lua` (with a web interface of its own),
`duel_arena.lua`, `plots.lua` (building plots with the in-game editor) and the `marker` example with its client
half - are the style to follow.
```
Then ask for the mode in plain words - *a capture-the-flag with two teams, a flag prop in each camp, ten minutes on
the HUD clock* - and the agent fetches what it needs. Claude Code fetches the URLs itself; for an agent without web
access, keep the whole reference beside the mode - it searches the file like the rest of the folder:
```bash
curl -O https://docs.kcd-mp.com/llms-small.txt
```
A **C# plugin** project works the same way with `sdk/KcdMp.Api.dll` and its `.xml` in place of the definitions file - the
agent reads the interfaces and their summaries from the project's reference - and `c-plugins.txt` in place of the Lua
API in the instructions.
## Other assistants
- **ChatGPT, Gemini, a chat window**: attach `llms-small.txt` as a file - at ~475k tokens it is too long to paste, and
only a very large context takes it whole - or just the set the job needs (the guides, the server API, the web
interface SDK); add the reference list the mode needs.
- **Cursor**: *Settings → Indexing & Docs → Add Doc* with `https://docs.kcd-mp.com/llms-small.txt`, then `@Docs` in a prompt.
- **Anything that crawls**: `robots.txt` allows every agent, and `/sitemap-index.xml` lists every page.
# Server configuration
> Every key of server.toml - the server, the game mode, its database, its client scripts and web interface, rates, area of interest, the world, containers, validation, the master list, commands, the audit, combat, parties, effects, actors and the spawn.
The server reads `server.toml` from its working directory (or `--config `). Every key is optional and the values below are
the defaults; a command-line flag overrides the file (`--max-players 64` beats `[server] max_players`). The file the server
ships, `server.toml.example`, carries the same keys with a line per group and a link here; the first start copies it to
`server.toml`, the file you edit, and an update replaces only the example. Without either file the server runs on the defaults
alone, which for two keys - the game mode's script and the game's tables - means less than the shipped file gives (noted below).
Where a key shapes what a game mode sees, its API page is linked.
## [server]
| Key | Default | |
|---|---|---|
| `name` | `"KCD:MP dev server"` | the server's name in the browser and the welcome |
| `motd` | `""` | a message shown to every joining player |
| `description` | `""` | a few lines about the server, shown when a player picks it in the launcher and on the public list. Plain text, `\n` starts a new line, 1000 characters at most |
| `website` | `""` | the server's website, `"https://..."` - a *Website* button beside the description. An address without `https://` gets it; anything that is no web address is left out (the console says so) |
| `discord` | `""` | a Discord invite - `"https://discord.gg/abc123"`, `"discord.com/invite/abc123"` or the code alone - a *Discord* button beside the description |
| `tags` | `[]` | up to 8 short words about the server, 24 characters each - `["roleplay", "pvp", "whitelist"]`; shown beside its name in the launcher's list and details, and matched by the list's filter - the only tags a server shows |
| `language` | `""` | the language its players speak, as a code: `"en"`, `"de"`, `"ru"`, `"pt-br"` ... |
| `port` | `7777` | UDP |
| `max_players` | `32` | the slot count; player ids run to `max_players - 1` ([`GetMaxPlayers`](/lua/server/functions/getmaxplayers/)). Fifty players walking, riding and fighting at once cost the server about a tenth of one core and 1.5 MB/s outbound; the count is yours to choose |
| `password` | `""` | players must send it to join; `""` = open |
| `level` | `"klaster"` | the level every client boots - `klaster`, `trosecko` or `kutnohorsko` ([levels](/reference/levels/), [`GetLevel`](/lua/server/functions/getlevel/)) |
| `build_hash` | `""` | require this game build; `""` = any |
| `stale_session_seconds` | `5.0` | names are not unique - two players may join under one name. A player who comes back under their name (from the same PC) replaces their own old connection once it has been silent this long, so a crashed game can rejoin at once; `0` = never |
| `log_file` | `"server.log"` | every line the console shows is written to this file too, next to the server; the previous run's file is kept as `server.log.1`. `""` = the console only |
| `http_status` | `true` | answers `http://:/` with the server's status as JSON - name, level, game mode, version, players and their names, slots, password, uptime, the world's time, the description, website, Discord, tags and language - for hosting panels, uptime monitors and server-list sites. It listens on the `port` number over **TCP** (the game uses UDP), so open or forward TCP too if you want it reachable from outside; a server nobody can join right now answers `503`. `false` = off |
| `join_rate` | `20` | players let in a second at most. When more come at once - a restart, a connection problem that made everyone's game rejoin - the rest wait their turn in the order they came, at the game's "handshaking" step, instead of all loading the server together; a quarter of the rate while the server's connection is very busy. See [below](#many-players-at-once). `0` = no limit |
| `stall_exit` | `30` | seconds: a server that has not been able to send or receive for this long ends itself with exit code `3`, so a host that starts a stopped server again (a game panel, a service manager) brings it back within seconds - its players have lost the connection by then anyway. The game mode's shutdown runs first, so a mode that saves at shutdown still saves. `0` = never; otherwise at least `10` |
| `network_threads` | `1` | how many network threads the server runs, each on its own UDP port - for servers of a thousand players and more. `1` = one, on `port` alone. `2` to `16` also listen on the ports after `port` (or on `network_ports`), and **every one of them must be open** to the players. See [below](#more-than-a-thousand-players) |
| `network_ports` | `[]` | the other network threads' UDP ports, when they are not the ones after `port` - for a game panel that hands out extra ports of its own: `[7790, 7791]` (that many threads plus the main one; `network_threads` may be left out) |
## [gamemode]
| Key | Default | |
|---|---|---|
| `script` | `"gamemodes/freeroam.lua"` | a `.lua` script or a `.dll` compiled against `KcdMp.Api` (`path.dll:TypeName` picks a type); `""` (the value without a `server.toml`) = the built-in freeroam |
| `watch` | `false` | `true` re-reads the script (or a C# plugin's DLL) when its file changes on disk - a development server ([`ReloadGameMode`](/lua/server/functions/reloadgamemode/)); the client scripts' folder is watched too. The web interface's folder is watched as well, apart from the script: a change there reloads the interface alone - the script keeps running and its state stays. A change is read once the folder has been still for a second and a half (a build tool's writes are over); a folder with an error is read again, less and less often, until it loads |
## [gamemode.database]
The game mode's own database, for whatever it wants to keep - accounts, factions, scores, the rules on doors and chests
([`GetDatabase`](/lua/server/functions/getdatabase/); [the databases guide](/guides/databases/) sets one up). Optional:
without a `driver` the mode's `GetDatabase()` answers `nil` and the mode runs without one. The server's own state - doors,
chests, taken items, the bans - is kept apart in `data/server.db`, made on the first start, and needs nothing here. The
credentials live in this file and nowhere else: never in a game mode script. The environment variables `KCDMP_DB_DRIVER`
and `KCDMP_DB_URL`, when set, replace `driver` and `url` (a server started by a script, a container), the way
`KCDMP_MASTER_URL` does for the master list.
| Key | Default | |
|---|---|---|
| `driver` | `""` | `"mysql"` (MariaDB too) or `"postgres"`; `""` = no database |
| `host` | `"127.0.0.1"` | |
| `port` | `0` | `0` = the driver's default (3306 / 5432) |
| `user` | `""` | a user with rights on this one database and nothing else |
| `password` | `""` | |
| `database` | `""` | the database's name |
| `url` | `""` | one line instead of the parts above: `"mysql://user:pass@host:3306/kcdmp"` |
| `ssl` | `"preferred"` | `"required"` for a database on another machine, `"preferred"`, `"none"` |
| `pool` | `8` | connections at most |
| `timeout_seconds` | `10` | a query running longer is cut off and fails |
| `max_rows` | `10000` | a query returning more is refused with an error (page it) |
| `slow_query_ms` | `500` | a query taking longer gets a line in the server log |
## [client]
The game mode's client half: Lua files the server sends to every joining player and runs in their game, in a sandbox that
reaches nothing outside the game ([the client guide](/lua/client/getting-started/)), and its web interface: a folder of
HTML, CSS and JavaScript files with a `ui.toml`, which the players' games show on top of the game. A mode without either
needs nothing here.
| Key | Default | |
|---|---|---|
| `scripts` | `""` | a folder of `.lua` files, sent in name order; `""` = the `client/` folder next to the game mode's script or plugin (`gamemodes/arena/arena.lua` + `gamemodes/arena/client/*.lua`), or for a single-file mode the folder named after it (`gamemodes/freeroam.lua` + `gamemodes/freeroam/client/*.lua`, the way the shipped freeroam draws its clock) - the built-in mode has none unless a folder is named. At most 64 files, 256 KB each, 2 MB together. `/reload` sends the folder again |
| `console` | `false` | `true` lets the players open the **F9** window of the KCD:MP overlay on this server - the console line and the debug view. A convenience for a development server, not a security boundary: the server never trusts what a client says either way. The game's own console (the `~` key) stays closed on every server |
| `ui` | `""` | the web interface: a folder (or a `.zip` of the folder's contents) of HTML, CSS, JavaScript, image, font and sound files with a `ui.toml` at the top. `""` = the `ui/` folder (or `ui.zip`) next to the game mode (`gamemodes/arena/arena.lua` + `gamemodes/arena/ui/`), or for a single-file mode the folder named after it (`gamemodes/freeroam.lua` + `gamemodes/freeroam/ui/`); a folder that several modes would share is never taken on its own - the built-in mode has none unless a path is named here. At most 512 files and 64 MB in all. Players download only the files their game does not have yet. The server checks the whole folder before it sends a byte: a folder with an error is not sent at all, the players keep the standard interface and the server log says what is wrong. `/reload` reads the folder again, and so does `[gamemode] watch` |
| `ui_cdn` | `""` | `https://host/folder/` - where the big files come from: a file that is not text (a picture, a font, a sound, a video) and is over 1 MB is not sent by the server; the players' games fetch it from here, under the hash of its content. The server's `--pack-ui ` switch writes the files to upload there and stops; list the host in the `connect` permissions of `ui.toml`. Without this key a file that big is an error. `""` = none |
| `ui_max_mb` | `8` | the most the web interface may send over the game connection, in MB (1 to 64), counted before compression and without the files that come from the CDN. From 4 MB up the log warns you: every player downloads that much the first time they join |
| `sounds` | `""` | the game mode's own sound files (`.ogg`, `.opus`, `.wav`, `.mp3`, `.flac`), played in the world through the game's sound: a folder. `""` = the `sounds/` folder next to the game mode (`gamemodes/arena/arena.lua` + `gamemodes/arena/sounds/`), or for a single-file mode the folder named after it (`gamemodes/freeroam.lua` + `gamemodes/freeroam/sounds/`). Nothing of it goes to the players at the join: a file is sent to a player only when a sound of it plays near them, the first time, and their game keeps it for the next time. The server log names the folder and its size at the start |
| `sounds_cdn` | `""` | `https://host/folder/` - a copy of your sounds folder on your own web host (Cloudflare R2, Bunny, Amazon S3 or any other that serves files over https): upload the folder as it is, and the players' games fetch each sound from there (`https://host/folder/voices/elder.ogg` for `voices/elder.ogg`) instead of from the server, which keeps its upload for the game. Every copy is checked against the server's own file: one the host lacks, or an old one it still holds after you changed a sound, comes from the server instead, so upload a changed sound again when you can. `""` = the server sends them |
| `sound_max_mb` | `8` | the biggest one sound file may be, in MB (1 to 16). A bigger file is not played, and the log names it at the start |
| `ui_report_errors` | `false` | `true` makes the players' games report script errors in the web interface's pages: the game mode is told about each one (at most 5 a minute for each player) and the server log gets a line. Off by default, when an error stays in the player's own game log. What arrives is the player's game's word, cut to a short length and cleaned of control characters, and never a reason to trust the player |
## [rates]
| Key | Default | |
|---|---|---|
| `tick_hz` | `30` | the simulation rate - how often `OnTick` runs |
| `snapshot_hz` | `30` | snapshot rounds per second, at most `tick_hz` |
| `workers` | `0` | threads for the per-client phase; `0` = the cores minus two |
| `adapt` | `true` | a player whose connection falls behind gets every second or third snapshot round until it catches up, and a player who has confirmed none gets one full snapshot at a time - what keeps a crowd on slow connections from swamping the server. `false` = every player gets every round |
| `nearest_full_rate` | `32` | in a crowd, the nearest this many moving players, horses, characters, dogs and carts around a player go in every snapshot, whatever `snapshot_budget` says; `0` = none is sure of every snapshot |
| `snapshot_budget` | `1600` | the bytes of movement one snapshot may carry to a player. In a crowd that does not fit, the nearer, faster and fighting ones (a party member, an opponent) go first and the others wait their turn, never longer than about 133 ms (some 7 updates a second at the least). A stop, a crouch, a drawn weapon or a seat always goes at once, and nothing waits on a server whose snapshots come less often than every 50 ms. It keeps a player's download steady in a packed town square instead of growing with every newcomer. `0` = no limit (everyone in every snapshot) |
| `reliable_rate` | `24` | megabytes a second at most of the messages that must arrive - everything but movement, voice and hit effects. A flood beyond it (hundreds of loose items restocked at once, a wave of players joining) waits its turn, each player's messages in their order, instead of being pushed out at once and choking the server's connection. On a big server the busiest moments may hold a message back a fraction of a second; set it for your server's upload speed ([below](#choosing-reliable_rate)). Left out of the file, the server uses `24`; at least `1` |
| `grime_rate` | `10` | the other players' dirt, blood and armour wear on each player's screen: at most this many updates a second to each player, the nearest bodies first. A body that changes again before its turn sends only its newest state. In a big battle every blow changes them, and without a limit they went to everyone after every blow - see [below](#big-battles). `0` = no limit |
| `grime_range` | `50.0` | metres: those updates only for bodies this near; a body farther away is brought up to date when it comes this near (a body coming into view always arrives with its dirt, blood and armour as they are). `0` = everyone in view |
| `crowd_report_hz` | `15` | when the server is too busy to send every player every movement update, or its simulation falls behind (a crowd of a thousand or more), the players who are not fighting send their own movement this many times a second instead of 30 until the crowd thins - at such times the others see them less often than that anyway. A player in a fight, or with a weapon out, keeps 30. `0` = always 30; otherwise `10` to `30` |
| `socket_buffer` | `16` | megabytes the operating system may hold for the server's connection each way, incoming and outgoing. When the server is busy for a moment, the players' movements and actions wait there; when it is full, the system throws the newest away and players feel lag, rubber-banding and blows that don't land. On Linux the system caps it - see [below](#big-crowds-on-linux). `0` = the network library's own 1 MB; at most `256` |
### Choosing `reliable_rate`
Pick the row of your server's upload speed. Hosts sell speeds in megabits a second (Mbit/s); `reliable_rate` is in megabytes a
second (MB/s) - divide by 8. The movement updates take their share of the line first (up to about 50 KB a second for each player
with the default `snapshot_budget`: about 5 MB/s at 100 players, 19 MB/s at 385), and `reliable_rate` is about half of what is
left. One server sends about 40 MB/s at most in all, so a value above `48` gains little.
| Server's upload | In MB/s | Typical crowd | `reliable_rate` |
|---|---|---|---|
| 100 Mbit (a small VPS, some game panels) | ~12 | up to ~100 players | `5` - `6` |
| 500 Mbit | ~60 | 300 and more | `20` - `24` |
| 1 Gbit (a dedicated server) | ~120 | 400 and more | `24` (the default) - `48` |
| 2 Gbit | ~240 | any | `48` |
| 10 Gbit | ~1 200 | any | `48` |
Too low, and a flood takes longer to go out: a hundred players rejoining at once is roughly 30 MB - about a second at `24`, half a
minute at `1`. Too high for the line, and a flood can overflow it again, which is what the setting is there to stop. **The default of
`24` is more than a 100 Mbit line carries** - on such a host, set it from the table.
### Many players at once
A big server's costliest moment is a crowd joining together - after a restart, or when everyone's game rejoins by itself after a
connection problem. Each newcomer is introduced to everyone and everyone to them, so a thousand players joining in the same few
seconds asks the server for millions of messages at once. `join_rate` (in `[server]`) lets them in a few at a time - 20 a second by
default, a quarter of that while the server's connection is very busy - in the order they came; the others wait at the game's
"handshaking" step without losing their place. A thousand players take under a minute. A server that never sees more than a few dozen
players at once never notices it.
### Big battles
When hundreds of players fight in one place, every blow is news for everyone who sees it - and only for them: players elsewhere on the
map are not told about a battle they cannot see. The server keeps that news in step with
what each player's connection can take: the others' dirt, blood and armour wear wait their turn per player (`grime_rate`,
`grime_range`), and each player's messages go out only as fast as their own connection passes them on, so a slow connection never
holds up the server's reading of everyone else's movements and blows. If a battle still sends more than the server can deliver, the
players whose messages have fallen furthest behind (about 15 seconds) are let go one at a time - their games rejoin by themselves -
instead of everyone timing out at once. The 10-second report in the log shows how close a server is: `reliable out:` names what
was sent, and the `network thread:` line the players' round trips and any player let go.
On a server that sees battles of several hundred players, lower `grime_rate` (`5`) and `grime_range` (`30`) first; then
`snapshot_budget` and `nearest_full_rate`, and `snapshot_hz` last (it makes everyone's movement less smooth).
### Big crowds on Linux
Linux holds every program's connection buffer to its own limit, `net.core.rmem_max` (incoming) and `net.core.wmem_max`
(outgoing) - about 0.2 MB on most systems - whatever `socket_buffer` asks for. The server's log says what it got at the start:
```
udp socket buffers: 0.2 MB receive, 0.2 MB send - [rates] socket_buffer asks for 16 MB; ...
```
A few dozen players never fill it. With hundreds, a busy moment can, and the server then logs a warning every ten seconds:
`the system dropped N incoming datagrams ...`. Raise the limits to at least `socket_buffer` (as root on the machine itself - a
container or a game panel cannot change them; ask your host):
```
sysctl -w net.core.rmem_max=16777216 net.core.wmem_max=16777216
```
and keep them across restarts with the same two lines (`net.core.rmem_max = 16777216`, `net.core.wmem_max = 16777216`) in a file
under `/etc/sysctl.d/`. Restart the server afterwards. Windows has no such limit.
### More than a thousand players
One network thread sends and receives every player's messages, and around two thousand players it is what fills up first - the
players' round trips grow, the most in a big battle. `network_threads` (in `[server]`) splits the players over several, each with
its own UDP port:
```toml
[server]
port = 7777
network_threads = 3 # also listens on 7778 and 7779
```
Every player still joins on `port` - the server list, the launcher and join links name that port alone - and the server sends each
joining game on to the thread with the fewest players; the game follows by itself (a join takes a fraction of a second longer). So
**open every port in the firewall or the game panel**, UDP: a game that cannot reach the port it was sent to comes back to `port`
after a few seconds and joins there, and the log warns that the port could not be reached (three such games in a row and that
thread takes no joins for five minutes). On a panel that gives out extra ports that are not next to `port`, list them:
`network_ports = [27016, 27020]`.
The 10-second report then has a `network thread N` line for each, with its players, and the server's line counts the joins sent on.
If a thread other than the one on `port` stops working, its players lose their connection, their games rejoin by themselves and are
placed on the others - the server goes on. Two to four threads suit a thousand to a few thousand players; a server that never sees
more than a few hundred has nothing to gain.
With the connection no longer full, the server's own simulation is the next limit: when it falls behind with a big crowd, the server
sends everyone's movement a little less often and asks the players who are not fighting for fewer updates (`crowd_report_hz`), the
same as when the connection is full, until it catches up. The log says which of the two is the reason.
## [aoi]
| Key | Default | |
|---|---|---|
| `radius` | `200.0` | metres a client sees around itself; `0` = everyone sees everyone. It also decides who hears of the level's loose items, doors and locked chests: a change goes to the players within about this distance of it, and a player is told what changed in a part of the level as they come near it - instead of every change going to everyone and every joining player getting the whole level's |
| `cell` | `64.0` | the grid cell size, metres |
## [world]
| Key | Default | |
|---|---|---|
| `time` | `"07:30"` | the world clock at start ([`SetWorldTime`](/lua/server/functions/setworldtime/)) |
| `time_ratio` | `15.0` | game seconds per real second; `0` freezes the clock ([`SetTimeRatio`](/lua/server/functions/settimeratio/)) |
| `rain` | `-1.0` | rain forced on every client, `0` .. `1`; below `0` = the weather's own ([`SetRain`](/lua/server/functions/setrain/)) |
| `weather` | `""` | the sky every client follows - a [preset or a profile](/reference/weather/); `""` = the server rotates the presets every few game hours ([`SetWeather`](/lua/server/functions/setweather/); the game mode sets how often each comes up - [`SetWeatherChances`](/lua/server/functions/setweatherchances/)) |
| `weather_blend` | `10.0` | seconds a weather change blends in over |
| `max_entities` | `256` | horses, pickups, props, actors and dogs the world holds at once |
| `map` | `"data/maps/{level}"` | the server's map of the level: the objects admins and the game mode placed on it and the parts of the level they took out ([Building on the map](/guides/map-editor/), [the map API](/lua/server/#the-servers-map)); `{level}` is the level's name. Every player's game downloads it at the join, keeps it for the next one, and gets every change at once. Nothing is written until the first edit; a map this server cannot read (a newer format, a broken file, another level's) loads read-only and is never written over. `""` = no map |
## [containers]
The level's chests and the loose items on its tables: what they hold, when they refill, whether that survives a restart
([the doors and containers API](/lua/server/#doors-and-containers)). The loot tables come with the tables export
([Exporting the game's tables](/guides/game-tables/)); without it every chest opens empty. The server keeps the doors,
gates, chests, taken items and the bans in `data/server.db`, made on the first start - there is nothing to configure, and
nothing else is kept: the server remembers no player (accounts are a game mode's, on its `[gamemode.database]`).
| Key | Default | |
|---|---|---|
| `loot` | `"game"` | what a chest holds the first time anyone opens it: `"game"` = rolled from the game's own loot tables, the preset the level placed that chest with - the way the single-player game fills it; `"empty"` = nothing, for a server whose game mode fills chests itself ([`SetContainerItems`](/lua/server/functions/setcontaineritems/), [`SetContainerPreset`](/lua/server/functions/setcontainerpreset/)) |
| `restock` | `"game"` | when a chest somebody touched is rolled again, and when a loose item somebody took comes back: `"game"` = each object's own period from the level (seven world days unless the level says otherwise), `"never"`, or a number of **world days** for every one (`"2"`). World days run on the server's clock (`[world] time_ratio`), so a frozen clock restocks nothing; `/restock` fills a chest at once |
| `world_items` | `true` | the items lying on tables and shelves are one per world: taken by one player, gone for everyone, back after the period as a pickup; `false` = every player's game keeps its own copies, as before |
| `persist` | `true` | doors, gates, chests and taken items are as they were after a restart (the server's own database); `false` = a restart is the level's defaults again and nothing is written |
## [validation]
The speed check measures each player over the last two seconds against the cap of the way they move - on horseback, on foot,
crouched. A report that would take them faster is refused and the client pulled back to the last accepted position, so a speed
hack gets its player nowhere faster than the cap; a lag spike of up to two seconds is no violation. A single step of more than
2 m faster than the cap (a teleport) is refused at once. Getting on or off a horse may move a player up to 8 m at once - from
their feet into the saddle or back - and no further.
Beside it run the **finer checks**, which `strict` and `speed_scale` below govern: a cap for each way of moving (on foot,
crouched, in combat mode, with an injured leg, with no stamina left, in the saddle), taken from the game's own movement figures
with about a tenth of room and an allowance of a second or two so a lag spike is no violation; jumps that rise too far or hang
too long; a horse galloping on stamina it has not got, and a loose horse moving faster than a rider could make it; and a block,
a dodge or a landed blow against a server actor that the server's own record of the fight does not back. They always count and
log; only with `strict = true` do they pull a player back (and read a sprint's stamina cost from the pace the server measured). Under
`strict` a server actor's swing that the player's game never answers is landed by the server itself, unless the server's own record
shows the player perfectly blocked it, blocked it, stepped away or was out of the actor's reach (an unarmed actor reaches 0.7 of what an
armed one does, [`[actors] reach`](#actors)). The ones that measure how fast a player or a horse moves run while `max_speed` is above `0` (the jump check and the checks on a server actor's blows do not need it), and a game mode hears each as a hint through
[`OnPlayerCheatDetected`](/lua/server/callbacks/onplayercheatdetected/) - `speed`, `blink`, `noclip`, `jump`, `fall`, `horse`,
`never_hit`, `void_ratio`, `contact_ratio`. The server also watches falls: a landing from four metres or more that the game reported no damage for
is logged as `fall`, but this version applies no fall damage of its own, with `strict` or without.
| Key | Default | |
|---|---|---|
| `max_speed` | `18.0` | metres per second on horseback (a gallop is about 11, a dash with horseshoes and a horse perk about 16), and the most anyone may move; `0` = no speed check at all. Under `16.2` riders at a full dash are pulled back, and the server warns at start |
| `max_speed_on_foot` | `8.0` | walking, running, sprinting (a sprint is about 5); `0` = `max_speed` |
| `max_speed_crouched` | `5.0` | `0` = `max_speed_on_foot` |
| `heightmap` | `"data/heightmaps/{level}.hmap"` | the level's terrain - the files `tools/KcdMp.ExportHeightmap.exe` writes from your own game ([Exporting the terrain](/guides/terrain/)). The terrain check is on while the level's file is there and off while it is missing, so exporting the terrain is all it takes; `""` = never ([`GetTerrainHeight`](/lua/server/functions/getterrainheight/)) |
| `navmesh` | `"data/navmesh/{level}.knav"` | the level's navigation mesh - the files `tools/KcdMp.ExportNavmesh.exe` writes from your own game ([Exporting the navigation mesh](/guides/navigation/)). While the level's file is there the NPC actors walk around walls and [`FindPath`](/lua/server/functions/findpath/) answers; while it is missing they walk straight lines; `""` = never |
| `navmesh_search_nodes` | `16384` | the most nodes one path search may visit - a game mode's [`FindPath`](/lua/server/functions/findpath/) or [`IsReachable`](/lua/server/functions/isreachable/), an actor's walk, a chase. Past it the search answers "no way" and the log names it with its two points, so one search can never hold up the server. A walk of several hundred metres through a town usually needs a few thousand; `256` to `10000000` |
| `navmesh_search_ms` | `100.0` | the same in milliseconds: whichever of the two comes first ends the search; `1` to `10000`. A place no way leads to at all (a room the mesh has closed in, a ledge) answers at once without a search |
| `collision` | `"data/collision/{level}.kcol"` | the level's collision geometry - the files `tools/KcdMp.ExportCollision.exe` writes from your own game ([Exporting the collision geometry](/guides/collision/)). While the level's file is there [`RayCast`](/lua/server/functions/raycast/) answers and the floor under a player counts as the ground; `""` = never |
| `line_of_sight` | `false` | with the geometry: a hit or shot claimed, a pull of a rider off their horse, a grab of a body or a pickpocket's reach through a wall (or a closed door) is refused; off, the server only logs what it would have refused |
| `no_clip` | `false` | with the geometry: a player whose reported step goes through a wall (or a closed door) is pulled back to the last accepted position; off, the server only logs what it would have refused. A wall is what a walker bumps into (small things the game lets a player pass through are not), and a player who walks on past one at a believable pace for a second and a half is followed, with a `noclip` hint to the game mode |
| `max_below_terrain` | `6.0` | metres a player may be under the terrain (cellars, mines) |
| `max_above_terrain` | `60.0` | metres over it (towers, cliffs); with the collision geometry the floor under the player - a bridge, an upper storey - is the ground, so a storey's worth (`10`) does |
| `strict` | `false` | the finer checks above act: `true` pulls the player (or the horse) back and prices a blow the server's record does not back; `false` only counts and logs what it would have done - read the log for a while before switching it on. It also decides the one thing of the [progression check](#the-progression-check) that moves a player - sprinting or mounting a horse while carrying more than the game allows (an overloaded player's jump is never held against them: the game allows it) - which needs `[audit] mode = "enforce"` as well, and which this version does not do yet (the weight rules are not measured: they only count and log). The XP, level, perk, nourishment and energy checks do not use `strict` |
| `speed_scale` | `1.0` | the limits of the finer checks above times this (`0.5` to `4`), for a server whose mods make players or horses faster: the caps of each way of moving, the pace of a player the game slows down, and a horse's sprint speeds. It does **not** move `max_speed`, `max_speed_on_foot` and `max_speed_crouched`, which refuse and pull back whatever `strict` says - a server whose mods double everyone's speed raises those too. An out-of-range value stops the server at start |
## [master]
The server list: the server announces itself there every `interval_seconds` and shows up in every launcher's *Servers* tab.
The environment variables `KCDMP_MASTER_URL` and `KCDMP_MASTER_ADDRESS` replace the two addresses when set, and the
command line's `--master` / `--master-address` beat both.
| Key | Default | |
|---|---|---|
| `url` | `"https://kcd-mp.com/server-list"` | the public list; another list's address, or `""` = announce nowhere (a private server) |
| `interval_seconds` | `60` | how often |
| `public_address` | `""` | the address the list should show; `""` = the one the master sees the heartbeat come from; `"host:port"` when players reach the server on another port than `[server] port` (a tunnel's) |
## [commands]
Admins are the game mode's: it promotes them with [`SetPlayerAdmin`](/lua/server/functions/setplayeradmin/) from its own
list or its own accounts (the shipped freeroam takes the names from the `KCDMP_ADMINS` environment variable, `"Henry,Hans"`);
the server keeps no accounts and no admin list.
| Key | Default | |
|---|---|---|
| `builtin` | `true` | `false` = no [built-in chat commands](/reference/chat-commands/) at all; the mode answers everything |
| `public` | `["help", "pos", "players", "items", "uptime"]` | the built-ins anyone may use; every other one answers admins only |
| `disabled` | `[]` | built-ins switched off by name, e.g. `["horse", "rain"]`; the mode may still answer them |
## [audit]
The audit compares what a player's game says with what the server knows happened. It has two halves under one switch - the
**item check** and [the progression check](#the-progression-check), on the character - and `mode` decides what either does
about a difference.
**The item check.** Every item that reaches a player's bag or their horse's saddlebags must be explained by something the
server saw happen: its own gift ([`GivePlayerItem`](/lua/server/functions/giveplayeritem/),
[`SetPlayerInventory`](/lua/server/functions/setplayerinventory/)), the spawn's outfit, a pickup of an item it placed, one of
the level's own items, a chest's contents, a shop, a body that was searched, the player's own drop picked up again, an arrow they
shot, a move between their bag and their horse's saddlebags, or the game's own making (crafting, alchemy, cooking,
butchering), which the game mode may judge with [`OnPlayerNativeItem`](/lua/server/callbacks/onplayernativeitem/). The
player's game names where each item came from the moment it arrives, and the server checks that against its own records. An
item nothing explains - one a cheat tool conjured or multiplied - is logged and, with `mode = "enforce"`, taken away after a
second or two. **That alone is never a violation**: an honest player whose game and the server disagree for a moment loses the
unexplained piece and nothing else. Every kind of item is judged the same way, a sword or a loaf. What leaves a player is
judged too: a drop, a use, a shot, a put into a chest or a sale of more than the records explain never becomes somebody's
pickup, a chest's contents or money.
A **violation** is a cheat proven beyond doubt: a game that keeps what the server ordered out of it, one that forges where an
item came from or the order of its own reports, one that edits the same item again and again after it was taken away, or one that
stops reporting its items (nothing at all for two minutes, or no report of its bag within twenty seconds of the spawn's strip).
[`OnPlayerCheatDetected`](/lua/server/callbacks/onplayercheatdetected/) hears the detections, the proven ones and the
hints alike, with how many of each kind the player raised (the movement hints come five times a visit, then once every five
seconds per kind), and [`OnPlayerAuditViolation`](/lua/server/callbacks/onplayerauditviolation/) every item about to be taken away.
| Key | Default | |
|---|---|---|
| `enabled` | `true` | `false` = the audit is off: every item a player's game reports is taken as the server's, and the progression check takes levels, XP, perk points, perks, nourishment and energy as the game reports them and judges no carry weight; the server's own gifts, takes and sales still work |
| `mode` | `"observe"` | `observe` judges and logs everything and takes nothing away - items, and the progression check below; `enforce` takes away what nothing explains and puts back what the server can disprove - switch to it once the log shows that your game mode hands out nothing the server does not see ([what each mode does](#what-observe-and-enforce-do)). Any other word stops the server at start. The environment variable `KCDMP_AUDIT_MODE` replaces the file's value and the command line's `--audit-mode enforce\|observe` beats both - the way to fall back to `observe` without editing the file |
| `action` | `"kick"` | `kick`, `ban` or `log` for a violation (a cheat proven beyond doubt: the ones above, and a game that writes the same forged XP, level or perk points back again and again); an unexplained item alone is taken away, never punished. Any other word stops the server at start |
| `ban_seconds` | `0` | for `action = "ban"`; `0` = for good |
| `guarded_categories` | `"Armor,MeleeWeapon,MissileWeapon,Ammo,Helmet,Hood,Money"` | the item categories ([items](/reference/items/)) the game's own making never gives a player: a crafted or found item of one of them counts only when the game mode takes it ([`OnPlayerNativeItem`](/lua/server/callbacks/onplayernativeitem/)), and a loose item of one that the level's tables do not list is never taken on the player's word. It does not choose what is judged - every category is. `Money` is the purse, and guarding it makes it the server's: a player starts each visit with the money the game mode gives ([`GivePlayerMoney`](/lua/server/functions/giveplayermoney/)) and keeps it through a death, and a shop sells against the money the records explain ([`GetPlayerMoney`](/lua/server/functions/getplayermoney/)). `HorseGear` (a horse's saddles, bridles, saddlebags and caparisons) is left out; add it to guard it like armour. An item the game's tables do not know - a game mod's - is never guarded |
An older file's `strikes` line is read and ignored: the item check judges every change at once and no longer counts strikes.
**The tables export.** The check needs the game's tables export ([Exporting the game's tables](/guides/game-tables/)): without
it the server cannot tell the items apart, so the item check is **passive** - it follows what the players' games report and takes
nothing away, until the export is there - and the carry-weight check has no weights to add up. The perks and consumables lists
in the same file are what the progression check understands perks, potions and drinks by. Export again after an update of the server:
the export carries the game's clothing sets and weapon sets (with how many of each item), which the check needs to recognise a
player's starting outfit and weapons. With an older file a spawn's clothing is credited by item category and the arrows of a
weapon set as one stack, up to a hundred, and the log says so (`the clothing preset ... is not in the tables export: its outfit is
credited by item category (re-export the tables)`).
### What `observe` and `enforce` do
| | `observe` | `enforce` |
|---|---|---|
| An item nothing explains | stays with the player; the log says `would remove` | taken out of the player's game after a second or two; the log says what and why |
| A drop, a use, a shot, a put or a sale of more than the records explain | goes through; the log says what it would have refused | refused |
| The first seconds of a visit (below): a drop, the use of a bag item, a shot, a shop deal, a dice stake | goes through | refused until the player's items are known |
| What a game mode reads of the bag and the purse ([`GetPlayerInventory`](/lua/server/functions/getplayerinventory/), [`GetPlayerMoney`](/lua/server/functions/getplayermoney/)) | what the player's game holds | what the server's records explain - gifts on their way counted, takes on theirs taken off |
| Levels, XP, perk points, perks, nourishment, energy nothing explains | the log says `would set back`; the record a mode reads follows the game; nothing is sent to the player's game | the rules that act (below) put it back, and the record holds the figure the server explains |
**Whatever the mode**, four things happen: a cheat proven beyond doubt gets the `action`; an item whose pickup or take the server
itself refused (it was out of reach, somebody took it first, [`OnPlayerPickup`](/lua/server/callbacks/onplayerpickup/) or
[`OnPlayerTakeWorldItem`](/lua/server/callbacks/onplayertakeworlditem/) said no) is taken out of the bag at once, because that
is a decision about a claim and not a judgement; what the game mode orders is carried out - its gifts and takes, a restore, a
sale, a body's search, a dice stake - and a progression order of the mode's own that the player's game did not confirm within
about 45 seconds is sent again; and the spawn's strip (below) happens.
**The first seconds of a visit.** At a player's first spawn of a visit - and at a later spawn whose clothing or weapon preset
differs from the one before - the server has their game empty the bag and put the outfit on, then starts to follow the bag: the
player's game sends the whole of it once and every change after. A respawn with the same presets keeps the bag and the purse.
Until the whole bag has arrived - the first moment for an honest game - `enforce` refuses a drop, the use of a bag item, a shot,
a shop deal (`Not now: your items are still being checked.`) and a dice stake, so nothing is spent from records the server has
not got. A game that never sends it is a proven `ledger_silent`: twenty seconds after the strip, sixty from the spawn when the
strip itself never comes.
**What explains an item, for the owner.**
- *The spawn's outfit* - the pieces of `[spawn] clothing_preset` and `weapon_preset` (and a mode's [`SetSpawnInfo`](/lua/server/functions/setspawninfo/)
presets), as the tables export lists them, and `[spawn] items`. A mode that dresses players with `SetPlayerInventory` instead gives them.
- *The game's own making* (crafting, alchemy, cooking, butchering, a drink's empty vial ...) - taken for ordinary items within a
limit of value a minute, so a normal craft passes and a flood of them does not; a guarded category is refused,
smithing a sword included, until the mode adopts it in `OnPlayerNativeItem`; anything the game's own scripts make is refused
while story quests are off ([`SetStoryQuests`](/lua/server/functions/setstoryquests/)); an item made by code outside the
game is never credited - it is a `foreign_call` hint, and under `enforce` the item is removed.
- *The level's items and chests* - a pickup of an item the server placed counts when the player was within about eight metres
and could see it, the first claim wins and a late one loses the item. One of the level's own loose items counts when the
level's tables list its place and the player stood within about eight metres of it; a loose item with no place in the export is
accepted only for ordinary items and within a limit a minute, and ammunition only up to what the player shot themselves. A
chest holds what the server's record of it holds: a player may open at most four a second, hold sixteen open at once, and a
game that names hundreds of chests the level does not have is refused new ones. An honest hand never notices.
- *A horse's saddlebags* - the server keeps what each horse carries, and the player whose game holds the bags (the owner, else the
rider, else whoever controls it) sees it; what moves between that player's bag and the saddlebags is credited only for what the
records let go of. The saddle, bridle and horseshoes the game puts on a horse the server brings (`/horse`, a mode's
[`CreateHorse`](/lua/server/functions/createhorse/)) are the horse's own gear: the check expects them and neither counts them
as saddlebag items nor logs them as unexplained.
**What an honest player can lose under `enforce`**, for the items: an item a game mod puts in the bag, which the server cannot
explain (`[mods] policy` is the owner's tool, and `OnPlayerAuditViolation` a mode's); a crafted, butchered or found item over the
limit above or of a guarded category; and an item a game mode's client script or an event hands out without the server's calls.
A game that says it cannot report its items is followed and has nothing taken by the check. The log names every removal and where the item seemed
to come from.
### Reading the log
| The line | It means |
|---|---|
| `item ledger: observe - ...`, `item ledger: enforce - ...` at the start | the item check's mode; `PASSIVE` = no tables export, nothing is removed; `OFF` = `enabled = false` |
| `progression audit: observe - ...`, `progression audit: enforce - ...` | the progression check's mode; under `enforce` the next line names the rules that do not act yet; further lines name a missing perk or consumables list and `carry-weight audit: INERT` (no item list); the nourishment and energy check follows with its own `nourishment and energy audit: ...` lines, saying what acts under the mode and what is only counted |
| `Henry#0 (...): item ledger open (23 item(s) reported)` | the server follows Henry's bag from here on |
| `... shortswordBroad x1 (native script) not explained by the records: observe - left alone` | a gain nothing explains and where it seemed to come from (below); under `enforce` `removed unless explained within a moment` |
| `... observe: would remove shortswordBroad x1 - holds 1, the records explain 0 (native script)` | what `enforce` would take, at most one line a class every half minute |
| `... shortswordBroad x1 removed from their bag - holds 1, the records explain 0 (native script) (removal 1 this session, no violation)` | the same, done: a removal, never a kick |
| `... drop of '' refused, more than the records explain`, `... observe: would refuse the use of ...` | what leaves a player, judged |
| `... anti-cheat: removal_ignored (proven): ..., kick` | a proven cheat and what the `action` did; `advisory` ones are hints (`, tolerated by the game mode` when a mode said so) |
| `... progression: weapon_sword: would set back 458.1 XP (...)` | the progression check, per player: the first thirty lines of a visit, then every fiftieth |
| `... weight trace: carried 12.5, capacity judged 140.0 (bound 140.0, strength 5, reported 140.0), not overloaded` | the carry-weight check's figures, when they move (`strength 5 + 2.5 from a drink` while a drink lifts the capacity) |
| `item ledger (observe), since the start: grants 12, native.craft 40, removed 3, would_remove 12, ...` | every ten minutes: the counters since the start, named for what they count (`grants`, `removed`, `would_remove`, `refused.`, `native.`, `progress.*`, `vitals.*`, `weight.*` ...) |
| `Henry#0 (...): measures: 0 steps refused, ...; reported: progress x2` | when a player leaves: what the checks counted for them |
Per player the item check writes its first forty lines of a visit and every fiftieth after that (`(120 ledger lines this
session)` at the end of such a line), so a flood from a hostile game cannot fill the log; the counters carry the rest.
The words in brackets after an item say where the server thinks it came from: `unknown` (no part of the game that moves items saw
it arrive - a value edited in memory looks like this), `native ` (the game made it: `craft`, `butcher`, `loot`, `tool`,
`eat`, `script` - the game's own Lua, which a cheat tool uses too - ...), `pickup`, `level`, `container`, `grant` (the server gave
it), `outfit`, `ammo`, `from a horse`, `own drop`. A game that says its item reporting cannot run is followed, not judged, under
`observe`; under `enforce` it is credited for what the server's own records explain and nothing is taken from it
(the log line reads `their hooks do not run: would remove ...`, and the hint is `ledger_unavailable`).
### The progression check
The same switch covers a second check, on the character. A trainer writes a level, XP, perk points, a perk, the nourishment or the
energy into a player's game as easily as a player earns them, so the server keeps its own account of what explains each rise and
puts it beside what the player's game reports:
- **XP, levels, perk points and perks.** What explains a rise is the XP the game awarded (the player's game counts each award it
makes and tells the server) at the server's XP rate - `0` until a mode turns XP on with
[`SetXPRate`](/lua/server/functions/setxprate/), so on a server that never does, any rise is unexplained - what the game mode
gave ([`AddPlayerSkillXP`](/lua/server/functions/addplayerskillxp/),
[`SetPlayerStat`](/lua/server/functions/setplayerstat/), [`SetPlayerProgress`](/lua/server/functions/setplayerprogress/)
... - every order of the mode's counts as the character's from the moment it is sent), the potions and perks that multiply XP,
the perk points the levels pay (one for every even level a stat, a skill or Warfare reaches) and the perks the player bought
with them. The XP each level takes is the game's own table, which the server holds. A rise nothing explains is an *excess*.
- **Nourishment and energy.** Both fall steadily with the game's clock - about two points for every game hour a character
stands still, each - so the server judges them on its own game clock (`[world] time_ratio`), never the player's calendar. A value
above the game's maximum (150 and 100), one that stands exactly still for an hour and a half of game time or more (six minutes at
the default ratio), one that falls far slower than the clock says, or one that rises with no food, drink or bed the server saw
looks like a trainer's. A meal or a potion, the game's own digestion buffs, a change of the server's clock and a mode's own set void
the stretch they touch, so an honest player is never judged across them; an open game menu (the inventory, the perks screen) all
but stops the game's digestion while its clock runs on, and the server cannot see menus yet, so a long flat stretch can be an
honest player's and is only logged; a night in a bed gives rest the check cannot tell from a rise yet, so it is only counted. A
server whose clock is frozen or runs faster than sixty game seconds a second is not judged for these two at all. With `enforce`, once the nudge is measured (below), the server may change a value that stands perfectly still
by one point to see whether it is written back: at most six times for each of the two, kept through a reconnect for six hours, never
below six points and never while a meal holds the value at its maximum.
- **Carry weight.** The server adds up the weight of what its item records say a player carries (what they wear counts, as it does
for the game; the money is left out, so it is meant to be at or under the game's own figure) and bounds what they may carry from
their strength, perks, potions and drinks - a drink lifts a player's strength for a while, and the server allows for that for at
least ten minutes after it ([`GetPlayerWeight`](/lua/server/functions/getplayerweight/)). The game never stops a player from
picking things up; being over the limit only holds them back - no sprinting and no mounting a horse, while a jump is still allowed
and so never judged - so the check watches whether an overloaded player does what the game forbids, and whether a player's game
reports a capacity the strength cannot give. **Nothing is ever taken from a heavy player.** A horse's saddlebags are compared with
the game's own limit too.
With `mode = "observe"` (the default) the server judges everything and logs what it would do - `would set back 340 XP` - and asks
the game mode ([`OnPlayerProgressViolation`](/lua/server/callbacks/onplayerprogressviolation/)) for the XP and level it
would put back, but sends nothing to a player's game for the audit: no put-back, no probe, no nudge. **The record a game mode
reads and saves still follows what the game reports**, so a forged value would be the one saved: a mode that keeps characters
should not rely on the record until the server runs `enforce`. With `mode = "enforce"` an excess is put back with an exact set
after a second look at fresh evidence: the player's game holds what the server can explain, and so does the record a game mode
reads and saves ([`GetPlayerProgress`](/lua/server/functions/getplayerprogress/)) - a forged value is never the one that is
saved, and a level that was put back is never announced ([`OnPlayerLevelUp`](/lua/server/callbacks/onplayerlevelup/)). The
mode is asked first and may vouch (below). A game that writes the same forged value back again and again, three times within ten
minutes, is cheating beyond doubt: a proven `progress_write_repeat` (never on a server with story quests on), and `action` follows
it as for the item check; it needs `enforce`, since only a put-back can be written back. Only a rise that matters counts toward the
three: an excess of at least 25 XP, or of 2 or more perk points or a perk - a smaller one is never proof. A value that keeps coming back
is put back four times in the first half minute and then once a minute for as long as the player's game holds it, so with
`action = "log"` it is gone within a minute of the trainer letting go.
**Which rules act.** A rule is measured against real servers before it acts, and this version ships with only the measured ones.
Under `enforce` it puts back:
- an XP that is over what its level takes, and a level over 30 - a state no character is ever in;
- a clear XP or level rise: 300 XP or more that nothing explains, and over three times what the server can explain of the rise or
three levels or more above it - a trainer's level 30, a skill written up by a dozen levels - and a game that joins with that much
more than a fresh character holds. Not on a server with story quests on, where the story's lessons and scripts hand out levels the
server never sees (vouch for them there, or run `observe`). A smaller rise is only logged, as below;
- perk points of a stat, a skill or Warfare that its levels (and what the mode gave) do not pay for, with the perks bought from
them counted as points - the newest perks bought with points nothing paid for go with them. The perks list of the tables export
is how the check tells a bought perk or a respec's refund from a gift, and without it a respec that returns points reads as
points nothing explains;
- a perk bought below the level it asks - a perk of a level the server put back is one. A level that reaches the server a moment
after the perk keeps it;
- a nourishment above 150 or an energy above 100, and a mode's own `SetPlayerNourishment` / `SetPlayerEnergy` above them is
trimmed to them.
The rest is judged, counted and logged as "would ..." and not acted on, under `enforce` as well: a smaller XP rise a stat or skill
gained against what the game awarded, the pool of perk points of the main level, a game that joins with a little more than a
fresh character holds, the nudge, a drain that is too slow, a rise with no meal or bed, and the weight. The server's start-up lines
under `enforce` name every rule that does not act yet. The proven repeat of a
frozen nourishment or energy (`vitals_write_repeat`) needs those rules, so it does not occur in this version. Read the log for a
while, as you would for the items, before you count on a rule to do more; a later version switches each one on when it has been
measured.
**What a game mode hears.** [`OnPlayerCheatDetected`](/lua/server/callbacks/onplayercheatdetected/) gets the proven
`progress_write_repeat` and, as hints that are evidence for a rule of the mode's own and never a reason to kick by themselves,
`progress` (a rise nothing explains), `progress_ack` (the player's game does not confirm the server's orders), `progress_gate` (the
player's game says its own XP counting is not running), `vitals` (a value over the game's maximum), `vitals_rewrite` (a value put
back to its maximum and written up again) and `code_guarded` (an edit of the game's code inside a function these checks rely on).
Of the `weight` hints the mode hears a player's game reporting a carry capacity above what their strength, perks, potions and
drinks allow - a trainer's capacity; the others (sprinting or mounting while overloaded, a hidden carried weight, a horse's
saddlebags) and the hints about a drain that is too slow wait for their measurements: they are counted and logged and not told to
the mode yet. Each hint reaches the mode and the log at most once every ten seconds and thirty times an hour per player and
kind, and the check itself lets through one for each record every ten minutes; the rest are only counted.
[`GetPlayerProgressAudit`](/lua/server/functions/getplayerprogressaudit/) has the counts for a mode that wants to decide for
itself - its `gate_down`, read in [`OnPlayerProgressViolation`](/lua/server/callbacks/onplayerprogressviolation/), is how a
mode treats a large excess from a game whose XP counting is not running as the trainer it most likely is.
**What the check compares against.**
- *A player who comes back* within six hours (by name and address) is compared with what the server counted before, so XP the game
awarded in between - offline, or on another server - is explained and a value edited in between is not. A player the server has no
history of - after a server restart, or a game that came from another server - starts from what their game reports, with every
XP potion that applies assumed to be running for as long as it lasts.
- *A game that has just started* is compared with what a fresh character holds - in this version for the log only.
- *A game update* changes the game's tables: the server's XP table was compared with the game of its own release, and on a newer
game build the crossing of a level is judged as `observe` would until a server update knows the new build. Export the game's
tables again after an update.
- *An order the player's game did not confirm* is sent again after about 45 seconds, in `observe` as well; `progress_ack` says so.
The game confirms an order by its number in its next report.
**What an honest player can lose under `enforce`** - the whole list. The rules that act in this version put back nothing an honest
character reaches except as noted; the first three cases are what the XP rule will bring when it acts:
1. **A game whose XP counting could not start** (the log says `progress_gate`): the XP it earns is not seen being earned, so it is
put back for the visit. The cause is on the player's side, and the fix too.
2. **A server with story quests on** ([`SetStoryQuests`](/lua/server/functions/setstoryquests/)): a quest, a lesson or a script
gives XP and levels the server never sees awarded, so they are put back too - unless the game mode vouches for them in
[`OnPlayerProgressViolation`](/lua/server/callbacks/onplayerprogressviolation/) (return `false`). A story server that
switches to `enforce` must do that; nothing is ever proven against a player on a story server.
3. **Progress a game mode gives in a way the server cannot see** - a teacher of its own, an event's reward - for the same reason and
with the same remedy.
4. **The moment between the server noticing an excess and putting it back**: XP earned in that second is overwritten - and a game
mode's own exact set that arrives while a player is earning XP overwrites that award as well.
5. **Perk points of a respec** on a server without the perks list in its tables export, as above.
A network blip, a server restart or an order the player's game dropped loses nothing: the server remembers what it counted, or takes
the game's report as the starting point, and sends again an order that was not confirmed.
## [mods]
The game's own mods a player's game may run: the Steam Workshop items and the folders of the game's `Mods` folder it loaded.
The player's game reports each one with its id, name and version (from its `mod.manifest`) when it joins; a refused one
turns the player away with the mod's name, and the game mode reads the list with
[`GetPlayerMods`](/lua/server/functions/getplayermods/). The list is the player's own game's word - a modified game can
leave a mod out - so the rules that matter (speed, damage, the inventory) stay the server's checks.
| Key | Default | |
|---|---|---|
| `policy` | `"any"` | `any`: every mod; `allow_list`: only the ones in `allowed`; `none`: no mods at all |
| `allowed` | `[]` | with `allow_list`: the mod ids a player may run, e.g. `["smithsreach"]` |
| `refused` | `[]` | mod ids never let in, whatever the policy |
## [combat]
The model is the [Combat guide](/lua/server/combat/).
| Key | Default | |
|---|---|---|
| `pvp` | `true` | `false`: players cannot lock on to or hurt each other |
| `respawn_seconds` | `10` | a dead player stands up again at their spawn after this; `0` = the mode calls `SpawnPlayer`. The respawn waits (30 s at most) while someone is searching the body |
| `loot_bodies` | `"all"` | what a dead player's body gives up when someone searches it: `"all"` = everything they carry and wear, and their purse; `"carried"` = the same without what they wear; `"none"` = bodies cannot be searched. The list is what the server's records say the dead player carried; whatever the search takes is taken out of the dead player's game item by item, and the searcher is credited only with what that game really gave up (the few items the game refuses to delete, quest items, are left alone). [`OnPlayerLootBody`](/lua/server/callbacks/onplayerlootbody/) can refuse a search |
| `corpse_seconds` | `0` | how long the body stays where it fell after the respawn; `0` = it goes with the respawn. At most `3600`. A body can be carried (see [the carrying guide](/guides/carrying-players/)); at most two of one player's, and a quarter of `max_entities` in all, stay at once - a new one past a limit takes the oldest one's place. The body holds nothing and the player comes back with everything nobody took - a game mode that wants it otherwise fills it in [`OnPlayerCorpse`](/lua/server/callbacks/onplayercorpse/) |
| `max_reach` | `4.0` | metres the attacker and the target may be apart for a claim to count |
| `fight_timeout` | `30.0` | seconds without a blow that end a fight; `0` = never by time |
| `claim_window_ms` | `1500` | a hit counts only this long after the attacker's swing; `0` = always |
| `damage_scale` | `1.0` | a multiplier on every hit |
| `max_claim_damage` | `80.0` | without the tables, a claim above this is clamped |
| `tables` | `"data/combat/items.json"` | the game's tables: weapons, armour, the item, soul, buff and mesh catalogues, and the loot tables the chests are filled from (`[containers]`) - the file `tools/KcdMp.ExportTables.exe` writes from your own copy of the game ([Exporting the game's tables](/guides/game-tables/)); `""` (the value without a `server.toml`) = no tables: the damage the clients report, clamped by `max_claim_damage`, the catalogues by GUID only, every chest empty |
| `claimed_buffs` | `[]` | the buff classes the server takes over ([buffs](/reference/buffs/), [`ClaimBuffClasses`](/lua/server/functions/claimbuffclasses/)) |
| `potion_speed` | `2.0` | a potion's regeneration runs this many times faster than the game's own |
| `fall_damage` | `1.0` | the game's own damage for a fall times this; a deadly fall is a death like any other (the notice, the respawn); `0` = falls hurt nobody |
| `alcohol_per_content` | `0.01` | a drink's alcohol content × this on the blood-alcohol level |
| `alcohol_decay` | `0.00125` | taken off the level per second |
| `drunk_threshold` | `0.32` | drunk from here up; sober again from half of it down |
| `hangover_seconds` | `120.0` | the hangover after sobering |
| `attack_scale` | `0.32` | a weapon's table attack × this = its damage before armour |
| `ranged_scale` | `0.0077` | an ammunition's attack × the launch speed × this = a projectile's damage before armour |
| `armour_pivot` | `50.0` | the armour on the struck part that halves a hit (`d / (d + pivot)`, 85 % at most) |
| `fist_damage` | `6.0` | bare-handed, before armour |
| `body_parts` | `""` | rewrites of the claim's body part, `"claim=part,..."`; normally empty |
| `max_stamina` | `100.0` | |
| `stamina_regen` | `15.0` | per second standing, three quarters of it walking or running (as in the game), after 1.5 s without a cost; halved with an injured head or torso |
| `attack_stamina_cost` | `10.0` | a swing (× 1.2 slash or smash, × 0.5 bare-handed, × 1.25 with an injured arm) |
| `hit_stamina_damage` | `12.0` | a hit taken |
| `shot_stamina` | `15.0` | a shot |
| `shot_interval_ms` | `400` | shots closer than this are refused |
| `shot_window_seconds` | `6.0` | a ranged hit counts within the flight time plus this |
| `block_stamina_cost` | `10.0` | a swing blocked |
| `perfect_block` | `true` | perfect blocks and master strikes count: a perfect-blocked swing does no harm, a master strike cuts back; `false` ignores both |
| `parry_window_ms` | `500` | at most this long a landed blow on a player whose game judges no blows (a bot) waits for their perfect block or block (their round trip decides, a little longer while their block is raised; `0` = no wait, at most `2000`) |
| `counter_parry_window_ms` | `700` | the same for a counter after a perfect block, which its victim sees and parries a moment later than a plain swing (`0` = as `parry_window_ms`; at most `2000`) |
| `perfect_block_stamina` | `15.0` | taken from the attacker whose swing was perfect-blocked (the defender pays nothing) |
| `victim_blocks` | `true` | the victim's own game judges every melee blow between two players: a blow their game blocked or parried does no harm, whatever the attacker's screen showed a moment earlier (`hit_rule` says what counts as a touch); `false` takes the attacker's word |
| `verdict_window_ms` | `1500` | at most this long a blow the attacker saw land waits for the victim's game to judge it (their copy of the swing may start a little late on their screen; a blow that is judged lands at once); `0` = no wait, at most `3000` |
| `hit_rule` | `"contact"` | which screen's touch makes a melee blow between two players count. `"contact"`: a blow counts when it touched on either player's screen - the attacker's or the defender's - unless the defender's game blocked or parried it in time (a blow the defender's game never answered counts too, unless the server saw their block up as it arrived); the two screens never agree exactly where everyone is, and this way a blow that clearly connected for the attacker is not lost. `"victim"`: a blow counts only when it touched on the defender's screen (`unanswered_blow` and `unshown_blow` decide the rest) |
| `defense_grace_ms` | `200` | with `"contact"`: the most latency a block or parry is credited for. The defender sees each swing a little late - about the two players' average ping; past this, a block counts only if it was raised that much earlier, and a perfect block or master strike counts as a plain block. Players under about 150 ms ping (or one high-ping player against a low-ping one) never feel it; `0` = no limit, at most `1000` |
| `trades` | `"first"` | two players swinging at each other at the same time. `"first"`: as in single player, the first blow to land stops the other player's swing if it had not landed yet - that blow does nothing, whatever its own screen showed. `"both"`: every blow that touched counts, so both players take the hit |
| `trade_tolerance_ms` | `60` | with `trades = "first"`: two blows this close together both land - single player's "same moment", widened a little for the players' clocks; at most `500` |
| `dodge_check` | `true` | a blow that touched only on the attacker's screen (the defender's game showed no touch) misses when the defender had already stepped clear of the blade at that moment - the attacker's screen shows the other player a moment in the past, so a step or a dodge away may not have reached it yet |
| `dodge_margin_m` | `1.5` | how far from the blade the defender must have been, in metres along the ground (`0.2` to `5`) |
| `dodge_allowance_ms` | `150` | the most a defender is credited for their reaction when both players' pings are high (they see each swing later); `0` at ordinary pings, at most `500` |
| `fair_ping_ms` | `200` | the most ping the fights make up for. Up to it, every blow, block and dodge counts at the moment the player really made it. Past it, the extra lag counts against that player: half of what their ping is over the limit is added to each blow, swing and dodge of theirs, and their blocks are excused that much less. A player with a bad connection is never at an advantage, at worst at a small disadvantage. `0` = no limit, at most `2000` |
| `unanswered_blow` | `"miss"` | with `hit_rule = "victim"`: a blow the victim's game never judged in that time: `"miss"` (their game never showed it reach them) or `"land"` (the attacker's word) |
| `unshown_blow` | `"miss"` | with `hit_rule = "victim"`: a blow whose swing the victim's game says it never showed at all (the other player's figure was busy or not there on their screen): `"miss"` or `"land"` |
| `attacker_preview` | `false` | `true`: the attacker's screen shows a hit the moment their blade touches the other player (a guess - some will do no damage); `false`: it shows the defender's verdict - the hit, the clash or nothing - a moment later |
| `master_strike_scale` | `1.5` | a master strike's damage against a plain swing of the same weapon |
| `master_strike` | `true` | players may master-strike (and disarm each other); `false` takes the move away - their games offer only the plain perfect block |
| `clinch` | `true` | two players may clinch: their blades lock, the one who leads strikes inside the clinch and the other may counter, on every screen at once; `false` never pairs them (they fight on) |
| `clinch_scale` | `0.6` | a clinch blow's damage against a plain swing of the same weapon |
| `clinch_parry_window_ms` | `1300` | at most this long a clinch blow waits for its victim's counter inside the clinch (their round trip decides; `0` = no wait, at most `3000`) |
| `sprint_stamina_cost` | `5.0` | per second at a sprint - the game's own |
| `jump_stamina_cost` | `9.0` | per jump - the game's own |
| `injury_threshold` | `15.0` | a hit of at least this injures the part struck |
| `bleed_per_damage` | `0.015` | a stab or a slash that gets through the armour opens a wound bleeding this × the damage in health per second (`0` = no bleeding) |
| `max_bleed` | `1.0` | health per second at most, all open wounds together |
| `bleed_seconds` | `40.0` | how long each wound bleeds (a new wound does not restart the others) |
Heavy armour holds cuts back: a cut bleeds only when the armour on the struck part let at least half of it through, else every
third cut it held opens a wound anyway. A game mode can change all of this, and decide any single blow, with
[`SetBleedRules`](/lua/server/functions/setbleedrules/).
## [ui]
What the players' screens show of each other, drawn in the game's own UI. A player can hide either on their own client
(`KcdMp_nameplates 0`, `KcdMp_health_bars 0` in the console); the server's `false` hides it for everyone.
| Key | Default | |
|---|---|---|
| `nameplates` | `true` | a name over every player's head, in the game's typeface, smaller and fainter with the distance; `"party"` shows the party members' alone, `false` nobody's; a game mode's `SetPlayerNameplate` / `SetPlayerColor` still change the text and the color |
| `health_bars` | `true` | a health bar under the name, following the server's word on that player's health; `"party"` or `false` like the plates |
| `through_walls` | `false` | `true` shows the plates through buildings and doors; the default hides one whose player or NPC actor is out of sight - the NPC actors' names follow the same rule |
| `range` | `50` | metres a plate shows up to; it fades out over the last third. A game mode can give the NPC actors' names a range of their own (the `nameplates` option `npc_range`, [Change the interface](/guides/interface/#nameplates)) |
| `injuries` | `false` | `true` adds six marks under the bar, lit red for the body parts the server knows as injured (head, torso, arms, legs) |
| `party_color` | `""` | a color like `"80E080"` for a party member's name; `""` keeps the game's own gold |
| `team_color` | `""` | the same for a teammate's name (`SetPlayerTeam`); a game mode's `SetPlayerColor` beats both |
| `party_compass` | `true` | the party members as blips on the game's compass, at their bearing, at any distance |
| `chat_bubbles` | `true` | a player's chat line over their head for a few seconds, in the game's speech bubble; a player can turn it off on their own client (`KcdMp_chat_bubbles 0`) |
| `map_fog` | `false` | `true` keeps the game's fog of war over the parts of the world map a player has not walked; `false` shows every player the whole map from the start. A game mode lifts the fog for one player with `RevealPlayerMap` |
## [voice]
The proximity voice chat: a player's voice reaches whoever stands near them and plays from where they stand, through the
game's own 3D sound, fading with the distance. The server relays each voice frame the moment it arrives, only to the players
within range; a game mode changes the range at run time (`SetVoiceRange`), gives one player their own (`SetPlayerVoiceRange`),
mutes a player for everyone (`SetPlayerVoiceMuted`) or for one listener (`SetPlayerVoiceMutedFor`, what a player's own
`/mute ` does) or deafens one (`SetPlayerVoiceDeaf`), and hears `OnPlayerVoiceStart` / `OnPlayerVoiceStop`. Players choose their own microphone, push-to-talk key and mode in the launcher's Options.
| Key | Default | |
|---|---|---|
| `enabled` | `true` | `false` = no voice chat on this server (the clients are told at the handshake) |
| `range` | `25` | metres a voice carries; a voice is at full level within the first metre and fades to silence at the range |
| `max_bytes` | `400` | the largest voice frame relayed; 400 covers the highest quality a client sends |
## [party]
The groups the server keeps for the game mode's own commands ([the parties guide](/lua/server/parties/)): the
invitation, the frames on the members' screens, the no-friendly-fire rule. The commands themselves - `/invite`, `/accept`,
`/p` - are the mode's; the shipped freeroam mode has them.
| Key | Default | |
|---|---|---|
| `max_size` | `5` | members per party (at least 2) |
| `invite_timeout` | `30` | seconds until an unanswered invitation runs out |
| `frames` | `true` | the party frames on every player's screen; a mode can switch them per player ([`ShowPartyFrames`](/lua/server/functions/showpartyframes/), or the `party` component of [Change the interface](/guides/interface/)) |
| `leader_leaves` | `"next"` | what the leader's leaving does: `"next"` hands the lead to the next member in join order, `"disband"` ends the party |
| `friendly_fire` | `false` | `true` lets members hurt each other; off, a hit between members is refused before the mode hears of it, like one between teammates |
## [emotes]
The gestures players make for everyone around: the wheel on **G** (hold it, point, let go; a tap leaves it open for a
click; the number keys pick directly) and the point held on **T** (the arm stays out while T is down, walking allowed).
The server checks every pick - the player alive, standing still on foot (the point goes with a walk), no weapon out, not
fighting - and ends a looping one when the player moves. A chat command for them is the
game mode's (the shipped modes have `/anim`; [the Lua group](/lua/server/#emotes) shows how).
| Key | Default | |
|---|---|---|
| `enabled` | `true` | `false` = no wheel and no point on T (G and T stay the game's own); a game mode's [`PlayPlayerEmote`](/lua/server/functions/playplayeremote/) - its `/anim` - still plays one |
| `wheel` | `["wave", "bow", "cheer", "clap", "dance", "come"]` | the wheel's emotes, clockwise from the top, at most eight; `[]` = no wheel, and **G** and **T** stay the game's own. The other built-ins are `salute`, `nod`, `sit`, `armscrossed`, `drink` and `point` (T's) |
`[emotes.custom]` adds your own, one key per emote: the name (1-24 of `a-z`, `0-9`, `_`, `-`) `= "Label | clip | fragment | tags | loop"`.
The label is what the wheel shows; the clip is what the others see the body play ([the animation lists](/reference/animations/));
the fragment and its tags are what the player's own game plays (the fragment families of the same lists; the tags may be
empty); the fifth part is `loop` (until the player moves) or `once`, the default, and may add `upper` (the upper body alone:
it plays while the player walks - looped with `loop`, the hands tied behind the back; no fragment needed) and `locked` (only a
game mode ends it). A sixth and a seventh part are the **in** and the **out**, each `clip fragment tags`: played once before
the loop and once when it ends - sitting down and standing up. A custom emote with a built-in's name replaces it.
```toml
[emotes]
wheel = ["wave", "bow", "cheer", "clap", "dance", "come", "kneel", "sit"]
[emotes.custom]
kneel = "Kneel | pray_kneel_loop | PrayKneeling | | loop"
rest = "Rest | resting_ground_idle_01 | Lying | lyingGround+var1 | loop | resting_ground_in_01 LieDown liePlaceGround+lieVar01 | resting_ground_out_01 GetUp lyingGround+var1"
cuffed = "Cuffed | hands_tied_back_idle | | | loop upper locked"
```
## [carts]
Horse-drawn wagons and two-wheeled carts. A **route** is a road the server drives its carts along at a steady pace - a loop, or there and back
with a turn at each end - with stops where they wait; a wagoner sits on the bench and players ride along in the back. A
**free cart** stands where it was put until a player takes the reins at its bench and drives it themselves (**W** / **S**
the pace, **A** / **D** to steer, **Shift** held for the canter). Players get in and out with the game's own prompt and
dismount key. Routes are recorded in the game by walking or riding the road ([the routes guide](/guides/cart-routes/));
a game mode can make its own with [`AddCartRoute`](/lua/server/functions/addcartroute/), put free carts anywhere
with [`CreateCart`](/lua/server/functions/createcart/) and decide what each is made of - the body, the wheels, the load,
the horses ([personal carts](/guides/personal-carts/)).
| Key | Default | |
|---|---|---|
| `enabled` | `true` | `false` = no carts on this server (no routes run, `CreateCart` makes none) |
| `routes` | `"data/carts/{level}.toml"` | the routes file, read at the start - `{level}` is the level's name; `/cartroute save` writes it; `""` = no file |
| `speed` | `7.0` | metres a second a route's carts travel at when the route names no speed of its own (7 is a horse's canter; at most 15) |
| `drive_speed` | `7.0` | metres a second at most for a cart a player drives ([`SetCartSpeed`](/lua/server/functions/setcartspeed/) sets one cart's own) |
| `driver_clothing` | `"24e4aa5b-cd2c-4dba-9426-b63e674b7037"` | the clothing preset of the wagoners on the routes ([the clothing presets](/reference/clothing-presets/)) |
## [effects]
| Key | Default | |
|---|---|---|
| `range` | `150.0` | metres from the point within which players get a mode's effect, decal or sound ([`SpawnEffect`](/lua/server/functions/spawneffect/), [`SpawnDecal`](/lua/server/functions/spawndecal/), [`PlaySound`](/lua/server/functions/playsound/)); `0` = everyone in that virtual world |
## [actors]
How an [NPC actor](/lua/server/#npc-actors) fights back, blocks and gets through doors; its wounds follow the `[combat]` injury and bleeding keys like a player's.
| Key | Default | |
|---|---|---|
| `fight_back` | `true` | a blow on an actor makes the attacker its opponent |
| `swing_interval` | `1.8` | seconds between its swings |
| `max_attackers` | `0` | `0` = every actor in reach of a player swings at them; a number makes the actors on one player take turns - at most that many swing at the player within a `swing_interval`, the others stand at their reach and wait their turn, the longest waiting first (`3` is close to how the game's own crowds fight). The waiting actors stay in the fight |
| `reach` | `2.2` | metres it swings from; an unarmed actor closes to 0.7 of it |
| `chase_speed` | `2.0` | metres per second after its opponent |
| `chase_range` | `30.0` | an opponent farther than this is given up |
| `attack_scale` | `1.0` | a multiplier on the blow the victim's game computed |
| `max_damage` | `60.0` | a blow does at most this |
| `block_chance` | `0.5` | how often an armed actor raises its guard against a swing; `0` never, `1` every time |
| `block_hold` | `0.7` | seconds the guard stays up |
| `drop_weapons` | `true` | a dying actor's held weapon and shield fall as pickups everyone can take (its preset's first melee weapon and first shield); `false` = it drops nothing |
| `open_doors` | `true` | a walking or chasing actor opens the closed doors in its way, for everyone; a locked door ([`SetDoorState`](/lua/server/functions/setdoorstate/)) stops it there - a walk ends at the door ([`OnActorArrive`](/lua/server/callbacks/onactorarrive/)), a chase waits at it. Needs the tables file (`[combat] tables`), which lists the level's doors; a file exported by an older `KcdMp.ExportTables` does not know which way the doors face, so an actor also opens a door it only walks past - export it again. `false` = actors walk through closed doors |
## [spawn]
| Key | Default | |
|---|---|---|
| `x`, `y`, `z`, `yaw` | `1280.0`, `1088.0`, `-1.0`, `0.0` | the default spawn ([`GetDefaultSpawn`](/lua/server/functions/getdefaultspawn/)) of a level without its own row; `z` below `0` = on the terrain |
| `klaster`, `trosecko`, `kutnohorsko` | `kutnohorsko = [752.0, 3346.0, 142.3, 0.0]` | a level's own spawn as `[x, y, z, yaw]`, used over the four keys above when the server runs that level (`--spawn` on the command line beats both); Kuttenberg's default is the inner yard of the Suchdol fortress, facing its gate |
| `horse_soul` | `Horse2`'s GUID | the breed of horses made without one ([horses](/reference/souls/horse/); a name or an id works too) |
| `clothing_preset` | `""` | the outfit players spawn in ([clothing presets](/reference/clothing-presets/)); `""` = they start naked - a game mode dresses them with [`SetPlayerInventory`](/lua/server/functions/setplayerinventory/) or `GivePlayerItem`. The bag is emptied (the purse the server holds for them stays) and the outfit put on at a player's first spawn of a visit and whenever a spawn's two presets differ from the spawn before ([`SetSpawnInfo`](/lua/server/functions/setspawninfo/)); a respawn with the same presets keeps what they carry |
| `weapon_preset` | `""` | their weapons ([weapon presets](/reference/weapon-presets/)), with the number of each item the preset gives; `""` = none |
| `items` | `["torch_weapon"]` | given at a player's first spawn of a session, on top of the presets ([items](/reference/items/)) |
| `stat_level` | `0` | strength, agility, vitality and speech raised to at least this at every spawn - the game announces every rise, and a level is never lowered; `0` = the level's own 5, nothing announced. A game mode that keeps its players' levels sets them exactly, lower too, with [`SetPlayerStat`](/lua/server/functions/setplayerstat/) and leaves this at `0` |
| `appearances` | thirteen generic men | the souls whose faces players get, one each by a hash of the name, unless the mode sets one ([souls](/reference/souls/), [`SetSpawnInfo`](/lua/server/functions/setspawninfo/)) |
# Guides
> One job per page, for any scripting language - exporting the game's tables, the terrain, the navigation mesh and the collision geometry from your own copy of the game, putting a server on the public list, connecting a database for the game mode, cart routes, building on the server's map and using its in-game editor, players' own carts, a character creation screen, knocking a player out and carrying them, picking pockets, throwing riders and pulling them from the saddle, keeping what a player discovered, keeping a character's stats, skills and perks, changing how the chat, the scoreboard and the rest of the interface look, an interface of your own made of web pages, and playing on Linux and Steam Deck.
Each guide is one job, start to finish, and holds whatever language the game mode is written in. The
[setting-up page](/getting-started/setting-up/) says where each fits.
## The game's data
The server does not have the game. It knows what the players' games report, but not what a sword does against a
brigandine, where the ground is, where the walls are or how to walk around them. The files below are the parts of the game
a server needs for that, **exported from your own copy of the game** with the tools in the Windows server folder's
`tools/`. They are never shipped with the server - they are the game's own data, and every server owner has the game - so
each owner makes them once and puts them under `data/`; a server on Linux gets the files copied over. Without a file the
server runs exactly as before: each one turns on one thing, and the server log says at start which it found.
| Guide | File | What it turns on |
|---|---|---|
| [Exporting the game's tables](/guides/game-tables/) | `data/combat/items.json` | damage from the game's own weapon and armour tables; items, souls, buffs and meshes by name - `/give shortswordBroad`, a horse breed by name, the potions and buffs the server owns; the audit's item check, which is passive without it |
| [Exporting the terrain](/guides/terrain/) | `data/heightmaps/.hmap` | the terrain check - a player far under or over the ground is pulled back - and the terrain height for a game mode |
| [Exporting the navigation mesh](/guides/navigation/) | `data/navmesh/.knav` | NPC actors that walk around walls instead of into them; paths, reachability, floors and the nearest walkable spot for a game mode |
| [Exporting the collision geometry](/guides/collision/) | `data/collision/.kcol` | line of sight on hits and shots, a step through a wall caught, the floor under a player as the ground; rays for a game mode |
The three level files are one per level per game build: a server that runs `klaster` needs `klaster.hmap`,
`klaster.knav`, `klaster.kcol`, and after a game update they are exported again. The tools do all three levels by
default, so the files are there whichever level the server switches to.
## Running a server
| Guide | |
|---|---|
| [Putting a server on the public list](/guides/public-server/) | the port, the address the list should show, the name and the password, the details players see in the launcher (a description, your website and Discord), and a Join button for your website |
| [Connecting a database](/guides/databases/) | a MySQL / MariaDB or PostgreSQL database for the game mode's own accounts, factions and scores - the server, the user, the `[gamemode.database]` block, and the rules that keep it safe and fast |
| [Cart routes](/guides/cart-routes/) | horse-drawn carts that travel your roads by themselves - recording a route in the game by riding it, its stops, how many carts, how fast and what they look like |
| [Building on the map](/guides/map-editor/) | the server's own map of the level: the game's buildings and props placed anywhere and stretched, parts of the level taken away, areas, lights, doors and use prompts - built in the game's own world with the in-game editor; layers, prefabs, sharing a map with another server, building plots for players |
| [Using the map editor](/guides/using-the-map-editor/) | the in-game editor step by step: flying, the panel, placing and changing things, your own pictures and pages, taking parts of the level away, areas, navigation, layers, prefabs, building with other admins, how your work is saved and moved from a test server to the live one |
## Building a game mode
| Guide | |
|---|---|
| [Personal carts](/guides/personal-carts/) | carts players drive, own and do up - the seats, every part of a wagon (body, axle, wheels, load, the horses' coats), a cart dealer and a cartwright |
| [A character creation screen](/guides/character-creator/) | players make their character - the face, the hair, the beard, the skin - on a screen of the mode's own with a preview body only they see; the look kept with the account |
| [Change the interface](/guides/interface/) | move, resize or hide the chat, the scoreboard, the party frames, the name plates and the rest of what KCD:MP draws, for one player or for everyone - every component, drawer and option, in Lua and C# |
| [Make a web interface](/guides/web-interface/) | pages of your own - a menu, a shop, a login screen, your own chat, scoreboard or loading screen - drawn over the game by every player's game: the folder and its `ui.toml`, talking to the pages, the keyboard and the mouse, KCD:MP's own web pieces and their theme |
| [Knocking a player out and carrying them](/guides/carrying-players/) | take a player down without a death, carry them over a shoulder and put them down - the game's own grab and carry, seen by everyone - and let players carry the dead and the fallen themselves |
| [Picking pockets](/guides/pickpocketing/) | players rob each other with the game's own "Rob", its charge and its wheel - who may rob and be robbed, what the wheel offers, what moves, what a caught thief faces - and NPC actors with pockets of their own |
| [Throwing riders and pulling them from the saddle](/guides/riders-thrown/) | a rider thrown by a branch, a beam or their horse on every screen, a throw on the mode's order, the switches that keep a rider up, and players on foot pulling riders down with the game's take-down |
| [Keeping what a player discovered](/guides/saving-discoveries/) | the places a character found on the map and the books they read, saved in the mode's own database and put back at the next login |
| [Keeping a character's stats, skills and perks](/guides/saving-progress/) | the levels, experience, perk points, perks, nourishment and energy saved in the mode's own database and put back at the next login; how much experience players earn; a horse's own stats |
## Playing
| Guide | |
|---|---|
| [Playing on Linux and Steam Deck](/guides/playing-on-linux/) | KCD:MP through Steam's Proton - the launcher as a non-Steam game, the WebView2 component it needs, the game folder, and the logs to send when something goes wrong |
# Exporting the game's tables
> Making data/combat/items.json from your own copy of the game - the weapon and armour numbers behind every hit, and the item, soul, buff, consumable and mesh lists the server names things from.
The server judges every hit itself - the attacker's weapon against the armour on the part it struck - and resolves the
keys a game mode and the chat commands use: an item by name, a horse breed by name, a buff by its class. Both need the
game's own tables, which the server does not have. `data/combat/items.json` is that export: the weapon and armour numbers
and the lists of items, souls, buffs, consumables, weapon presets and prop meshes, read straight from the game's files.
With it:
- a hit does what the game's tables say it does (`[combat] attack_scale`, `armour_pivot` and the rest scale it -
[the combat model](/lua/server/combat/));
- items, souls, buffs and meshes answer to their **names** and ids as well as their GUIDs - `/give shortswordBroad`,
`/horse Horse2`, `GivePlayerBuff(pid, "potion_marigold_decoction")`, `/prop barrel_a` - with the English names too
(`/give duelling longsword`);
- the server owns potions, drink and the buffs it claims, and knows what each consumable does;
- **chests hold what the game would put in them**: the export carries the game's loot tables and, for each level, the
loot preset every chest is placed with and the loose item every slot on a table holds, so the server rolls a chest's
contents the way the single-player game does and restocks them on its own clock ([the `[containers]`
section](/getting-started/server-config/#containers); [`RollLoot`](/lua/server/functions/rollloot/) in a mode);
- the server knows **every door of the level** - where it stands and which way it faces: [`GetNearestDoor`](/lua/server/functions/getnearestdoor/)
in a mode, and the NPC characters open the doors they walk through (`[actors] open_doors`);
- the server knows **where each of the level's story layers stands** - the walls, fences and heaps the story shows and hides: an
admin finds the layer of whatever they stand at with `/layer near`, a mode with [`GetLevelLayersNear`](/lua/server/functions/getlevellayersnear/),
and when a mode switches one off (Suchdol's fence broken by a siege) the server's own collision leaves it out too
([the level's layers](/lua/server/#level-layers));
- **the audit can tell the items apart** ([the `[audit]` section](/getting-started/server-config/#audit)): the item list
says what each class is, which category it belongs to and how much it weighs, the weapon and clothing sets say exactly what a
spawn's outfit consists of - and how many of each item - the smithing recipes say what a player can forge at an anvil and from
which materials, and the perks and consumables lists are what the progression check understands perks, potions, drinks and
meals by.
Without it the server takes the damage the players' games report (clamped by `[combat] max_claim_damage`), knows
items, souls and buffs by their GUIDs only, and opens every chest empty. It says so at start: `combat tables: ... not
usable` or nothing at all. The item check goes **passive** (`item ledger: PASSIVE` in the log): it follows what the players'
games report and removes nothing, until the file is there; the carry-weight check has no weights, and the progression check
judges less of perks, potions and drinks.
**After updating the server, export again.** A file from an older version still loads, but it lacks the game's clothing sets and
the number of each item a weapon set gives (a bow set's arrows): the spawn's clothing is then credited by item category and a
stack of arrows as one stack, up to a hundred, and the log says so (`the clothing preset ... is not in the tables export`).
It also lists only one of the chests and table slots that share a name with others in the level (Kuttenberg and Trosky have
hundreds): the others open empty and their items are not the level's, and the log says to export again (`lists one stash and
one item slot of each name the level gives several`). A file without the smithing recipes counts any melee weapon or horseshoe
made at an anvil as forged without checking its materials, and the log says so at the start (`smithing: no anvil recipes in
this export`). A file from before 2026-10-06 knows no story layer's place: `/layer near` says to export again, and a layer a mode
switches off stays solid for the server. The same goes after a game update.
## Exporting it
On the machine that has the game, run `tools/KcdMp.ExportTables.exe` from the Windows server folder - double-click it,
or from a terminal. It finds the game through Steam, the Xbox app or the Epic Games Launcher (or asks for the folder in a dialog), reads the game's tables, the
English names, the object files and the three levels' own files, and writes `data/combat/items.json` into the server
folder in a few seconds; the last lines say what it counted and where the file went. The game does not start.
```
KcdMp.ExportTables [--game ] [--out ] [--no-names] [--no-meshes] [--voice-language ]
```
`--game` names the game's folder when neither Steam, the Xbox app nor the Epic Games Launcher does; `--out` writes elsewhere; `--no-names` leaves the English names out
(`/give` by English name stops working); `--no-meshes` skips the object files (a few seconds faster, no props by name;
the levels' chests, table slots and doors are still read); `--voice-language` picks the voice language the actors' spoken lines are read
from (`english` by default, else whichever voice language the game has installed - a warning says so; the lines are the same in every
language, only their lengths differ). Without any voice language installed the export has no spoken lines and says so: actors then stay
silent. `--help` prints the same.
## Installing it
The tool puts the file where the server reads it. For a server on **Linux**, copy it into that server's data folder as
`combat/items.json`. The configuration names it:
```toml
[combat]
tables = "data/combat/items.json"
```
The server log confirms it at start: `combat tables: data/combat/items.json (... weapons, ... armour pieces): damage from
the tables`.
## The ids
The reference lists on this site - [items](/reference/items/), [souls](/reference/souls/),
[buffs](/reference/buffs/), [meshes](/reference/meshes/) and the rest - are the same export of
the same game build, so their numeric **ids** are the ones your server resolves. An id is a row number of a name-sorted
export: the same for one game build, shifted by a game patch that adds things. Keep the **name** or the **GUID** in a
game mode and in anything else durable; use the id at the chat line.
One export per game build: after a game update, export again, and the server picks the new file up at its next start.
# Exporting the terrain
> Making data/heightmaps/.hmap from your own copy of the game - the level's ground heights that turn on the terrain check and GetTerrainHeight.
The terrain is the level's heightmap: the height of the ground at every point, without buildings. With it the server
refuses a position more than `[validation] max_below_terrain` metres under the ground (cellars and mines are allowed
for) or `max_above_terrain` over it (towers and cliffs), and pulls the client back to where it last stood - a fly hack or
a fall through the world ends there. A game mode gets [`GetTerrainHeight`](/lua/server/functions/getterrainheight/),
the ground under any point; a spawn or a teleport with `z` below `0` lands on it.
Without it the terrain check is off for that level and the function answers `nil`.
## Exporting it
The Windows server folder carries the tool for it: run `tools/KcdMp.ExportHeightmap.exe` on the machine that has the game
and the KCD:MP client (the game closed, and Steam running for the Steam copy). The terrain lives in the engine, so the tool **starts the game**
once per level through the KCD:MP launcher, samples the terrain when the level is loaded and closes it again - about a
minute per level; the files land in the server folder's `data/heightmaps/`. Leave the game alone while it runs.
```
KcdMp.ExportHeightmap [--level |all] [--step ] [--game ] [--client ]
```
All three levels by default; `--level klaster` does one. `--step 2` samples every 2 metres instead of 4 (a finer map, four
times the size). The game is found through Steam, the Xbox app's games, the Epic Games Launcher or `--game`; the KCD:MP client is looked for next to the tool, then
`--client`, then asked for in a dialog. `--help` prints the same.
By hand, from inside the game: stand in the world on the level, open the KCD:MP console (**F9**, where the server allows
it) and run `KcdMp_heightmap`. The file `heightmap-.hmap` lands in your KCD:MP data folder
(`%LOCALAPPDATA%\KcdMp`) in under a second; the client log there says so, with how many samples it holds. The default
samples every 4 metres over the level's 4096 × 4096 m (about 2 MB); `KcdMp_heightmap 2` samples every 2 metres,
`KcdMp_heightmap 4 0 0 8192 8192` covers a larger area.
## Installing it
The tool puts the files where the server reads them. For a file made by hand, or for a server on **Linux**, put it in
the server's data folder under `heightmaps/`, named after the level (`klaster.hmap`). That is where the server looks by
default:
```toml
[validation]
heightmap = "data/heightmaps/{level}.hmap" # the default; "" = never
max_below_terrain = 6.0
max_above_terrain = 60.0
```
`{level}` is replaced by the level the server runs, and the terrain check is on while that level's file is there - one
line serves every level you export. The server log confirms the map at start (`heightmap: ... samples every 4 m ...`)
or says it found no file, or why it could not use the one it found.
With the [collision geometry](/guides/collision/) as well, the floor under a player - a bridge, an upper storey - counts as the
ground, so `max_above_terrain` can be a storey's worth (`10`) rather than a tower's.
One heightmap per level per game build: after a game update, export it again.
# Exporting the navigation mesh
> Making data/navmesh/.knav from your own copy of the game - the game's own walkable surface, on which NPC actors walk around walls and a game mode asks for paths.
The navigation mesh is the game's own: the walkable surface of the level - every yard, road, floor, stair and bridge -
as polygons, the way the game's own people walk it. With it an NPC actor sent somewhere
([`MoveActor`](/lua/server/functions/moveactor/)) walks there around the walls instead of straight through them and
opens the doors on its way (`[actors] open_doors`), an actor chasing a player goes around the corner after them, and a
game mode can ask for a walk between two points
([`FindPath`](/lua/server/functions/findpath/)), whether one is reachable from the other
([`IsReachable`](/lua/server/functions/isreachable/)), where the floor is - indoors, on a bridge, where the terrain
says otherwise ([`GetNavmeshHeight`](/lua/server/functions/getnavmeshheight/)) - and the nearest walkable spot to a
point ([`NearestNavmeshPoint`](/lua/server/functions/nearestnavmeshpoint/)).
Without it the actors walk straight lines - the body stops at a wall and catches up when the line comes out - and the
functions answer `nil` / `false`; [`HasNavmesh`](/lua/server/functions/hasnavmesh/) tells a mode which it is.
## Exporting it
Run `tools/KcdMp.ExportNavmesh.exe` from the Windows server folder on a machine that has the game. It reads the mesh
straight from the level's own files - the game does not start, it takes a second per level - and writes the files into
the server folder's `data/navmesh/`.
```
KcdMp.ExportNavmesh [--level |all] [--variant ] [--game ] [--out ]
```
All three levels by default; `--level klaster` does one. The game keeps a mesh for the level as the story leaves it -
doors bricked up, a ruin cleared, a camp built - and the tool takes the state a fresh world starts in; `--variant `
picks another of the level's states, for a server that stages one (`-1` is the base mesh alone). The game is found
through Steam, the Xbox app's games, the Epic Games Launcher or `--game`; `--out` writes elsewhere. `--help` prints the same.
## Installing it
The tool puts the files where the server reads them. For a server on **Linux**, copy them into its data folder under
`navmesh/`, named after the level (`klaster.knav`). That is where the server looks by default:
```toml
[validation]
navmesh = "data/navmesh/{level}.knav" # the default; "" = never
```
`{level}` is replaced by the level the server runs, and the mesh is used while that level's file is there - nothing to
turn on. The server log confirms the mesh at start
(`navmesh: ... tiles, ... polygons ...: the actors walk paths, FindPath is live`) or says it found no file, or why it
could not use the one it found.
The mesh is held in memory while the server runs: a few MB for `klaster`, about 400 MB for `trosecko` and 600 MB for
`kutnohorsko`. On a small machine running a big level, `navmesh = ""` keeps it out (the actors then walk straight
lines).
Every path search is bounded by `navmesh_search_nodes` and `navmesh_search_ms` in the same section (16384 nodes and
100 ms by default). A place the mesh has cut off - a room whose door the game's mesh does not join, a ledge, a roof -
answers "no way" at once; a search that runs past the budget stops and answers "no way" as well. Both show in the log
with what asked and the two points:
```text
navmesh: the game mode's FindPath (3164.4 876.8 56.1 -> 3152.9 857.5 61.1): the end is on a part of the navmesh no way leads to (114 polygons) - no path
navmesh: the chase of actor 812 (...): stopped at the search budget (16384 nodes, 63 ms; ...) - no path
```
A line like the first one means the place itself cannot be walked to on the game's mesh: pick another spot, or join it
with a nav link ([`CreateNavLink`](/lua/server/functions/createnavlink/)). An NPC chasing someone it cannot reach asks
again every five seconds and walks straight at them meanwhile.
One mesh per level per game build: after a game update, export it again.
# Exporting the collision geometry
> Making data/collision/.kcol from your own copy of the game - the level's walls, roofs, floors and trees as the game's physics knows them, for line of sight, the true ground and a game mode's rays.
The collision geometry is the level as the game's own physics knows it: every wall, roof, floor, stair, fence, rock and
tree trunk, the way arrows and people bump into them. With it the server can shoot rays of its own:
- a blow or a shot claimed through a wall is caught - `[validation] line_of_sight` turns the refusal on; the log says
what it would refuse either way;
- a player walking through a wall or a closed door is caught the same way - `[validation] no_clip`: the step from the
last accepted position to the reported one is checked against the geometry and pulled back. Only what a walker bumps into
counts as a wall (the hanging goods of a stall, the books on a shelf and other small things the game lets a player walk
through do not), and a player who keeps walking on past a wall at a believable pace for a second and a half is followed
rather than held back for good - the game mode hears it as a `noclip` hint;
- the floor under a player - a bridge, a roof, an upper storey - counts as the ground of the
[terrain check](/guides/terrain/), so the allowance over the terrain can be a storey rather than a tower;
- a game mode gets [`RayCast`](/lua/server/functions/raycast/), [`IsLineOfSight`](/lua/server/functions/islineofsight/)
and [`GetGroundZ`](/lua/server/functions/getgroundz/) - a wall ahead, a shot's clearance, the floor to put a spawn on.
The doors the server knows are open let a ray through. Players, actors and horses are not part of it, and neither is
anything the game lets you push or knock over - crates, barrels, buckets, carts, a brazier: the server could not follow
them once moved, so the geometry holds the level as built. Standing on a barrel, `GetGroundZ` gives the floor under it.
Without it the rays answer nothing, no line of sight is checked and the ground is the terrain's;
[`HasCollision`](/lua/server/functions/hascollision/) tells a mode which it is.
## Exporting it
Run `tools/KcdMp.ExportCollision.exe` from the Windows server folder on a machine that has the game and the KCD:MP client
(the game closed, and Steam running for the Steam copy). The geometry lives in the running game, so the tool **starts the game** once per level
through the KCD:MP launcher: it loads the level, waits half a minute for the level's objects to settle, walks the physics
world in a few passes - seconds, on top of the game's own loading time, which is minutes on the big levels - and closes
by itself. The tool prints the progress; leave the game alone while it runs. The file lands in the server folder's
`data/collision/`.
```
KcdMp.ExportCollision [--level |all] [--wait ] [--game ] [--client ]
```
All three levels by default, one game run each; `--level klaster` does one. `--wait` changes the settling time (30 s by
default). The game is found through Steam, the Xbox app's games, the Epic Games Launcher or `--game`; the KCD:MP client is looked for next to the tool, then
`--client`, then asked for in a dialog. `--help` prints the same.
## Installing it
The tool puts the files where the server reads them. For a server on **Linux**, copy them into its data folder under
`collision/`, named after the level (`klaster.kcol`). That is where the server looks by default, and the geometry is used
while the level's file is there:
```toml
[validation]
collision = "data/collision/{level}.kcol" # the default; "" = never
line_of_sight = false # true: a hit or shot claimed through a wall is refused
no_clip = false # true: a step through a wall is pulled back
```
Both rules start off: the server logs what they would have refused, so an owner can watch a few sessions for false
alarms (a doorway the geometry has narrower than the game, a fence the horse takes) before turning them on.
The server log confirms the geometry at start (`collision: ... parts, ... triangles ...: RayCast and the ground are
live ...`) or says it found no file, or why it could not use the one it found. Loading a level takes under a second and 150-270 MB of memory (the files
are 11-21 MB; the monastery has 280 000 parts, the two big levels 100 000-300 000 parts and 1.2 million triangles
each).
One file per level per game build: after a game update, export it again.
# Putting a server on the public list
> What it takes for players to find and join your server - the port, the address the list should show, the name, the password and the details players see in the launcher, and a Join button for your website.
A server announces itself to the **public list** at kcd-mp.com once a minute, and every launcher's *Servers* tab shows
what the list has. Nothing has to be registered, but the list only shows a server players can join: when an
announcement comes in, the list asks the server what a launcher asks it - over its UDP port, at the address it would
list - and shows it once it answers, a few seconds after its first announcement. It keeps asking every half minute: a
server that stops answering is off the list within a minute and back as soon as it answers again, and one that stops
announcing is gone three minutes after its last announcement. A server whose own work stops (nobody could join it)
stops announcing too, and says so in its log. The settings are `[master]` and `[server]` in `server.toml`
([Server configuration](/getting-started/server-config/)).
**Announced but not on the list?** The list could not reach the server: the UDP port is closed or not forwarded
(section 1), or the list sees another address than the one players should use (section 2). The server's console says
so: `master: on the public list as ` once it is listed, and otherwise `master: the list cannot reach this server
at ` with the reason - check that the address is the one players use. Test from outside your network with the
launcher's *Direct connect* - the list asks exactly what its *Query* button asks. A server in your own network answers
your launcher even when nobody outside can reach it, so seeing it in your own launcher proves nothing.
## 1. The port
The server takes one **UDP** port, `[server] port` (7777 by default). Open it on the machine's firewall and, behind a
home router, forward it to the machine - UDP, not TCP. Two servers on one machine take two ports.
The same number over **TCP** answers the server's **status page**: `http://:7777/` gives its name, level, game
mode, version, the players and their names, the slots, whether it wants a password, its uptime and the world's time as
JSON - for a hosting panel, an uptime monitor or a website that lists your server. A server nobody can join at that
moment answers `503`. Open TCP too if you want it reachable from outside; `[server] http_status = false` switches it off.
## 2. The address
The list shows a server under the address it sees the announcement come from - the machine's public IPv4 address (the
server announces itself over IPv4), which is right for a server on a rented box or behind a plain home connection. When
it is not - a server that should be found under a hostname, or one behind an address the list cannot see - name it. A
server that players reach on another port than its own (a tunnel such as playit.gg hands out one) gives that port too,
`"host:port"`. Leave it `""` unless you need it: a fixed address goes stale when a home connection's address changes, and
two servers must never name the same address and port - the list keeps one row for both (the console warns about it):
```toml
[master]
url = "https://kcd-mp.com/server-list" # the public list; "" = announce nowhere (a private server)
interval_seconds = 60 # how often
public_address = "" # "" = the address the list sees; else a hostname or an address, with ":port" for a tunnel's port
```
`url = ""` keeps a server off the list altogether - a test server, a private one for friends who connect by address. The
environment variables `KCDMP_MASTER_URL` and `KCDMP_MASTER_ADDRESS` stand in for the two addresses when set, and the
command line's `--master` / `--master-address` beat both.
## 3. What players see
The list carries the server's **name**, its **level**, the players on it against `max_players`, the game mode's name,
the server's version and whether a **password** is needed:
```toml
[server]
name = "KCD:MP dev server" # the name in the list and in the welcome
motd = "" # a line shown to every joining player
password = "" # players must type it to join; "" = open
max_players = 32
level = "klaster" # klaster, trosecko or kutnohorsko
```
A click on a server in the launcher opens its **details** beside the list: what it runs, and what you tell players about it -
a description, your website, your Discord, a few tags and the language spoken there. The public list carries them too:
```toml
[server]
description = "Medieval roleplay at Trosky.\nWhitelist on our Discord." # \n starts a new line; 1000 characters at most
website = "https://example.org"
discord = "https://discord.gg/abc123"
tags = ["roleplay", "whitelist"] # up to 8
language = "en"
```
Your tags show beside the server's name in the list and in its details, and players can type one into the list's filter.
They are the only tags a server shows: the launcher adds none of its own.
A player picks the server in the launcher's *Servers* tab (or types its address under *Direct connect*), gives the
password if there is one, and the launcher boots their game onto the server's level. A client and a server only play
together on the same **protocol number** - the release notes give it - so a server is updated together with the client
release; `[server] build_hash` can further require one game build.
The list is also a page on the website, [kcd-mp.com/server-list](https://kcd-mp.com/server-list), with the same
entries, the fullest servers first - as in the launcher, which shows only the servers that answer it.
## 4. A Join button on your website
A link to `kcdmp://connect/:` opens the player's launcher on your server's details, where one click joins:
```html
Join our server
```
The address is the one players use (the list's), with the port; without one the launcher takes `7777`. The link carries
nothing else - no password, no player name: a server with a password asks for it in the launcher. Players get the link
type with the launcher, the first time its window opens; a player who never started it gets nothing from the click, so
put the download beside the button. Browsers ask once before they open the launcher. Discord shows such a link as plain
text - post your server's address there, or a page of yours with the button.
## 5. Keeping it yours
- **Admins**: the game mode names them ([Setting up](/getting-started/setting-up/#8-admins-and-accounts): the
shipped freeroam takes the `KCDMP_ADMINS` names); the server keeps no accounts and no admin list. `[commands] public`
names the built-ins anyone may use, `disabled` switches some off.
- **Accounts**: a name belongs to nobody as far as the server is concerned. A game mode with a database behind it
([Databases](/guides/databases/)) gives players a `/register` and a `/login` and keeps their things between visits; the
shipped freeroam does exactly that once `[gamemode.database]` is set.
- **Bans**: `/kickban [minutes]` (5 minutes when no number is given) and `/unban` in the chat; a ban for good,
or any rule of your own, is a game mode's ([`Ban`](/lua/server/functions/ban/)). The bans live in the server's own
database (`data/server.db`) and survive a restart. A name ban is only as strong as the name; the address
ban is the one that holds.
- **Cheating**: the server owns every player's health, stamina, items and position - it judges what the clients report
against its own record, and the [world data](/guides/#the-games-data) sharpens that judgement: the terrain and the
collision geometry catch a player through a wall or in the air, the tables make a hit what the game says it is, and the
audit (`[audit]`) catches items a player cannot explain and a character's levels, experience, perks, nourishment, energy
or carried weight that nothing explains ([the progression check](/getting-started/server-config/#the-progression-check)).
It starts in `observe`, which only logs - read the log for a while, then switch it to `enforce` (a cheat the server can prove,
a game that ignores an order or goes silent, gets the `[audit] action` in either mode, a kick by default).
# Connecting a database
> A MySQL / MariaDB or PostgreSQL database for the game mode's own accounts, factions and scores - the server, the user, the [gamemode.database] block in server.toml, and the rules that keep it safe and fast.
The server keeps its own world by itself - the doors, the chests, the taken items, the bans - in a file it makes on its first
start (`data/server.db`), and nothing needs to be set up for that. What it does **not** keep is anything about a player: a name
is whoever typed it, and a player who leaves is forgotten. Accounts, factions, ranks, scores, the place a player left, the rules
on doors and chests - all of that is the **game mode's**, and a game mode keeps it in a database of its own. This guide connects
one. The shipped `basicrp.lua` is the worked example - a password box to register and log in with, and every character put
back where they left with what they carried, their skills and their horse (section 3) - and `freeroam.lua` uses it for a
`/register` and a `/login`; a mode of yours uses it for whatever it likes, in
[Lua](/lua/server/functions/getdatabase/) or [C#](/csharp/server/#the-modes-database).
## 1. The database server
MySQL, MariaDB or PostgreSQL, on the same machine as the game server or anywhere it can reach. Make a database and a user that
may use **that database and nothing else**:
```sql
-- MySQL / MariaDB
CREATE DATABASE kcdmp CHARACTER SET utf8mb4;
CREATE USER 'kcdmp'@'%' IDENTIFIED BY 'a long password';
GRANT ALL PRIVILEGES ON kcdmp.* TO 'kcdmp'@'%';
```
```sql
-- PostgreSQL
CREATE USER kcdmp WITH PASSWORD 'a long password';
CREATE DATABASE kcdmp OWNER kcdmp;
```
A game mode creates its own tables from there (`CREATE TABLE IF NOT EXISTS ...` in its init), so the user needs to be allowed to.
## 2. server.toml
```toml
[gamemode.database]
driver = "mysql" # or "postgres"
host = "127.0.0.1"
port = 0 # 0 = the driver's default (3306 / 5432)
user = "kcdmp"
password = "a long password"
database = "kcdmp"
ssl = "preferred" # "required" for a database on another machine
```
One line does instead of the parts: `url = "mysql://kcdmp:a%20long%20password@127.0.0.1:3306/kcdmp"`. The credentials live in
this file and nowhere else - never in a game mode script, which you may share; keep `server.toml` readable by the server's own
user alone. The server opens the database before the game mode starts and refuses to start on one it cannot reach, so a typo
shows in the first lines of the log, not at the first player. Every key is in the
[configuration page](/getting-started/server-config/#gamemodedatabase); the four limits (`pool`, `timeout_seconds`,
`max_rows`, `slow_query_ms`) have defaults that suit a game server.
## 3. Running BasicRP
BasicRP needs nothing more than the two steps above: an **empty** database, its user, and the `[gamemode.database]` block,
with the mode named in `server.toml`:
```toml
[gamemode]
script = "gamemodes/basicrp.lua"
```
There is no SQL file to run. At its first start BasicRP makes every table it uses, and the log says `BasicRP: accounts on
(mysql)` (or `postgres`) once they are there. Without a database it says `accounts are off` instead: everyone plays,
and nothing of theirs is kept when they leave. A later version that needs
a new table or column adds it by itself at the next start - the release notes name each one - so an update is the new files
and a restart. Its tables, for a look in your database tool:
| Table | One row per |
|---|---|
| `players` | account: the name, the password's hash, the place they left, the purse, the look, how fed and rested they are |
| `player_items` | stack a character carries, what they wear marked `worn = 1` |
| `player_horses` | horse a character owns: its breed, its coat and whether it is the one `/horse` brings |
| `horse_items` | stack in a horse's saddlebags, its saddle and other gear marked `worn = 1` |
| `player_progress` | stat or skill of a character: its level, the experience toward the next and the perk points not spent yet |
| `player_perks` | perk a character holds |
| `horse_stats` | stat an admin gave a horse |
Deleting a row of `players` deletes everything of that character with it. The tables are plain SQL, so a question like "who
carries this sword" is one `SELECT` on `player_items`. Leave the tables' layout to BasicRP, and change a character's rows only
while that player is offline: BasicRP reads a character at the login and writes on every change, so an edit made while they
play is overwritten.
## 4. What a game mode gets
`GetDatabase()` in Lua (`IServerApi.Database` in C#) answers the handle, or `nil` when this file has no `driver`: a mode says so
and runs without. Every query runs on a thread of its own and answers in a callback a tick or more later - the server never
waits for the database:
```lua
local db = GetDatabase()
function OnGameModeInit()
if not db then Log("no database: the scores are not kept") return end
db:ExecuteSync("CREATE TABLE IF NOT EXISTS scores (name VARCHAR(24) PRIMARY KEY, kills INTEGER NOT NULL DEFAULT 0)")
end
function OnPlayerConnect(pid)
if not db then return end
db:Query("SELECT kills FROM scores WHERE name = @n", { n = GetPlayerName(pid) }, function(rows, err)
if err then Log("scores: " .. err) return end
SetPlayerData(pid, "kills", rows[1] and rows[1].kills or 0)
end)
end
```
`Query`, `Execute`, `Scalar` and `Batch` (several writes in one transaction) are the four calls; `QuerySync` and `ExecuteSync`
answer at once but only inside `OnGameModeInit` - the tables a mode makes, the rules it loads before the first player. The
[`GetDatabase`](/lua/server/functions/getdatabase/) page has every call, what the values look like on the way in and out,
and how a mode's `/register` and `/login` fit together ([`SetSecretCommand`](/lua/server/functions/setsecretcommand/),
[`HashPassword`](/lua/server/functions/hashpassword/), [`VerifyPassword`](/lua/server/functions/verifypassword/)).
## 5. Safe
- **Parameters, always.** `@name` in the SQL and `{ name = value }` beside it. There is no escape function on purpose: a value
that is concatenated into the SQL text is how a player's name becomes a `DROP TABLE`. Every example in these pages binds its
values; do the same and injection is not a thing that can happen to your mode.
- **A user of its own**, with rights on the mode's database alone (above). The server's own file is never reachable through
the mode's database, whatever the SQL says.
- **Passwords hashed**, never stored: `HashPassword` gives one text to store, `VerifyPassword` checks a login against it.
- **The password of the database** in `server.toml` only; the server never logs it.
- `ssl = "required"` for a database on another machine.
## 6. Fast
The tick runs thirty times a second and every callback of the mode runs inside it. A database round trip is milliseconds;
thirty of them a second would be the whole tick. The rule: **the database is never on the hot path.**
- Read a player's row **once, on the connect**, into `SetPlayerData` and the mode's own tables. Answer every callback from
memory. `OnPlayerUseDoor` deciding whether a faction's rank may open a door reads a table the mode filled at init and keeps
in step - never a query.
- Write **on change** and **on the disconnect**; several writes at once go as one `Batch`.
- `QuerySync` only at init. Anywhere else it is refused with an error, so a slow database can never freeze the server.
- The limits are there for the day something goes wrong: a query over `timeout_seconds` is cut off, one returning more than
`max_rows` is refused (page it), one slower than `slow_query_ms` is named in the log. A failing query hands its error to the
callback and the server goes on.
## 7. Finding the keys
A rule on a door or a chest names the object by its **key**, the entity's name in the level file
(`AnimDoor[Door/door_village_left10_...]`, `confessorsChest`). Two ways to get them: the [level lists](/reference/levels/)
have every door and chest with its position, and in the game an admin's `/objectinfo` puts a label over every object around
them for a minute - the kind, a readable name, the distance - and writes the full keys to the chat and the client's log. A mode's
own command does better than either: [`GetNearestDoor`](/lua/server/functions/getnearestdoor/) names the door the player
stands at, so a faction leader sets a rule by walking up to the door and typing `/perm 3`, and never sees a key at all.
# Cart routes
> Horse-drawn carts that travel your roads on their own - recording a route in the game by riding it, its stops, how many carts run it, how fast, and what they look like.
A cart route is a road the server drives its carts along by itself: a wagon's two horses (or a two-wheeler's one) at a
canter, a wagoner on the bench, players riding along. The carts pull up at the route's stops, wait there, and go on - round a loop, or there and
back with a turn at each end. Players get on with the game's own prompt by the wagon ("Get on") and get off with the game's
dismount key wherever they like; a cart waits a few seconds while someone climbs in or out.
Next to the routes there are **free carts**, which stand where they were put until a player takes the reins and drives them
- players' own carts, a dealer's, a cartwright's: [personal carts](/guides/personal-carts/).
## Recording a route
The routes are yours to lay out: an admin records one in the game by riding (or walking) the road, and the server keeps it
in the routes file of that level (`[carts] routes`, `data/carts/.toml` by default) and runs it from then on - after
every restart too. The commands are admin-only unless `[commands] public` names them.
1. Stand where the road begins and type `/cartroute new Mill Road` - the name is how you will refer to it (1-32 letters,
digits, spaces and `-_.'`).
2. Ride the road. `/cartroute auto 12` drops a point every 12 metres as you move - the easy way on a winding road; or type
`/cartroute point` at each bend yourself. Keep to the road: the carts drive straight from point to point, the corners
rounded over a few metres, and go through whatever lies between two points.
3. Where the carts should wait, stop and type `/cartroute stop 30 Mill` - 30 seconds at the stop named "Mill".
4. At the end: `/cartroute mode back` if the carts should turn round there and come back the same way (a loop, the default,
drives from the last point back to the first - so end a loop near its start). `/cartroute carts 2` runs two carts on it,
spread evenly; `/cartroute speed 5` makes this route slower than `[carts] speed`; `/cartroute kind cart` runs two-wheelers
instead of wagons (`kind wagon` back; either clears the parts chosen so far); `/cartroute part body b_covered` gives
its carts a covered body (any [part of a cart](/guides/personal-carts/#what-a-cart-is-made-of) of the route's kind: `body`,
`axle`, `wheels`, `load`, `horse1`, `horse2` on a wagon - `/cart parts` by a cart lists its kind's).
5. `/cartroute save` - the route runs at once and is written to the file.
`/cartroute undo` takes the last point back, `/cartroute cancel` drops the recording. `/cartroute list` shows every route
of the level; `/cartroute carts Mill Road 3` changes how many carts a saved route runs, `/cartroute part Mill Road load hay_large`
what they look like (`default` gives a slot back), `/cartroute remove Mill Road` takes it away with its carts. Each is
written to the file at once.
`/cart` puts a free cart in front of you (across your way, the bench at your right hand; `/cart cart` a two-wheeler);
`/cart remove` takes away the free cart nearest you.
## The routes file
`/cartroute save` writes it; you can also edit it by hand, with the server stopped. One `[[route]]` table per route:
```toml
[[route]]
name = "Mill Road"
level = "klaster"
points = [[1284, 1080, 26.1], [1244, 1080, 26.1], [1244, 1106, 25.6], [1284, 1106, 25.5]]
speed = 7 # m/s; leave it out for [carts] speed
mode = "loop" # or "back"
carts = 1
kind = "wagon" # or "cart", the two-wheeler
driver = true # false = no wagoner on the bench
parts = { body = "c", load = "hay_medium" } # what the carts look like; a slot left out is the default
stops = [{point = 0, name = "Mill", wait = 30}]
```
The points are world metres (`/pos` prints yours); a height left out, or below `0`, is the ground's when the server has the
[terrain](/guides/terrain/), else the players' games put the wheels on the ground. A stop's `point` counts from `0` in the file.
A part the server does not know is left at the default, with a line in the server's log.
The server's own settings for carts are in [`[carts]`](/getting-started/server-config/#carts): whether there are any,
the file, the pace of the routes and the fastest a player may drive.
## From a game mode
A mode builds its own on the same pieces: [`AddCartRoute`](/lua/server/functions/addcartroute/) makes a route (kept
until the server stops, or written into the file with [`SaveCartRoutes`](/lua/server/functions/savecartroutes/)) - its
`parts` the look of its carts - [`OnPlayerEnterCart`](/lua/server/callbacks/onplayerentercart/) can ask a fare, keep a
player out or seat them elsewhere, [`OnCartStop`](/lua/server/callbacks/oncartstop/) hears a cart arrive at a stop and
[`HoldCart`](/lua/server/functions/holdcart/) keeps it there; [`SetCartPart`](/lua/server/functions/setcartpart/)
changes one cart's look (a route's carts go back to the route's when it is replaced). A route cart's seat `0` is its
wagoner's; its passengers get seats `1` to `5` on a wagon, `1` and `2` on a two-wheeler. The whole list is the [Carts group](/lua/server/#carts) of the
server API.
# Building on the map
> The server's own map of the level - the game's buildings and props placed anywhere, parts of the level taken away, areas, lights, doors and use prompts - built in the game's own world with the in-game editor, shared with other servers, and handed to players as building plots.
Every server keeps a map of its level: the game's own meshes placed by its admins and its game mode - a wall, a gate, a market's
stalls, a whole camp - turned every way and scaled, parts of the level itself taken away - a house, a wall, a rock, a tree -, named
areas, the game's lights, fires, sounds and doors, and links that tell the server's characters where they can jump. Players get it
when they join, before they appear in the world, and keep it for the next time; every change after that reaches everyone at once.
Nothing is installed by the players: every object is one of the game's own meshes, which every copy of the game has.
## Turning it on
`[world] map` in `server.toml` says where the server keeps the map - one folder per level:
```toml
[world]
map = "data/maps/{level}" # "" = no map
```
That is the default, so a new server has a map from the start. Nothing is written until the first edit; from then on the folder
holds the map's files (readable JSON, one object a line), the shapes the server learned for the meshes it uses, the exports and the
backups. Copy the folder to give another server of the same level the same map.
## Building in the game
An admin types `/editor`. The player becomes the camera and flies (`W` `A` `S` `D`, `Space` up, `C` down, `Shift` faster), the
right mouse button held turns the view, and the editor's panel opens at the side - it needs the game's web engine, which comes with
KCD:MP (without it the `/map` commands below still work). [Using the map editor](/guides/using-the-map-editor/) walks through all of it
step by step; in short:
- **Placing**: the panel's browser lists the game's meshes by folder, with a search; a click on one starts placing it - it follows
the pointer over the ground and other objects, `Q` / `E` turn it, a click puts it there. Before the meshes come the game's working
things: a **light** (its colour, reach and strength in the inspector), an **effect** (a fire, smoke, a candle's flame), a **sound**
playing where it stands, a **marker** only the game mode sees (a spawn point, a race's gate), a **door** that opens, closes and
locks for everyone, and a **seat** to sit or sleep on anything. The game's own chairs, benches and beds are sat on and slept in
by themselves, like the level's; a chair's **At a table** in the inspector seats the player at a table instead of on its own. The
game's own ladders (`objects/manmade/common_fixtures/ladders/`: the village ladders and the siege ladders) are climbed like the
level's while they are solid and not stretched - lean one against a wall or a loft, its foot on the ground.
- **Changing**: a click selects (with `Shift` more than one), and the tools change the selection by hand: `1` move along an arrow or
over the ground, `2` turn about a ring, `3` scale - the centre box scales the object whole, an arrow's box **stretches it along that
axis** (a wall made longer, a beam thinner). `X` switches the arrows between the world's axes and the object's own, `N` the snap
(its grid and angle in the panel), `G` drops the selection to the ground, `Ctrl+D` copies it, `Delete` takes it away. The
inspector types the numbers instead: place, turn, scale along X / Y / Z, material, solid, a use prompt.
- **Undo** (`Ctrl+Z`) and **redo** (`Ctrl+Y`) are each admin's own; several admins can build at once and see what the others have
selected.
- **Prefabs**: a selection saved under a name ("Save as prefab") is placed again anywhere, turned as you like, from the panel's
Prefabs list - as separate objects, or **as one unit**: one map object drawn as the prefab's objects, which follows the prefab when
it is saved again (the way to build a big village light).
- **Areas** (`4`): a drag on the ground draws a box, with `Shift` a circle; the polygon shape takes a click per corner. An area has
a name, a label players may see when they walk in, and its rules are the game mode's.
- **Navigation** (`5`, `V`): the view shows where the server's characters can walk; two clicks test a path, or draw a link for a
jump or a gap their walk cannot join.
`/editor` again closes it.
## Without the editor
The same works from the chat for admins: `/map place ` places a mesh at your feet facing your way, `/map near` lists what is
near (with each object's number), `/map move | turn | scale | delete ...` changes one, `/map remove look` takes out the house,
wall, rock, tree or level object you look at and `/map restore ` puts it back, `/map layer on|off` switches a layer.
`/help` lists them all.
## Taking parts of the level away
What `/map remove look` takes out is gone for everyone - drawn no more, and players and the server's characters walk where it stood.
A building is often several pieces (a facade, a roof, a cloister): take them out one look at a time. From very far away the game draws
a level's buildings as merged far-view models, so a house taken out can show again at that distance.
## Pictures and pages
Your own pictures go on the map too: put PNG files in the map folder's `pictures/` folder (a file's name is the picture's - letters
`a`-`z` first, then letters, digits, `_` and `-`; at most 2048 pixels a side, 64 pictures and 8 megapixels in all) and type
`/map pictures reload` - `/map pictures` lists them. In the editor they are under "Pictures and pages", beside the web interface's
surface pages (notice boards, signs - the [web interface guide](/guides/web-interface/)): pick one and click a wall or any face, and it hangs
there, its face towards you, a centimetre out of the wall. Q and E turn it about the wall, the inspector sets its size in metres,
whether its see-through parts show the world behind them (soft or hard) and how bright it stays at night (its glow). A picture with
see-through parts starts soft; "Solid" puts it on a plain board.
A picture or a page hung on a placed object sits on that object: it moves, turns and stretches with it and goes when it is deleted.
"Take it off" in the inspector (or `/map attach 0`) lets it go; `/map attach ` puts any object on another. Without
the editor: `/map place picture [width]` and `/map place surface [width]` hang one in front of you. The pictures go with
an export.
## Layers
Every object belongs to a map layer (`base` unless you say otherwise). A layer switched off hides all of its objects for everyone at
once - an event's build put up and taken down with one command. A game mode can also show a layer to some players only
([`SetPlayerMapLayer`](/lua/server/functions/setplayermaplayer/)): a quest's camp only its hero sees, a house's furniture for its
owner.
## Sharing a map
- `/map export ` writes the whole map as one file in the map folder's `exports/`; `/map exports` lists them.
- Copied into another server's `exports/`, `/map import replace` makes that server's map the same - the same objects under the
same numbers, so `/map hash` gives the same fingerprint on both - and `/map import merge` adds its objects beside the map's
own. A replace backs the old map up first.
- The map is also backed up once a day, into `backups/`, the newest 14 kept; `/map backup` makes one now.
- `/map import sandbox ` takes in a layer made with the game's official modding tools (a `.lyr` file in `exports/`): its
meshes, lights and effects, on a map layer of the layer's name.
## Building plots for players
The example mode `gamemodes/plots.lua` hands the editor to players, safely: an admin marks plots with `/plot create `, a
player claims the plot they stand on (`/plot claim`) and builds on it (`/plot build`) - only their own objects, at most a number of
them, and nothing outside their plot. Read it for the pattern: [`SetPlayerEditor`](/lua/server/functions/setplayereditor/) opens
the editor with what the player may do, [`OnMapEdit`](/lua/server/callbacks/onmapedit/) says no to an edit the mode does not want.
## For game modes
A game mode builds the same map from its script: [`CreateMapObject`](/lua/server/functions/createmapobject/) and its kin place
and change objects (a mode's own may last for its run only), [`RemoveLevelObject`](/lua/server/functions/removelevelobject/)
takes parts of the level away, [`CreateArea`](/lua/server/functions/createarea/) draws areas with
[`OnPlayerEnterArea`](/lua/server/callbacks/onplayerenterarea/) / [`OnPlayerLeaveArea`](/lua/server/callbacks/onplayerleavearea/),
[`CreateMapLight`](/lua/server/functions/createmaplight/), [`CreateMapDoor`](/lua/server/functions/createmapdoor/),
[`CreateMapSeat`](/lua/server/functions/createmapseat/) and the other working things, a use prompt and [`OnPlayerUseMapObject`](/lua/server/callbacks/onplayerusemapobject/), and
[`ExportMap`](/lua/server/functions/exportmap/) / [`ImportMap`](/lua/server/functions/importmap/),
[`PlaceMapPrefab`](/lua/server/functions/placemapprefab/) for the rest - or [`CreateMapPrefab`](/lua/server/functions/createmapprefab/)
for a prefab placed as one unit, its objects listed by [`GetMapPrefabParts`](/lua/server/functions/getmapprefabparts/) (their doors' keys,
their ids for a use prompt). Pictures and pages: [`CreateMapPicture`](/lua/server/functions/createmappicture/),
[`CreateMapSurface`](/lua/server/functions/createmapsurface/) (its surface talks to the mode like any web surface:
[`GetMapObjectSurface`](/lua/server/functions/getmapobjectsurface/)), a map object's `attach`. Every function is in the server's
index under "Map".
## Limits
- A map holds at most 10,000 objects (the game has a limited number of entities per level).
- A stretched object's look stretches exactly; a round piece of its collision (a barrel's, a column's) stays round, its width the
mean of the two sideways stretches - the server follows the game in that.
- Chests and ladders are not among the working things yet.
# Using the map editor
> The in-game map editor step by step - opening it, flying, placing the game's meshes and working things, moving, turning, scaling and stretching them, hanging your own pictures and pages, taking parts of the level away, areas, navigation, layers and prefabs, building with other admins, and how your work is saved and moved to another server.
The map editor is how an admin builds the server's map from inside the game: you fly through the level, pick the game's own
buildings, props, lights and doors from a panel, put them where you want them and change them with the mouse. Every change
reaches every player at once and is saved by the server. What the map is and how it is kept is in
[Building on the map](/guides/map-editor/); this page is the editor itself, from opening it to moving your work to another server.
## Before you start
- You need to be an admin on the server - the game mode decides who is one. A game mode may also open the editor for a player
with limits of its own - a building plot, a number of objects - see [`SetPlayerEditor`](/lua/server/functions/setplayereditor/).
- The server needs a map: `[world] map` in `server.toml`, which is on by default.
- The panel is drawn by the game's web engine, which comes with KCD:MP. A game without it cannot open the panel; the
[chat commands](#without-the-panel) still work there.
## Opening and closing
Type `/editor` in the chat. Your character becomes the camera and flies, and the panel opens on the right. `/editor` again, or
the **✕** at the panel's top, closes it: your character lands wherever you flew to.
| To | Do |
|---|---|
| fly | `W` `A` `S` `D`, `Space` up, `C` down, `Shift` faster |
| look around | hold the right mouse button and move the mouse |
| fly to what is selected | `F` |
| use the chat | `Enter`, as always - chat commands work while the editor is open |
The left mouse button belongs to the world: it selects, drags and places. The panel takes the mouse only while the pointer is
over it.
## The panel
- **The head**: how many objects the map holds and its revision (every change counts one), **↺** undo and **↻** redo (also
`Ctrl+Z` / `Ctrl+Y`), and **✕** to leave.
- **The tool bar**: `1` Move, `2` Turn, `3` Scale, `4` Areas, `5` Nav - click one or press its key.
- **World / Local** (`X`): whether the arrows and rings follow the world's axes or the object's own.
- **Snap** (`N`), its **grid** in metres and its **angle** in degrees: moves land on the grid, turns go in steps of the angle.
- **The status line** under the tools: green says what was done, red why the server refused it (another admin's object, the
map's limit of objects, a game mode's rule ...).
- **Sections**: Place, the selected object, Areas, Navigation, Layers and Shortcuts. A click on a section's title folds it away
or opens it again; Shortcuts starts folded and lists every key.
## Placing things
The **Place** section lists the game's meshes by folder - click a folder to open it, the path above the list to go back - and
its search box finds a mesh by any part of its name across all folders. The game's unfinished placeholder models (they show
plain white) are left out: **Show ... white placeholder models** at the end of the list brings them back, marked **white**, and
**finished** beside one places the game's finished model of it instead. A click on a mesh starts placing it:
1. The mesh follows the pointer over the ground and over other objects (a crate on a table, a lamp on a wall's top).
2. `Q` and `E` turn it before you place it.
3. A left click puts it there. You keep placing the same mesh until you press the right mouse button or **Stop placing**.
Above the folders are the game's **working things**: a **Light** (its colour, reach, strength and style - steady, flickering or
pulsing - in the inspector), an **Effect** (a fire, smoke, a candle's flame), a **Sound** playing where it stands, a
**Marker** that only the editor and the game mode see (a spawn point, a gate of a race - its tag is the name the mode looks for)
a **Door** that opens, closes and locks for everyone, and a **Seat** - a place to sit or a bed to sleep in, on anything (its pose
in the inspector; its line points where the sitter looks). The game's own chairs, benches and beds need no Seat: placed upright at
their own size, they are sat on and slept in like the level's. A chair seats the player on their own; tick its **At a table** in the
inspector for a chair at a table (the arms rest on the table in front of it). Like the level's own chairs, a chair never blocks a
player - they would not get onto or off its seat - while the game mode's actors still walk round it.
The game's own **ladders** (the folder `objects/manmade/common_fixtures/ladders/`: the village ladders `ladder_rustic_225` to
`ladder_rustic_575` and the two siege ladders) are climbed like the level's: walk up to one and press the use key. Lean its top
against a wall or a loft's edge - turn it with the ring about its side axis, 5 to 15 degrees as the level's lean - and keep its foot
on the ground (the game offers no climb to a ladder whose foot lies 0.4 m or more under the player's feet). The climb is always on the open side. A scaled ladder
climbs as far as it is long; a stretched one, or one whose **Solid** is off, is only scenery. The broken and burned ladders are
scenery too, as in the game.
## Selecting and changing
A click on an object selects it; `Shift` or `Ctrl` with the click adds it to the selection or takes it out; a click on empty
ground clears the selection.
- **Move** (`1`): drag an arrow to move along it, or drag the object itself to slide it at its own height.
- **Turn** (`2`): drag a ring to turn about its axis.
- **Scale** (`3`): the box in the middle scales the object as a whole, the box at the end of an arrow **stretches it along that
axis** - a wall made longer, a beam thinner, a barrel taller.
- `G` drops the selection to the ground, `Ctrl+D` copies it beside itself, `Delete` takes it away.
The inspector under the selection does the same with numbers - type a value and press `Enter`:
| Field | What it is |
|---|---|
| Place | where it stands, in metres (X east, Y north, Z up) |
| Turn | its turn in degrees about Z (yaw), X (pitch) and Y (roll) |
| Scale | its size along its own X, Y and Z (three alike: the whole object) |
| Solid | whether players and the server's characters bump into it; a solid object also clears the flowers, weeds and tall grass tufts under its floor and walls |
| Actors | what the server's characters make of it when they find their way: **In the way** (as its shape says), **Blocks** (never through), **Walkable** (they walk on it - a bridge, a ramp), **Ignored** |
| Draw distance | how far away it is still drawn: 1-255, 100 is normal for its size, 255 as far as the game draws; 0 = the game's own |
| LOD ratio | how soon the game swaps in the object's simpler models as players move away: 1-255, 100 is normal; higher swaps sooner and is lighter to draw (a town of many buildings), lower keeps the detailed model farther; 0 = the game's own |
| Use prompt | words players see on it with the use key, such as "Read the notice"; the game mode hears the key ([`OnPlayerUseMapObject`](/lua/server/callbacks/onplayerusemapobject/)) |
| Layer | the [layer](#layers) it is on |
| Material | one of the game's materials in place of the mesh's own (empty: its own) |
Under the fields: **Focus** (fly to it), **Copy**, **Ground**, **Delete**, and **Save as prefab** (see [Prefabs](#prefabs)).
## Pictures and pages
Your own pictures hang on the map like posters, banners and shop signs, and so do the web interface's surface pages (a notice
board, a sign with live text - [Make a web interface](/guides/web-interface/)). Put the pictures in the map folder's `pictures/`
folder as PNG files - the file's name is the picture's: letters `a`-`z` first, then letters, digits, `_` and `-`; at most 2048
pixels a side, 64 pictures and 8 megapixels in all - and type `/map pictures reload`.
1. Click a name under **Pictures and pages** in the Place section.
2. Point at a wall, or any face of a placed object - the picture stands on that face, facing you.
3. `Q` and `E` turn it about the face; a left click hangs it.
Selected, a picture or page has its own fields: its **Size** in metres (width and height), how it **Shows** its see-through
parts (**Solid** on a plain board; **See-through, soft** or **See-through, hard** showing the wall behind - a picture with
see-through parts starts soft), its **Glow** (0 lit by the world like everything else, 1 as bright at night as by day) and what
it **Sits on**.
A picture or a page hung on a placed object sits on that object: it moves, turns and stretches with it, and goes when the object
is deleted. **Take it off** lets it go; `/map attach ` puts any object on another one.
## Taking parts of the level away
The level's own buildings, walls, rocks, trees and props come out with the chat, the editor open or not:
1. Aim the centre of the screen at it.
2. Type `/map remove look`. It is gone for everyone - players and the server's characters walk where it stood. The invisible walls
the level keeps around some props (so nobody climbs into a wrecked cart) go with them.
3. `/map near` lists what is around you with numbers, the removals too; `/map restore ` puts one back. Looking where a
removed thing stood and typing `/map remove look` again names it and its number.
A building is often several pieces - a facade, a roof, a cloister - taken out one look at a time. From very far away the game
draws houses as merged models, so a house taken out can show again at that distance. The story's shut gates and fences are
level layers: `/layer`, not a removal.
## Prefabs
A group you build often - a market stall, a camp, a guard post - is saved once and placed again anywhere:
1. Select its objects (`Shift`-click), type a name under the inspector (letters, digits, `-` and `_`) and press **Save as prefab**.
2. It appears in the Place section's **Prefabs** list. Click it, turn it with `Q` / `E`, click to place the whole group - one undo
takes it all back.
3. The **×** beside a prefab deletes the prefab; what was placed from it stays.
**As one unit**: with the switch above the Prefabs list on, a click places the prefab as **one unit** - one map object that every
player's game draws as the prefab's objects. A street of houses stays light for the server and every player (eighty houses are
eighty objects, not two thousand), and a unit **follows its prefab**: save the prefab again under the same name and every unit of it
changes at once. A click on any of its objects selects the whole unit; it moves, turns, scales (one size for all of it) and is
deleted as one, and its inspector sets whether it is solid, its draw distance, its LOD ratio and its layer. To change one of its
objects on its own, press **Unpack** in the inspector: the unit becomes ordinary objects (one undo puts it back). A prefab that units
are placed from cannot be deleted until they are unpacked or deleted. Admins without the editor: `/map prefab unit ` at their
feet, `/map prefab unpack `.
## Areas
The Areas tool (`4`) draws named areas on the ground - a town square, an arena, a no-fighting zone - whose rules are the game
mode's ([`OnPlayerEnterArea`](/lua/server/callbacks/onplayerenterarea/)).
- **Box**: drag on the ground (`Shift` while dragging: a circle). **Circle**: drag from its middle. **Polygon**: click the ground
at each corner, then click the first corner again or press **Close the outline**; `Backspace` takes the last corner back.
- `Ctrl` while dragging draws over an existing area instead of selecting it.
- Click an area (or its name in the list) to select it; double-click the name to fly there.
A selected area has a **Name** (what the game mode uses), a **Label** players see when they walk in if **Shown** is on, its
**Height** (bottom and top in metres), a **Colour** for the editor (a hex value such as `40c060`), its **Layer**, and **Actors**:
**Nothing**, **Keep out** (the server's characters walk around it) or **Floor** (they walk a floor at its bottom - a raised
stage).
## Navigation
The Nav tool (`5`) is about the server's walking characters.
- **Nav view** (`V`) draws where they can walk around you.
- **Path test**: click two points on the ground and see the path they would take, and how long it is.
- **Nav link**: click where a link starts and then where it leads - a jump, a gap, a ladder their walk cannot join by itself;
`Shift` on the second click makes it one way. Click a link to select it; **Delete the link** removes it.
## Layers
Every object is on a layer - `base` unless you say otherwise. The **Layers** section lists them with a switch each: a layer
switched off hides all of its objects for everyone at once, such as an event's build put up and taken down with one click.
**New into** is the layer new objects go on. A game mode can also show a layer to some players only
([`SetPlayerMapLayer`](/lua/server/functions/setplayermaplayer/)).
## Building together
Several admins can have the editor open at once. Each sees the others' work as it happens and who is editing (and how much they
have selected) at the bottom of the panel. Undo and redo are each admin's own: yours never takes back someone else's change. An
object another admin has selected is theirs while it stays selected - change it and the status line says who has it.
## Saving your work
There is nothing to save by hand: every change is written by the server as you make it, and is kept through a restart or a
crash.
- `/map save` writes the map files at once (before stopping a server by force, for instance).
- The map is backed up once a day, the newest 14 kept; `/map backup` makes one now and `/map backups` lists them.
- Mistakes in a session: `Ctrl+Z`. Anything older: a backup or an export.
## Moving your work to another server
Build on a test server, then move the result to the live one - both on the same level:
1. On the test server: `/map export `. The whole map - objects, removals, areas, navigation links, layers and your
pictures - is written as one file into the map folder's `exports/`.
2. Copy that file into the live server's map folder, under `exports/`.
3. On the live server:
- `/map import replace` makes the live map the same as the test server's. The old map is backed up first, and players
see the change at once - no restart. Anything built on the live server meanwhile is replaced too.
- `/map import merge` adds the test server's objects beside the live map's own - for one new building or camp. Merging
the same file twice places everything twice.
4. `/map hash` gives the same number on both servers after a replace.
Or stop the live server, copy the whole map folder over and start it again. More on the files: [Building on the map](/guides/map-editor/).
## Without the panel
Everything above has a chat command for admins - useful on a game without the web engine, or for exact work:
| Command | |
|---|---|
| `/map place [scale]` | a mesh at your feet, facing your way (`/map place light`, `effect`, `sound`, `marker`, `door`, `seat [pose]` for the working things) |
| `/map place picture [width]`, `/map place surface [width]` | a picture or a page in front of you |
| `/map near [radius]` | what is around you, with numbers |
| `/map move `, `/map turn `, `/map scale ` | change one object |
| `/map delete ` | delete one |
| `/map attach ` | put one on another (`0` takes it off) |
| `/map remove look`, `/map restore ` | the level's own objects |
| `/map layer on\|off` | switch a layer |
| `/map pictures [reload]` | list the pictures, or read the folder again |
| `/map export`, `/map import`, `/map backup`, `/map save` | see above |
`/help` lists them all.
## When something does not work
- **The panel does not open**: that copy of KCD:MP is missing its web engine; the chat commands still work.
- **A click goes through an object**: an object with Solid off has no collision, so the editor uses its box instead - a picture
hung on one lands on that box.
- **A building is solid where it looks a little different**: with Solid on, KCD:MP uses the game's own collision of an object,
also when the game keeps a building's in another piece. Some objects have none anywhere; for those it makes one from the
object's shape, slightly simplified on a very detailed building. Turn Solid off for decorations players should walk through.
- **Short grass shows through a floor**: a solid object clears the flowers, weeds and tall grass tufts under it, but not the
level's short grass. Raise the floor a little above the ground, or build on a path or bare ground.
- **"Nothing to place it on there"**: the pointer is on the sky; aim at the ground or an object.
- **A removed house shows again from far away**: the game's far-view models, see [above](#taking-parts-of-the-level-away).
- **"Nothing there to take out" while looking at a wall or a rock**: the game cannot tell which piece of the level that spot
belongs to, and `/map remove look` takes nothing rather than a guess (a guess could take the bush in front instead). Look at
the same thing from another spot or another part of it.
- **A red line in the panel**: the server's reason - another admin's object, a limit of the map, or a game mode's rule
([`OnMapEdit`](/lua/server/callbacks/onmapedit/)).
# Personal carts
> Carts players drive, own and do up - putting a cart down, the two kinds (the wagon and the two-wheeler), the keys, the seats, every part of a cart (body, front axle, wheels, load, the horses' coats), and a game mode's cart dealer and cartwright.
A **free cart** is a wagon with two horses - or a two-wheeled cart with one - that stands where it was put until a player takes the reins at its bench. Then
their game drives it - **W** trots, **W** with **Shift** held canters, **S** brakes and backs up, **A** / **D** steer - and
everyone sees it move; a wall, a tree or another cart in the way stops the horses. Other players get on beside the driver or
in the back with the game's own prompt ("Get on") and off with the game's dismount key. The carts that drive a road by
themselves are the other half of the carts: [the routes guide](/guides/cart-routes/).
The server does the driving, the seats and the looks; the game mode decides everything else - who may drive which cart, what
it looks like, what it costs, and whether it is still there after the player's next login.
## Putting a cart down
A cart is of one of two **kinds**:
| Kind | What it is |
|---|---|
| `wagon` | four wheels, two horses at the pole, a bench and a bed behind it - six seats |
| `cart` | the game's two-wheeled cart: one horse between its shafts, the driver on the bed's front edge, a passenger on each side rail - three seats |
An admin's `/cart` (or `/cart cart` for a two-wheeler) puts a free cart 4.5 m ahead, across their way with the bench at their
right hand; `/cart remove` takes away the free cart nearest them. A mode uses
[`CreateCart`](/lua/server/functions/createcart/) - with the cart's parts if it should not look like the default one - and
[`DestroyEntity`](/lua/server/functions/destroyentity/); [`GetEntityTemplate`](/lua/server/functions/getentitytemplate/)
answers a cart's kind:
```lua
-- a covered wagon full of sacks, 4 m ahead of the player, across their way
local x, y, z = GetPlayerPos(pid)
local r = math.rad(GetPlayerYaw(pid))
local cart = CreateCart("wagon", x - math.sin(r) * 4, y + math.cos(r) * 4, z, GetPlayerYaw(pid) + 90, nil,
{body = "b_covered", axle = "b_covered", load = "sacks"})
-- a peddler's two-wheeler with a market load behind a palomino
local peddler = CreateCart("cart", x + 6, y, z, 0, nil, {load = "market", horse1 = "palomino"})
```
How fast a player may drive is `[carts] drive_speed` ([server settings](/getting-started/server-config/#carts)), one
cart's own with [`SetCartSpeed`](/lua/server/functions/setcartspeed/).
## The seats
| Seat | On a wagon | On a two-wheeler |
|---|---|---|
| `0` | at the reins - the driver | at the reins, on the bed's front edge |
| `1` | the bench beside the driver | the right side rail, behind the wheel |
| `2`, `3` | the back of the bed, right and left | `2`: the left side rail |
| `4`, `5` | the bed ahead of them, right and left | - |
The bench's prompt gives the reins of a cart nobody drives, else the seat beside them (a side rail on a two-wheeler); the
bed's prompt a seat in the back on the player's side. The mode sees and moves every seat: [`GetCartSeatPlayer`](/lua/server/functions/getcartseatplayer/)
says who sits where, [`SetPlayerCartSeat`](/lua/server/functions/setplayercartseat/) moves a seated player (to the reins
too), [`SetCartSeatLocked`](/lua/server/functions/setcartseatlocked/) keeps a seat from the prompt, and
[`OnPlayerEnterCart`](/lua/server/callbacks/onplayerentercart/) may keep a player out or seat them elsewhere. The shipped
modes give every player `/seat` (who sits where) and `/seat ` (move there).
## What a cart is made of
A wagon is the game's own parts, and the game builds every one of its wagons on the same chassis - so any body goes with any
front axle, wheels and load. Each **slot** holds one **part**; a slot the mode never set shows the default (the first
named below). A change shows on every screen at once, while the cart drives too. A wagon's slots:
| Slot | Parts |
|---|---|
| `body` | the game's three wagons: `a` (the default), `b` (boards lying in its bed), `b_plain` (the same, its bed empty), `c` (long, with ladder sides); `a_covered`, `b_covered`, `c_covered` - under a canvas cover |
| `axle` | `b` (the default), `b_covered` - the front axle with the pole |
| `wheels` | `b_covered` (the default), `b`, `polygonal` (rough-hewn) |
| `load` | `none` (the default), `sacks`, `charcoal`, `carpets` (cloth, rolls of fabric, crates), `stones`, `hay_small`, `hay_medium`, `hay_large`, `goods` (sacks, crates, baskets, a barrel), `junk` (hides, chains, shackles, rotten apples ...), `cabbage` |
| `horse1`, `horse2` | the horses' coats (left, right): `black`, `bay_dun`, `chestnut`, `light_grey`, `buckskin`, `palomino`, `seal_brown`, `dark_grey`, `red_roan`, `brown` - unset, each cart has a pair of its own |
The two-wheeler is one body with its shafts, so it has fewer slots:
| Slot | Parts |
|---|---|
| `body` | `b` - the only one |
| `wheels` | `b` - the only one |
| `load` | `none` (the default), `goods` (barrels, a basket, sacks), `market` (baskets and a barrel), `sacks`, `cabbage` (wicker sides full of cabbages) |
| `horse1` | the horse's coat, the same names as a wagon's |
The functions: [`SetCartPart`](/lua/server/functions/setcartpart/) and [`SetCartParts`](/lua/server/functions/setcartparts/)
change them, [`GetCartPart`](/lua/server/functions/getcartpart/) and [`GetCartParts`](/lua/server/functions/getcartparts/)
read them - in any callback too - and [`GetCartPartSlots`](/lua/server/functions/getcartpartslots/) /
[`GetCartPartNames`](/lua/server/functions/getcartpartnames/) list the catalogue of a kind for a shop's menu (a slot
the kind has not is refused). An admin's `/cart parts` lists the nearest cart's in the chat and `/cart part `
changes it.
A load is a look: the sacks and the hay take no room from the seats in the back. A mode that fills a cart with hay can keep
the back seats from the prompt with [`SetCartSeatLocked`](/lua/server/functions/setcartseatlocked/).
## A cart dealer
A dealer sells carts, the buyer's own from then on: nobody else takes its reins, anyone may ride along. The whole of it in
Lua (the prices are the mode's):
```lua
CART_OWNER = {} -- cart id -> the player who bought it
PLAYER_CART = {} -- player id -> their cart
local MODELS = {
peddler = {price = 150, kind = "cart", parts = {load = "market"}},
farmer = {price = 300, kind = "wagon", parts = {body = "c", wheels = "polygonal"}},
merchant = {price = 800, kind = "wagon", parts = {body = "b_covered", axle = "b_covered", wheels = "b"}},
}
-- /buycart peddler | farmer | merchant: the cart 4 m ahead, its reins kept for the buyer
function buyCart(pid, model)
local m = MODELS[model]
if not m then SendClientMessage(pid, COLOR_WHITE, "The dealer sells: peddler (150), farmer (300), merchant (800).") return end
if PLAYER_CART[pid] then SendClientMessage(pid, COLOR_RED, "You have a cart already.") return end
if GetPlayerMoney(pid) < m.price then SendClientMessage(pid, COLOR_RED, "That is " .. m.price .. " Groschen.") return end
local x, y, z = GetPlayerPos(pid)
local r = math.rad(GetPlayerYaw(pid))
local cart = CreateCart(m.kind, x - math.sin(r) * 4, y + math.cos(r) * 4, z, GetPlayerYaw(pid) + 90, nil, m.parts)
if not cart then return end
GivePlayerMoney(pid, -m.price)
SetCartSeatLocked(cart, 0) -- the bench's prompt offers a seat, not the reins ...
CART_OWNER[cart], PLAYER_CART[pid] = pid, cart
end
-- ... and the owner at the bench (at either side of a two-wheeler) is seated at the reins
function OnPlayerEnterCart(pid, id, seat)
local bench = seat == 1 or GetEntityTemplate(id) == "cart"
if CART_OWNER[id] == pid and bench and not GetCartSeatPlayer(id, 0) then return 0 end
return true
end
```
## A cartwright
A cartwright does up the player's own cart for a price: a covered body, other wheels, a load, other horses.
```lua
local WORK = {body = 150, axle = 40, wheels = 60, load = 20, horse1 = 100, horse2 = 100}
-- /cartwright , next to your own cart
function cartwright(pid, slot, part)
local cart = PLAYER_CART[pid]
local cx, cy = GetEntityPos(cart or -1)
local px, py = GetPlayerPos(pid)
if not cx or (cx - px) ^ 2 + (cy - py) ^ 2 > 10 * 10 then SendClientMessage(pid, COLOR_RED, "Bring your cart here.") return end
local kind = GetEntityTemplate(cart)
local names = GetCartPartNames(slot, kind)
if not WORK[slot] or #names == 0 then SendClientMessage(pid, COLOR_WHITE, table.concat(GetCartPartSlots(kind), ", ")) return end
if not part then SendClientMessage(pid, COLOR_WHITE, table.concat(names, ", ")) return end
if GetCartPart(cart, slot) == part then return end
if GetPlayerMoney(pid) < WORK[slot] then SendClientMessage(pid, COLOR_RED, "That is " .. WORK[slot] .. " Groschen.") return end
if SetCartPart(cart, slot, part) then GivePlayerMoney(pid, -WORK[slot]) else SendClientMessage(pid, COLOR_RED, "No such " .. slot .. ".") end
end
```
## Making it their own
Past the parts, a cart takes the game's own models on its body and other wood. [`AddCartProp`](/lua/server/functions/addcartprop/)
puts any of [the game's meshes](/reference/meshes/) on it - a barrel in the back, a lantern on the bench - in the
body's own frame (metres to its right, ahead and up), and [`SetCartPartMaterial`](/lua/server/functions/setcartpartmaterial/)
gives a part one of the game's material variants (a darker wagon, new wheels). Admins try both by hand with `/cart prop` and
`/cart material` before a mode sells them.
```lua
-- the painter's shop: a darker wagon with a lantern on its bench
SetCartPartMaterial(cart, "body", "objects/manmade/vehicles/wagons/wagon_b_darker.mtl")
AddCartProp(cart, "objects/manmade/common_illumination/lantern_fancy_a_hanging.cgf", -0.6, 2.4, 1.6)
```
## Their own horse in front of it
A cart's horses are its own unless the mode hitches a player's horse ([`HitchHorse`](/lua/server/functions/hitchhorse/)): the
horse stands in front of the cart with its coat, keeps its owner and saddlebags, and its strength counts in what the team pulls.
A cart made with a horse slot `"none"` has no horse there; with none at all it waits for one. What a cart carries is the mode's
too: [`SetCartLoad`](/lua/server/functions/setcartload/) says how many kilograms, and a heavy cart goes slower and tires its
horses sooner ([`GetCartPull`](/lua/server/functions/getcartpull/) says how far). A horse's type
([`SetHorseType`](/lua/server/functions/sethorsetype/)) makes a good cart horse a slow riding horse, and a racer a poor puller.
```lua
-- /hitch at the cart a player stands by: their own horse in front of it, the wagon's own horses out
local horse = GetPlayerHorse(pid)
if horse and SetCartPart(cart, "horse1", "none") and HitchHorse(cart, horse, 1) then
SetCartPart(cart, "horse2", "none")
SendClientMessage(pid, COLOR_WHITE, "Your horse pulls the wagon alone - go easy with the load.")
end
```
## Keeping a player's cart
The server forgets a cart when it stops, and a mode's tables when it reloads - what a player owns is the mode's to keep, in
its [database](/guides/databases/): the parts (`GetCartParts(cart)` is a plain table of slot names), its props
([`GetCartProps`](/lua/server/functions/getcartprops/)), materials and where the cart stood
([`GetEntityPos`](/lua/server/functions/getentitypos/)). At the player's next login `CreateCart` with those parts brings
it back; at their disconnect the mode saves it and takes it away (`DestroyEntity`) or leaves it standing - a cart left
standing is anyone's to drive unless its reins stay locked.
# A character creation screen
> Let players make their character - the face, the hair and its color, the beard, the skin - on a screen of your game mode's own, with a preview body only they see, and keep the look with their account.
A player's **look** is four parts of the game's characters: the **skin**, the **face**, the **hair** with its color and the
**beard**. The server owns it - the mode sets it with [`SetPlayerLook`](/lua/server/functions/setplayerlook/), everyone
sees it, the player's own game too - and the mode decides how a player gets to choose one. A character creation screen is
the mode's own, in two halves:
- the **server half** (the game mode) sends the parts it offers, opens the screen, takes the look the player settles on in
[`OnPlayerLookChange`](/lua/server/callbacks/onplayerlookchange/) and saves it;
- the **client half** (a [client script](/lua/client/getting-started/) the server sends with the mode) draws the menu,
shows every change on a **preview body** in front of the player that nobody else sees
([`ShowLookPreview`](/lua/client/functions/showlookpreview/)), and asks for the look
([`RequestLookChange`](/lua/client/functions/requestlookchange/)).
The shipped **BasicRP** mode has a complete one: an account's first login without a look goes through it - in a virtual world
of the player's own at the spawn, so nobody sees a half-made character - and into the shared world when it is done; `/look`
opens it again any time, and the look is saved with the account. Its files are the worked example of this page -
`gamemodes/basicrp.lua` and `gamemodes/basicrp/client/creator.lua` in the server folder; `basicrp/client/account.lua` next to
it is the password box a player registers and logs in with (a text field of the script's own: [`OnTextInput`](/lua/client/callbacks/ontextinput/),
and [`QuitGame`](/lua/client/functions/quitgame/) for its Exit).
## 1. The parts
[`GetLookParts(kind [, pattern])`](/lua/server/functions/getlookparts/) lists what a man's look may name, in the game's
order, for `"head"` (the face), `"hair"`, `"beard"` and `"body"` (the skin); [the look parts](/reference/looks/) are
the same lists on paper. Each entry has its `name`, its `style` (what it derives from), its `traits` (the words the game
matches faces, skins and hair by: `Pale`, `Suntanned`, `Posh`, `YoungAdult` ...) and `generic` - `true` for the plain parts
the game gives its crowds, not a named character's, not a wound's.
A few rules make good lists:
- **faces**: the generic heads whose `style` is `""` - the heads, not their variants;
- **hair**: a hair *model* (`x_hair_05`) derives its colors (`m_hair_005_black`, `m_hair_005_blonde` ...) - offer the model as
a style and its generic colors as a second row; `x_hair_20` is a shaved head, `m_hair_barber_01` to `_16` the barber's cuts;
- **beards**: `m_beard_00` is none, the generic `m_beard_*`, and `UC_beard_barber_01` to `_08` the barber's;
- **skins**: the generic `m_body_*` tones (the pale, the tanned and the dark ones, young and old).
- **a woman**: `GetLookParts(kind, pattern, "female")` lists her faces, hair and skins (the generic `f_body_*` and `f_roma_*`
tones; she has no beard). Send both lists and let the player pick the body first; the look the creator asks for says
`gender = "female"`. The preview turns into a woman's body with it, and once the mode takes the look the player's own
game asks them to restart on her body - it loads with the level - and remembers it for the next join. She can do
everything he can: the single-player game's rules that keep women off horses, grindstones and alchemy tables do not
apply to players, so a mode needs nothing extra for her to ride, sharpen or brew.
## 2. The server half
```lua
local creating = {} -- pid -> true while the creator is open
function openCreator(pid, first)
creating[pid] = true
SendClientEvent(pid, "creator_parts", partsLine) -- the lists, once (a plain string of your own format)
SendClientEvent(pid, "creator", (first and "new;" or "edit;") .. LookToString(GetPlayerLook(pid)))
end
-- the player's "done": asked only after the server checked every part
function OnPlayerLookChange(pid, look)
if not creating[pid] then return false end -- no creator open: no look from this player
creating[pid] = nil
db:Execute("UPDATE players SET look = @l WHERE name = @n", { l = LookToString(look), n = GetPlayerName(pid):lower() })
SendClientEvent(pid, "creator", "close")
return true -- the look is the player's, on every screen
end
```
`OnPlayerLookChange` is the only door: **a mode without it takes no look from a player**, so a player cannot give themselves a
look on a server that offers no way to. The server has already checked the parts (a part of that kind, all of them a man's
or all a woman's; one ask a second); the mode checks what it cares about - here, that its creator is open.
The look travels as one line of text, `"body=m_body_tan_02;head=m_head_004;hair=m_hair_005_blonde;beard=m_beard_03"`
([`LookToString`](/lua/server/functions/looktostring/); a woman's starts with `gender=female;`), which fits a `TEXT`
column. At the next visit it goes straight back:
```lua
-- at the login, with the row read from the database
if not (row.look and row.look ~= "" and SetPlayerLook(pid, row.look)) then
openCreator(pid, true) -- no look yet (or a part a game update removed): make one
end
```
The server itself remembers no look past a visit: without the mode's database a look lasts until the player leaves.
## 3. The client half
A file in the mode's `client/` folder (`gamemodes//client/*.lua`, or `gamemodes//client/` next to a single-file
`gamemodes/.lua`); the server sends it to every player who joins.
```lua
local parts, look, row = nil, {}, 1
function OnServerEvent(name, payload)
if name == "creator_parts" then parts = parseParts(payload) end
if name == "creator" then
if payload == "close" then return close() end
local kind, line = payload:match("^(%a+);(.*)$")
look = LookFromString(line) -- the player's look to start from ({} for a new character)
SetKeyboardCapture(true) -- the menu has the keys; the player stands while choosing
drawMenu() -- CreateUiClip / CreateUiRect / CreateUiText under one clip
ShowLookPreview(look, {distance = 1.6}) -- a body in front of the player, dressed as they are
end
end
function OnKey(key, pressed)
if not pressed then return end
if key == "d" then look.hair = nextHair(look.hair) ShowLookPreview(look) redrawMenu() end
if key == "q" then ShowLookPreview(look, {turn = -30}) end
if key == "space" then RequestLookChange(look) end -- the mode's OnPlayerLookChange decides
end
function close()
HideLookPreview()
DestroyUiElement("menu")
SetKeyboardCapture(false)
end
```
What each piece does:
- [`SetKeyboardCapture(true)`](/lua/client/functions/setkeyboardcapture/) gives every key to `OnKey` and the game takes no
input meanwhile - the player neither walks nor turns away from the preview, Enter does not open the chat, Esc is yours to
close the menu with. It ends with `false`, a reload of the scripts or the end of the session; a script whose `OnKey` fails
gives it back by itself.
- [`ShowLookPreview(look, options)`](/lua/client/functions/showlookpreview/) makes the body the first time - where the
player looks, `distance` metres ahead on their floor, facing them - and changes it in place afterwards: the look, the
`distance` (`0.9` is the face, `2.5` the whole body), the `turn` in degrees. Only this player sees it.
- [`RequestLookChange(look)`](/lua/client/functions/requestlookchange/) asks the server; when the mode takes the look,
[`OnLookChange`](/lua/client/callbacks/onlookchange/) tells the script, and [`GetPlayerLook`](/lua/client/functions/getplayerlook/)
answers with it from then on. A mode that confirms with a script event of its own ("close" above) also covers the player who
kept the look they had.
## 4. Where the player stands
The preview body is placed where the player looks when the creator opens. Open it where there is room in front of them - the
spawn of a mode is usually such a place; a mode that wants a set stage moves the player first
([`SetPlayerPos`](/lua/server/functions/setplayerpos/) with a yaw that faces the open side) and opens the creator a moment
later. A player who should not be seen while choosing goes into a [virtual world](/lua/server/functions/setplayervirtualworld/)
of their own and back into `0` when done.
## 5. Checklist
- The server has the tables export (`[combat] tables` in [server.toml](/getting-started/server-config/)): without it
`GetLookParts` is empty and the parts pass unchecked - the [tables guide](/guides/game-tables/) makes the file.
- The mode defines `OnPlayerLookChange` and takes a look only while its creator is open.
- The look is saved as `LookToString`'s line and given back with `SetPlayerLook` at the login.
- The client half gives the keyboard back and hides the preview on every way out: done, cancel, the mode's close.
# Change the interface
> Decide how the chat, the scoreboard, the name plates, the party frames and the rest of KCD:MP's interface look for each player - move them, resize them, hide them, hand them to a web interface, switch the game's own HUD pieces - for one player or for the whole server, from Lua or C#.
Everything KCD:MP draws on a player's screen - the chat at the foot of it, the list of players that Tab shows, the names over
heads, the party frames, the notices a mode sends - is a **component** with a name. A game mode can change two things about a
component, for one player or for everyone:
- **who draws it** - the *drawer*: `native` is KCD:MP's own piece, the default; `hidden` draws nothing; on a server with a
[web interface](/guides/web-interface/), `default` is KCD:MP's web version of the piece and a frame's name hands it to your own page;
- **how it is laid out** - the component's *options*: the chat's corner and number of lines, whether Tab holds or toggles the
scoreboard, where the party frames sit, the colour of the name plates, which of the game's own HUD pieces show, and a few more.
A server that calls none of these functions looks exactly as it always did. The calls are
[`SetPlayerInterface`](/lua/server/functions/setplayerinterface/) and
[`SetInterfaceForAll`](/lua/server/functions/setinterfaceforall/) for the drawer,
[`SetPlayerInterfaceOptions`](/lua/server/functions/setplayerinterfaceoptions/) and
[`SetInterfaceOptionsForAll`](/lua/server/functions/setinterfaceoptionsforall/) for the options, and
[`GetPlayerInterface`](/lua/server/functions/getplayerinterface/) to read a drawer back. The C# names are the same, on
`IServerApi` ([below](#the-same-in-c)).
## The components
| Component | What it is | May be hidden | Web version |
|---|---|---|---|
| `chat` | the lines at the foot of the screen and the box a player types in | yes | yes |
| `scoreboard` | the list of players that Tab shows | yes | yes |
| `notices` | the toasts a mode sends: [`ShowInfoText`](/lua/server/functions/showinfotext/), [`ShowNotification`](/lua/server/functions/shownotification/), [`ShowGameLog`](/lua/server/functions/showgamelog/) and [`ShowObjectiveEvent`](/lua/server/functions/showobjectiveevent/) | yes | yes |
| `party` | the party frames down the side of the screen | yes | yes |
| `emotes` | the emote wheel (G) and the point (T) | yes | yes |
| `hudtext` | [`GameText`](/lua/server/functions/gametext/) lines and [HUD texts](/lua/server/functions/createhudtext/) | yes | yes |
| `sleep` | the line that follows a sleep in a bed | yes | yes |
| `nameplates` | the names, health bars, injury marks and chat bubbles over players' heads | yes | no |
| `compass` | the blips of markers and party members on the game's compass | yes | no |
| `death` | the box a player who has died sees | no | yes |
| `dialog` | the message boxes a player must answer or read - a kick, a refused join, a question | no | yes |
| `loading` | the loading screen | no | yes, chosen in `ui.toml` |
| `shop` | the vendor's list panel | no | yes |
| `voice` | the marks that show who is talking | no | yes |
| `hud` | the game's own bars and prompts | no | no |
The six that may not be hidden are always drawn as they are; asking for `hidden` there is refused with the reason `the shop
cannot be hidden` (or `the death ...`, `the hud ...`).
## The drawers
There are two everywhere, written in any case:
- **`native`** - KCD:MP draws the component. It is what every component starts as.
- **`hidden`** - nothing draws it and its keys stop working (two exceptions: a hidden scoreboard still takes Tab, which the
game never gets during a session, and the party invitation's accept and decline keys stay). What the piece does behind the
scenes carries on:
a hidden chat still collects the lines that arrive, a hidden scoreboard still knows who is on the server, hidden party
frames are still a party. Choosing `native` again shows it as it was. [What `hidden` does](#what-hidden-does) to each
component is below.
To give a drawer back, say `native`: there is no word for "as everyone else has it".
A server with a [web interface](/guides/web-interface/) has two more for every component with a web version (the table above):
- **`default`** - KCD:MP's own web version of the component, styled by the interface's theme: the web chat has channel tabs
and suggests commands as the player types, the web scoreboard shows the columns of
[`SetScoreboardColumns`](/lua/server/functions/setscoreboardcolumns/).
- **the name of a frame** of the interface - your own page draws the component: it receives the component's lines, roster or
texts and its keys, and calls its actions ([Make a web interface](/guides/web-interface/#replacing-a-piece-of-kcdmps-interface)).
A player whose game cannot draw the web drawer - the web layer is off or failed, the page is not ready yet - gets the native
piece meanwhile, and the web one the moment it can draw. The loading screen's drawer is chosen in the interface's `ui.toml`
alone, since it shows before anything the mode sends can arrive.
## The options
An option is a text under a key; the server checks every one against the range below and refuses a call with a wrong word in
it - **nothing changes at all**, not even the good keys of the same call. Numbers may be written with a decimal point (kept to
three places); a colour is a **text** of six hex digits, `"F6E890"` or `"#F6E890"`, not a number; `"default"` (or an empty
text) takes a key back to its default. The other components have no options.
**Sizes and margins are pixels on a screen 1080 pixels tall.** On a taller or shorter screen the piece grows or shrinks with
the height, and a wider screen only gives it more room across: `margin_x = 12` is the same fraction of the screen's **height**
for every player (a 2560x1080 screen and a 1920x1080 screen both get 12 pixels). The scaling stops below 432 and above 2160
rows. `anchor` picks the corner or edge the piece is attached to; the margins count from the edges it touches. `top` and
`bottom` are centred across, `left` and `right` centred down and `center` both - a centred direction has no margin.
### chat
| Option | Values | Default | What it does |
|---|---|---|---|
| `anchor` | `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right` | `bottom-left` | where the chat is attached |
| `margin_x` | 0 to 960 | `12` | the gap to the left or right edge |
| `margin_y` | 0 to 540 | `64` | the gap to the top or bottom edge |
| `width` | 300 to 1200 | `560` | how wide a line is before it wraps |
| `lines` | a whole number, 4 to 20 | `10` | how many lines show at once; Page Up and Page Down scroll back by half of them |
| `backing_alpha` | 0 to 100 | `45` | how dark the frame behind the lines is while the box is closed (`0` none, `100` solid) |
| `backing_alpha_open` | 0 to 100 | `85` | the same while the box is open or the chat is scrolled back |
| `font_scale` | 0.6 to 1.6 | `1` | the size of the text, times the usual size |
### scoreboard
| Option | Values | Default | What it does |
|---|---|---|---|
| `mode` | `hold`, `toggle` | `hold` | `hold` shows the board while Tab is held; `toggle` opens it with Tab and closes it with Tab or Esc (a menu or the loading screen closes it too) |
| `title` | text, at most 64 characters | empty | replaces the `KCD:MP - ` part of the title line; the number of players and the page stay after it. Empty keeps the usual title. A longer text is refused, not cut |
### party
| Option | Values | Default | What it does |
|---|---|---|---|
| `anchor` | `top-left`, `left`, `bottom-left`, `top-right`, `right`, `bottom-right` | `top-left` | which edge the column of frames sits against |
| `margin_x` | 0 to 960 | `24` | the gap to the left or right edge |
| `margin_y` | 0 to 1000 | `173` | the gap to the top or bottom edge - `173` is where the column has always started, a sixth of the way down |
| `show_stamina` | `true`, `false` | `true` | the stamina bar of each member |
### nameplates
| Option | Values | Default | What it does |
|---|---|---|---|
| `color` | six hex digits | `F6E890` | the colour of a player's name when the mode gave them none ([`SetPlayerColor`](/lua/server/functions/setplayercolor/)) |
| `font_scale` | 0.5 to 2 | `1` | the size of the names, the chat bubbles and the trade prompt, times the usual size |
| `bar_width` | 40 to 240 | `120` | how wide the health bar is |
| `text` | `name`, `id`, `name_id` | `name` | what a plate says when the mode gave the player no label ([`SetPlayerNameplate`](/lua/server/functions/setplayernameplate/)): their name, their player id (`3`), or both (`Henry (3)`) |
| `peek_key` | the name of a keyboard key - `lalt`, `lctrl`, `lshift`, `n`, `f4`, `space` ... | empty | the names show only while this key is held; empty shows them always. The health bars, the chat bubbles and a vendor's prompt are not names and follow their own rules |
| `peek_scope` | `all`, `players` | `all` | whose names wait for the `peek_key`: every plate's, or the players' alone - with `players` the labels of the mode's NPC actors (a shopkeeper, a guard, a quest giver) always show |
| `npc_range` | a whole number, 0 to 255 | `0` | how many metres away an NPC actor's name still shows (it fades out over the last third) - a town's shopkeepers readable from across the square, or a guard's name only up close; `0` uses the server's own range for the players' names (`range` under `[ui]` in the [server settings](/getting-started/server-config/#ui)) |
### compass
| Option | Values | Default | What it does |
|---|---|---|---|
| `span` | 30 to 180 | `90` | the degrees of the horizon the compass strip covers - a smaller number spreads the blips further apart |
### hud
One switch for each of the game's own HUD pieces, `1` shown (the default) or `0` hidden (`true`, `false`, `on`, `off`, `yes`
and `no` are read the same): `compass`, `stats` (the health and stamina bars), `subtitles`, `hints` (the "press a key"
prompts), `cursor` (the crosshair dot and the item card), `buffs`, `info_text`, `game_log`, `common_event`, `fancy_event` and
`tutorial_message`. [`SetPlayerHudVisible`](/lua/server/functions/setplayerhudvisible/) switches one piece for one player
and says what each piece is; the last five are where the mode's own toasts show, so hiding them hides those too.
## What `hidden` does
- **`chat`** - the lines and the frame are not drawn. **Enter does not open the chat box** - the key goes to the game - and
Page Up and Page Down are left alone, so a player whose chat is hidden cannot type into it. Use it for a mode that draws its
own chat from a [client script](/lua/client/getting-started/) (which sends what the player types to the server with
`SendServerEvent`), or for a stretch where nobody should talk; a chat of your own on a web page is a frame drawer instead. Lines that arrive meanwhile are kept, so showing the chat
again shows them; a box that is open when the chat is hidden closes.
- **`scoreboard`** - the board is never shown and Tab does nothing (KCD:MP still takes the key, the game does not get it).
- **`notices`** - the game's own toasts from `ShowInfoText`, `ShowNotification`, `ShowGameLog` and `ShowObjectiveEvent` are
dropped. Tutorial boxes ([`ShowTutorial`](/lua/server/functions/showtutorial/), and with them the party invitation and
the death box) and the calls that hide or clear a notice still work.
- **`party`** - the party frames are not drawn. This is the same switch as [`ShowPartyFrames`](/lua/server/functions/showpartyframes/):
`hidden` is `ShowPartyFrames(pid, false)`, `native` is `true`, and each reads the other. The invitation box and its Y and N
keys stay.
- **`emotes`** - the wheel never opens and G and T go back to the game. The wheel's list on the server is untouched, and an
emote a mode plays with [`PlayPlayerEmote`](/lua/server/functions/playplayeremote/) still plays.
- **`hudtext`** - `GameText` lines and HUD texts are not drawn.
- **`sleep`** - the line after a sleep in a bed is not drawn; the sleep itself works as before.
- **`nameplates`** - names, health bars, injury marks and chat bubbles are not drawn. The "[E] Trade" prompt over a vendor stays.
- **`compass`** - the blips are not drawn; the compass itself is the game's.
## Strangers: ids on a key, and names players were told
A server whose players do not know each other - a roleplay server - often shows no names at all: a key shows each player's id,
and "/meet 3 Toto" teaches the player who typed it that id 3 is Toto. The two `nameplates` options above and
[`SetPlayerNameplateFor`](/lua/server/functions/setplayernameplatefor/) are all it takes:
```lua
function OnGameModeInit()
-- a plate says the player's id, and only while Left Alt is held; the NPCs' labels (the shops, the guards) always show
SetInterfaceOptionsForAll("nameplates", { text = "id", peek_key = "lalt", peek_scope = "players" })
end
-- who told whom: the mode keeps it (a table here, a database for a real server) and puts it on the screen when either joins
local told = {} -- told[viewer][target] = "Toto"
local function show(viewer, target)
local name = told[viewer] and told[viewer][target]
SetPlayerNameplateFor(viewer, target, name and (name .. " (" .. target .. ")") or "")
end
function OnPlayerCommandText(pid, cmd, args)
if cmd ~= "meet" then return false end
local id, name = args:match("^(%d+)%s+(.+)$")
id = tonumber(id)
if not id or not IsPlayerConnected(id) then
SendClientMessage(pid, COLOR_RED, "Use /meet ")
return true
end
told[pid] = told[pid] or {}
told[pid][id] = name
show(pid, id)
return true
end
```
The text is for **one viewer's screen alone**; everyone else keeps seeing the plate everyone sees. It lasts until the target
leaves or the mode is reloaded - the id is the next player's to take - so a mode that remembers who knows whom sets it again in
`OnPlayerConnect` for both sides. Holding the peek key while it is also the cursor key (Left Alt unless the player chose another)
does not bring the pointer up.
One player's plate can also go altogether, on every screen:
[`SetPlayerNameplateHidden`](/lua/server/functions/setplayernameplatehidden/). With
[`OnPlayerStanceChange`](/lua/server/callbacks/onplayerstancechange/) a sneaking player shows no name:
```lua
function OnPlayerStanceChange(pid, stance, previous)
SetPlayerNameplateHidden(pid, stance == STANCE_CROUCHING)
end
```
## Who sees what
What a player sees is **their own setting** if the mode made one for them, else the setting made **for everyone**, else the
default.
- `SetInterfaceForAll` and `SetInterfaceOptionsForAll` hold for whoever joins later as well, and they **replace** what the
mode set for one player - the drawer of that component, or those keys - so a call for everyone is a clean start.
- For one player, `native` overrides a `hidden` made for everyone. There is no "as everyone has it" for a drawer; for an
option, `"default"` is exactly that: it takes the key back to what everyone else has (the setting made for everyone, else the
option's own default). A `"default"` in the call for everyone takes it back to the option's own default.
- The `party` starts from `[party] frames` in [server.toml](/getting-started/server-config/#party); `SetInterfaceForAll("party", ...)`
changes what a newcomer gets from then on.
- **A reload of the mode puts everything back** - every drawer native, every option at its default. Set the interface where the
mode is made again: for everyone in `OnGameModeInit`, for one player in `OnPlayerConnect`, and both run again after a reload.
- A player who leaves and comes back starts again from the settings made for everyone; a mode that remembers a player's own
layout keeps it in its [database](/guides/databases/) and sets it in `OnPlayerConnect`.
- Several calls in one tick reach the player as one change, and a call that changes nothing sends nothing. A player who
joins gets the settings made for everyone before their game has finished loading.
- [`GetPlayerInterface`](/lua/server/functions/getplayerinterface/) returns two words for a component, what was asked for
and what draws it now; with `native` and `hidden` they are always the same, and with a web drawer the second is `native` while
the player's game cannot draw the web one.
A refused call answers `false` and the reason in words a person can read, ready for a log or a chat line:
| Reason | When |
|---|---|
| `not connected` | the player is not on the server |
| `unknown component 'x'` | no component of that name |
| `unknown option 'x' for the chat` | no option of that name on that component |
| `the shop cannot be hidden` | the component has no hidden drawer |
| another reason for a drawer | a word that is not a drawer of that component - the reason says what is wrong with it |
| `the compass has no web version yet`, `no interface is loaded`, `no such frame in the interface` | a web drawer the component or the server cannot take |
| `lines must be a whole number from 4 to 20` | a number out of its range or not a whole number where one is needed (`width must be a number from 300 to 1200`) |
| `anchor must be one of top-left, top, ...` | a word that is not on the option's list |
| `show_stamina must be true or false` | a flag that is neither |
| `color must be a colour of six hex digits, like F6E890` | a colour that is not six hex digits |
| `title must be at most 64 characters` | a title that is too long |
## An example in Lua
```lua
-- the chat in the top-right corner with twelve lines, a scoreboard that Tab opens and closes,
-- and a /plates command that switches the name plates off and on for whoever types it
function OnGameModeInit()
-- for everyone, and for whoever joins later; a word the server cannot take answers false and the reason
local ok, why = SetInterfaceOptionsForAll("chat", { anchor = "top-right", margin_x = 16, margin_y = 24, lines = 12 })
if not ok then Log("chat layout refused: " .. why) end
SetInterfaceOptionsForAll("scoreboard", { mode = "toggle", title = "The Kuttenberg Inn" })
end
function OnPlayerCommandText(pid, cmd, args)
if cmd ~= "plates" then return false end
local _, drawer = GetPlayerInterface(pid, "nameplates")
local hide = drawer ~= "hidden"
SetPlayerInterface(pid, "nameplates", hide and "hidden" or "native")
SendClientMessage(pid, COLOR_SERVER, "Name plates " .. (hide and "off" or "on") .. ".")
return true
end
```
A player's own layout, for one player, is the same call with a `pid`:
```lua
SetPlayerInterfaceOptions(pid, "chat", { lines = 6, width = 420, font_scale = 0.8 }) -- a smaller chat
SetPlayerInterfaceOptions(pid, "chat", { lines = "default", width = "default" }) -- and back to what everyone has
```
## The same in C#
The members are on `IServerApi` - `SetPlayerInterface`, `GetPlayerInterface`, `SetInterfaceForAll`, `SetPlayerInterfaceOptions`
and `SetInterfaceOptionsForAll`; the setters return `true` or `false` with the reason in an `out string?`, and the options are a
`IReadOnlyDictionary` - every value is text, as in the tables above. `GetPlayerInterface` returns
`(string Requested, string Effective)?`, `null` for a player who is not connected or a component that does not exist.
```csharp
using KcdMp.Api;
public sealed class InterfaceMode : IGameMode // the members every mode needs (Name, OnShutdown ...) are left out here
{
private IServerApi _api = null!;
public void OnInit(IServerApi api)
{
_api = api;
// for everyone, and for whoever joins later: the chat top-right with twelve lines, a scoreboard Tab opens and closes
var chat = new Dictionary { ["anchor"] = "top-right", ["margin_x"] = "16", ["margin_y"] = "24", ["lines"] = "12" };
if (!api.SetInterfaceOptionsForAll("chat", chat, out var why))
api.Log($"chat layout refused: {why}");
api.SetInterfaceOptionsForAll("scoreboard", new Dictionary { ["mode"] = "toggle" }, out _);
}
public bool OnPlayerCommand(IPlayer player, string command, string args)
{
if (command != "plates") return false;
// /plates: the name plates off and on for the player who types it
var hidden = _api.GetPlayerInterface(player, "nameplates")?.Effective == "hidden";
_api.SetPlayerInterface(player, "nameplates", hidden ? "native" : "hidden", out _);
return true;
}
}
```
## Things to know
- A hidden `chat` cannot be typed into: Enter goes to the game. Hide it only for a mode that has its own way to take text.
- Every word is checked on the server before anything is sent, so a refused call is safe to make from a command a player
can type - log the reason, or tell the player.
- The interface is the mode's: a reload resets it, so it belongs in `OnGameModeInit` and `OnPlayerConnect`.
- The party frames have one switch with two names: `[party] frames` / `ShowPartyFrames` and the `party` component are the same
thing.
# Make a web interface
> Give your server an interface of its own made of web pages - a menu, a shop, a login screen, your own chat or scoreboard, a loading screen - that every player's game draws over the game; the folder, ui.toml, talking to the pages, the keyboard and the mouse, replacing KCD:MP's own pieces, themes, and the players who have no web layer.
A **web interface** is a set of pages - HTML, CSS and JavaScript - that every player's game draws over the game while they
are on your server: a menu, a shop window, a login screen, a map, a chat of your own. KCD:MP carries the browser engine that
draws them (Chromium, through CEF) and starts it only on a server that has a web interface. A server without one looks exactly
as it always did, and a player whose game cannot draw a page keeps the standard interface.
The interface is a folder of files next to the game mode. Each page in it is a **frame**: a layer of its own over the game,
named in the folder's `ui.toml`, which also says what the frame may do. The game mode shows and hides the frames, sends them
data and hears what they send back. A page talks to the game through one object, `KcdMp`, that the game gives every page;
the [web interface SDK](/js/web/) documents it.
This guide builds one frame from start to finish, then covers the keyboard and the mouse, replacing a piece of KCD:MP's own
interface, the look, big files and the players who have no web layer.
## The folder
The interface lives in a folder called `ui`, next to the game mode's `client` folder:
```
gamemodes/
mymode.lua the game mode
mymode/
client/ its client scripts, if it has any
ui/
ui.toml what the interface holds and what each frame may do
panel/
index.html
panel.css
panel.js
```
`[client] ui` in `server.toml` points somewhere else: another folder, or a `.zip` of one, which panel file managers handle
better than the hundreds of files a build tool writes ([server configuration](/getting-started/server-config/#client)).
At start the server checks the folder and prints what it found, with every mistake in `ui.toml` named by its key. An
interface with errors is not sent, and players keep the standard interface. A player's game downloads the interface when they
join and keeps it, so a later join fetches only the files that changed.
## A first frame
### `ui.toml`
```toml
api = 1
name = "My server's interface"
version = "1"
[frames.panel]
page = "panel/index.html"
focus = "input"
events_in = ["panel.*"]
events_out = ["panel.*"]
feeds = ["session"]
```
One `[frames.]` section per frame. This one shows `panel/index.html`, may receive and send events whose names start with
`panel.`, reads the `session` feed (the server's name, the player's name, the ping) and may ask for the keyboard and the mouse
cursor ([below](#the-keyboard-and-the-mouse)).
### The page
`panel/index.html`:
```html
Welcome
```
`panel/panel.js`:
```js
// the game mode's "panel.hello" event: the value is the table it sent, as JSON parsed
KcdMp.onServer("panel.hello", (value) => {
document.getElementById("hello").textContent = "Welcome, " + value.name;
});
// a click sends the game mode an event of the page's own
document.getElementById("buy").addEventListener("click", () => {
KcdMp.emitServer("panel.buy", { item: "bread" });
});
```
`panel.css` is any stylesheet. A page is transparent where it draws nothing, so the game shows through around the box.
### The game mode
```lua
-- the player's web layer is up (also called once right after OnPlayerConnect with the state their game reported)
function OnPlayerWebStatus(pid, status, reason)
if status == "ready" then
ShowPlayerWebFrame(pid, "panel")
end
end
-- the panel's page has loaded and listens
function OnPlayerWebReady(pid, frame)
if frame == "panel" then
SendClientEvent(pid, "panel.hello", { name = GetPlayerName(pid) })
end
end
-- what a page sends: the frame's name comes as the source
function OnClientEvent(pid, name, payload, source)
if source == "panel" and name == "panel.buy" then
local order = JsonDecode(payload)
if type(order) == "table" and order.item == "bread" then
-- check it as any other input from a player, then sell the bread
end
end
end
```
[`ShowPlayerWebFrame`](/lua/server/functions/showplayerwebframe/) and the other calls answer `true` when the request was
handed to the player's game, or `false` and the reason in words: `"no such player"`, `"no web interface"`, `"the player has no
web layer"`, `"no such frame"`. [`HidePlayerWebFrame`](/lua/server/functions/hideplayerwebframe/) hides a frame and keeps its
page alive, so showing it again finds it as it was. [`DestroyPlayerWebFrame`](/lua/server/functions/destroyplayerwebframe/)
closes it instead and frees the memory its page used - for a screen the player opens once, like a login or a character creator;
showing it again loads the page from the start.
## Rules for pages
Every page runs under the same strict security policy in every player's game. Most pages written for a browser work as they
are; these are the differences that catch people out:
- **Scripts come from files.** `` runs; an inline `` (in the game the copy steps aside for the game's own). Then
[`KcdMp.mock`](/js/web/properties/mock/) plays the game's part: events, messages, feeds and keys.
- **In the game.** Turn on **UI developer mode** in the launcher's settings: the F9 window gets a Web tab with every frame's
state, the pages' console, a line to run JavaScript in a frame and a reload button, and the game argument
`-KcdMp_web_dev_url =http://127.0.0.1:/` (the launcher's **Game arguments**) loads a frame from a development
server on your own PC. In this mode the launcher joins only servers on your own list (a favourite, a line of your server list,
this PC).
- **Script errors** stay in the player's own log unless `[client] ui_report_errors = true`: then the mode hears each one in
[`OnPlayerWebError`](/lua/server/callbacks/onplayerweberror/).
- **The types** of the whole `KcdMp` object are in [`KcdMp-web.d.ts`](https://docs.kcd-mp.com/KcdMp-web.d.ts), for TypeScript
and for editors that read it.
## The same in C#
A C# mode has every call above on `IServerApi` and every callback on `IGameMode`, with the same names and the same rules:
`ShowPlayerWebFrame(player, "panel", out var reason)`, `OnPlayerWebStatus(IPlayer, string status, string reason)` and the rest -
[the C# page](/csharp/server/#iserverapi---the-functions) lists them under **Web interface**.
# Knocking a player out and carrying them
> Take a player down without killing them, carry them over your shoulder to somewhere else and put them down - the game's own grab and carry, seen by everyone - and let players carry the dead and the fallen themselves.
A fight does not have to end in a death. A mode can **knock a player out** - they drop where they stand, their screen goes
dark, their health stays as it is - and someone can pick them up, **carry them over a shoulder** to somewhere else and put
them down. It is the game's own grab, carry and put-down: the carrier's body takes the other over the shoulder, walks with
them and lays them down, and every player who has both bodies sees exactly that. The same works for the **dead** - a
character that lies dead, a dead player's corpse - and for NPC actors, which a mode can have carry or be carried.
This page is the whole job: knocking a player out, carrying them, and letting players grab bodies themselves. The names are
in [the Carrying group of the server API](/lua/server/#carrying).
## 1. Knocking out
[`SetPlayerKnockedOut(pid, true, seconds)`](/lua/server/functions/setplayerknockedout/) takes a player down. With
`seconds` they wake by themselves after that long; without, they stay down until the mode wakes them
(`SetPlayerKnockedOut(pid, false)`), they die, leave or respawn. It answers `true`, or `false` and the reason ("They are
riding.") - a rider has to be put off the horse first ([`ThrowPlayerFromHorse`](/lua/server/functions/throwplayerfromhorse/)).
While they are down players cannot fight, ride, take a cart seat, make an emote, start a dice match or open anything; their
health is whatever it was - bleeding goes on, so a long knock-out of a wounded player can still end in a death. The mode is told
twice: [`OnPlayerKnockedOut`](/lua/server/callbacks/onplayerknockedout/) and
[`OnPlayerWake`](/lua/server/callbacks/onplayerwake/) (`"woke"` when the timer ran out, `"mode"`, `"died"`, `"left"`,
`"respawned"`).
A common use is a mode's own rule for a blow that would kill:
```lua
-- a blow that would kill knocks the player out for a minute instead
function OnPlayerDamage(pid, attacker, damage)
if damage >= GetPlayerHealth(pid) and not IsPlayerKnockedOut(pid) then
if SetPlayerKnockedOut(pid, true, 60) then
return GetPlayerHealth(pid) - 1 -- they keep one point of health
end
end
end
```
An NPC actor can be knocked out too ([`SetActorKnockedOut`](/lua/server/functions/setactorknockedout/)): it drops, its
walk and its fight stop, and it can be carried the same way.
## 2. Carrying on the mode's order
[`CarryPlayer(carrier, target)`](/lua/server/functions/carryplayer/) has one player take another. The carrier's game plays the
grab - about three seconds - and then the carry; the target does not have to be knocked out (they show knocked out for the
carry and wake after it), but a player the mode knocked out stays down after the put. Both must be standing in the world close
together (the order reaches six metres) and not busy - riding, in a cart, flying, in a dice match, in a fall or a pull-down,
already carrying or carried. The carrier must also be alive and not knocked out, in a fight, at a shop or in a dialogue (the
player they take may be in a fight, at a shop or in a dialogue). The answer is `true`, or `false` and a reason you can show:
```lua
local ok, why = CarryPlayer(guard, prisoner)
if not ok then SendClientMessage(guard, COLOR_RED, why) end -- "Hans is too far away (8.2 m; 6 m at most)."
```
[`StopCarry(pid)`](/lua/server/functions/stopcarry/) puts them down - `pid` can be the carrier or the carried, and **which of them
may order it is yours to decide**: a `/putdown` command should check `IsPlayerCarrying(pid)` first, or the carried player puts himself down -
with the game's own put-down, or at a place of your choosing, or at once:
```lua
StopCarry(guard) -- the put-down plays, the body lies where the game puts it
StopCarry(guard, cellX, cellY, cellZ, 180) -- ... at the cell, facing south
StopCarry(guard, cellX, cellY, cellZ, 180, true) -- ... at once, no animation
```
A put ordered while the grab still goes on waits for it to finish, then plays. While they carry, the carrier walks at the carry's pace
(2.4 metres a second unless you pass another, at most 4) and cannot sprint, jump, fight, ride or act. The carried player's position is the carrier's - the server does not judge it - and they
cannot act either. When the carry ends, [`OnPlayerCarryEnd`](/lua/server/callbacks/onplayercarryend/) says why: `"put down"`,
`"mode"`, `"hit"` (the game broke it off - a blow on the carrier), a death, a disconnect, `"moved"` (you teleported one of them)
and the rest. Anything you do to one of the two on your own - [`SetPlayerPos`](/lua/server/functions/setplayerpos/), a
respawn, [`MountPlayer`](/lua/server/functions/mountplayer/), a cart seat, another virtual world - ends the carry, so end it
first when you mean to keep it. A carried player is not a free player for your commands either: a `/unstuck` or a `/createcart` that
moves him would end his carry for him, so a mode that means it says no to a knocked-out or carried player first (BasicRP does).
[`IsPlayerCarrying`](/lua/server/functions/isplayercarrying/), [`IsPlayerCarried`](/lua/server/functions/isplayercarried/),
[`GetPlayerCarrying`](/lua/server/functions/getplayercarrying/) and
[`GetPlayerCarriedBy`](/lua/server/functions/getplayercarriedby/) read it back - the last two answer a kind (`"player"`,
`"corpse"` or `"actor"`) and an id.
## 3. Bodies and NPC actors
[`CarryEntity(carrier, id)`](/lua/server/functions/carryentity/) takes an NPC actor's body - dead, or alive and shown knocked out for the
carry - or a dead player's corpse. A player's corpse is a body in the world only when the server keeps it
(`[combat] corpse_seconds` above 0 in `server.toml`): it is then carried like any other, and searched where it was put
down. An NPC actor can carry too: [`ActorCarryEntity(actorId, id)`](/lua/server/functions/actorcarryentity/) and
[`ActorCarryPlayer(actorId, pid)`](/lua/server/functions/actorcarryplayer/) have a guard take a body or a player; walk the
actor with [`MoveActor`](/lua/server/functions/moveactor/) once its grab is done (a few seconds later; it answers `false`
until then) and the body goes with it, then
[`ActorPutDown`](/lua/server/functions/actorputdown/). No callback tells an actor's carry - poll
[`GetEntityCarrier(id)`](/lua/server/functions/getentitycarrier/).
## 4. Letting players grab
With [`SetCarrying(true, rules)`](/lua/server/functions/setcarrying/) the players do it themselves: the game's own "grab"
prompt shows on a body that lies down - a dead character's, a knocked-out player's or actor's - and a player within reach
takes it. It is **off by default**; the rules say what it takes:
```lua
function OnGameModeInit()
SetCarrying(true, {
range = 2.5, -- metres from the body when they reach for it
speed = 2, -- the carrier's pace, metres a second
corpses = true, -- the dead
players = true, -- knocked-out players
actors = false, -- knocked-out NPC actors
})
end
```
The server checks each ask - the reach, that nobody else has the body, that the player is free to - and the mode has the last
word in [`OnPlayerCarry`](/lua/server/callbacks/onplayercarry/); return `false` to refuse:
```lua
-- only the guards (team 1) may carry other players; anyone may carry the dead
function OnPlayerCarry(pid, kind, id, victim)
if kind == "player" and GetPlayerTeam(pid) ~= 1 then
SendClientMessage(pid, COLOR_RED, "Only the guards may carry prisoners.")
return false
end
end
```
[`SetEntityCarryable(id, false)`](/lua/server/functions/setentitycarryable/) takes one body out of the prompt - a quest
corpse that must stay where it lies - while your own orders still carry it. [`OnPlayerCarryStart`](/lua/server/callbacks/onplayercarrystart/)
and [`OnPlayerCarryEnd`](/lua/server/callbacks/onplayercarryend/) are the places to keep score, say a line or save the place the body was
put.
## The example modes
`freeroam.lua` and `basicrp.lua` in the server folder have a `CARRY` table at the top (`enabled = false`, `range`, `speed`,
and what the prompt takes), switch the grab on from it, and answer the commands an admin needs to try it alone: `/ko [player] [seconds]`,
`/wake [player]`, `/carrying on|off`, `/carry ` and `/putdown`. BasicRP lets every player use `/carry` (on a knocked-out
player within the rules' range and a metre more, as the prompt does) and `/putdown` (what they carry themselves) while
`CARRY.enabled` is true; an admin's `/carry` and `/putdown` work in BasicRP whatever `CARRY.enabled` says, and `/carry` takes any
player, knocked out or not. The two modes differ on `/ko`: BasicRP's, given no time, keeps the player down for `CARRY.ko_seconds`
(120 as shipped; `0` = until `/wake`), freeroam's keeps them down until `/wake`. BasicRP answers a knocked-out or carried player's
commands with "Not while you are knocked out or carried." - he may still talk. `duel_arena.lua` keeps it off.
## What to know
- **A player's game must be able to take part.** A game from an older or unsupported build cannot carry or be carried; the server
starts nothing with it, and the order answers `false` with the reason. (It is knocked out all the same: the server holds the
player down, his game just does not show it.)
- **The prompt's state keys are the server's.** The grab is switched on for the clients through the world state `carry`, and a body
taken out of it through that entity's state `carry`; do not set them yourself. A mode reload switches the grab off again - the new
mode's `OnGameModeInit` calls `SetCarrying`.
- **A body the game cannot grab stays where it lay.** And where a carrier puts one down is where the game laid it as long as that is
within a few metres of him, on his level and in the open; if not, the server lays it a step ahead of him.
- **Nothing is saved.** A carry and a knock-out are over when the server stops; a mode that keeps a prisoner knocked out saves
that itself.
- **Women carry and are carried like men.** Nothing in the API treats them differently.
- **Reach.** A player's own grab reaches as far as `range` (3 metres by default) and the server allows a metre more for the
positions being a few frames old; the mode's orders reach six metres.
# Picking pockets
> Let players pick each other's pockets with the game's own Rob prompt, its charge and its wheel, give NPC actors pockets to pick, and decide the rules - who may rob, what is on the wheel and what a caught thief faces.
Players can **pick each other's pockets** with the game's own "Rob": the thief holds **E** at another player's body, the game's
**charge** starts - its roll can still get them caught - and then its **wheel**, the tile game of single player, opens over a few
things the other player carries. That is one **try**. What the wheel takes moves from the victim's bag to the thief's. The
thief's game plays it all on their screen; the server makes the offer and moves what is taken. NPC actors can have pockets too.
Pickpocketing is **off until a mode switches it on**, and the rest is the mode's: who may rob and be robbed, how much is on the
wheel, and what a caught thief or a robbed player gets. The names are in
[the Pickpocketing group of the server API](/lua/server/#pickpocketing).
## 1. Switching it on
[`SetPickpocketing(enabled, rules)`](/lua/server/functions/setpickpocketing/) switches it on or off - **off by default**.
When it is on, the game's Rob prompt shows on every player's body to every other player, and on the NPC actors that have
pockets (section 7). Every key of `rules` is optional; these are the defaults, but for `deny`:
```lua
function OnGameModeInit()
SetPickpocketing(true, {
range = 3, -- metres from the body (0.5 to 6)
cooldown = 30, -- seconds before the same thief may rob the same player again (3600 at most, 0 = none)
max_items = 8, -- stacks on the wheel, the purse counted (11 at most)
max_stack = 5, -- the most of one kind of item on the wheel
behind_only = false, -- true: only from within 70 degrees of straight behind
min_seconds = 2.5, -- the shortest try that may take anything (60 at most)
money = false, -- true: the victim's purse is one more entry on the wheel ...
money_share = 0.1, -- ... holding this share of it (0 to 1) ...
money_cap = 100, -- ... up to this many Groschen (6553.5 at most)
actors = true, -- NPC actors with pockets can be robbed
deny = {"Food", "dagger"}, -- item classes, names or categories never offered (none by default)
})
end
```
A number that cannot be used, or a key left out, is the default; a call without `rules` keeps the earlier ones.
[`GetPickpocketing`](/lua/server/functions/getpickpocketing/) reads the switch and the rules back, and switching it off ends
the tries that run. `range` is what the server allows - the game's own prompt reaches about 2.4 metres. A take that comes sooner
than `min_seconds` is refused and the try ends, so raise that one with care.
## 2. Who may rob and be robbed
While it is on, everyone may rob everyone.
[`SetPlayerPickpocketable(pid, false)`](/lua/server/functions/setplayerpickpocketable/) takes a player's pockets out of it:
the prompt does not show on their body, every ask is refused and a try on them ends.
[`SetPlayerCanPickpocket(pid, false)`](/lua/server/functions/setplayercanpickpocket/) takes the right to rob from a player.
Both last the player's session, through respawns, until the mode is reloaded - so set them where the mode knows a player's role,
usually in [`OnPlayerSpawn`](/lua/server/callbacks/onplayerspawn/):
```lua
-- admins and guards (team 1) cannot be robbed; only the thieves' guild (team 2) robs
function OnPlayerSpawn(pid)
SetPlayerPickpocketable(pid, not (IsPlayerAdmin(pid) or GetPlayerTeam(pid) == 1))
SetPlayerCanPickpocket(pid, GetPlayerTeam(pid) == 2)
end
```
[`IsPlayerPickpocketable`](/lua/server/functions/isplayerpickpocketable/) and
[`CanPlayerPickpocket`](/lua/server/functions/canplayerpickpocket/) read them back. The server does not look at parties or
teams: members of one can rob each other unless the mode says no. A player who is dead, knocked out or carried cannot be robbed
([knocking a player out and carrying them](/guides/carrying-players/)).
## 3. What the server checks
The thief's game asks, and nothing plays until the server grants the try. A refusal reaches the thief alone, with a line in
their chat saying why (each kind of line at most once in fifteen seconds). The server checks that:
- pickpocketing is on, the thief may rob, and both stand in the world, in one virtual world, with the victim's body in the
thief's view;
- neither is busy: on a horse, in a cart, flying, in a dice match, at a shop, in a dialogue, searching a body, knocked out,
carried or carrying someone, in a fall or a pull-down, or in a fight;
- the victim's pockets may be picked, or the NPC actor has pockets and `actors` allows it;
- the thief stands within `range`, behind the victim when `behind_only` is set, and - with the level's
[collision geometry](/guides/collision/) and [`[validation] line_of_sight`](/getting-started/server-config/#validation) on - with
no wall or closed door between them;
- nobody else is robbing the victim, and the thief did not rob them in the last `cooldown` seconds, counted from the start of
that try whatever it ended in;
- there is something to take: a victim who carries only what they wear has nothing on the wheel.
Then the mode has the last word. [`OnPlayerPickpocketStart(pid, victim, actor)`](/lua/server/callbacks/onplayerpickpocketstart/)
is called only when all of that has passed, and `false` refuses (a refusal starts no cooldown) - the place for a rule of the
mode's own, such as a safe zone or a guard in sight. `victim` is the robbed player's id and `actor` is `-1`, or the other way
round for an NPC actor.
## 4. What is on the wheel
The server makes the **offer** when it grants the try, and the thief's game plays over exactly that. For a player it is what they
carry - **never what they wear or hold in a hand**: one stack for each kind of item, at most `max_stack` of it, shuffled, and at
most `max_items` stacks. With `money` the victim's purse is one more entry in the shuffle, so a full bag can leave it off the
wheel (`deny` does not reach it). The game's quick-slot containers, item aliases, NPC tools and key rings are never in a pocket,
and `deny` keeps out whatever else you name: an item class, an item name or a category of
[the item catalogue](/reference/items/).
The mode cannot add to the wheel, and the odds of being caught are the game's own roll. It can refuse the try, keep things off
with `deny`, or veto a stack afterwards.
## 5. What the thief takes
When the wheel ends, the thief's game says which of the offered stacks it took. The server refuses a claim that names a stack the
offer did not hold, one twice, more than was offered, one sooner than `min_seconds` after the grant, or one after ninety seconds:
nothing moves and the try ends as `"cancelled"`. Otherwise each stack goes to the mode first -
[`OnPlayerPickpocket(pid, victim, actor, class, amount)`](/lua/server/callbacks/onplayerpickpocket/) - and `false` leaves
that stack where it is:
```lua
-- outside the thieves' guild (team 2) a thief takes coin only
function OnPlayerPickpocket(pid, victim, actor, class, amount)
local info = GetItemInfo(class)
if GetPlayerTeam(pid) ~= 2 and not (info and info.category == "Money") then
return false
end
end
```
`amount` is what would move: it is already cut to what the victim still holds, since they may have eaten, sold or put on
something since the offer. The purse is the [Groschen](/reference/items/money/) class, and its `amount` is in **tenths** of a
Groschen - `25` is 2.5 - while `money_cap` and the money functions count Groschen. A veto says nothing by itself, so tell the
thief why. What is left moves: the victim's game gives up those items and the thief gets exactly what it gave up. It is the
server's own move, so the inventory audit explains both sides and nobody is flagged.
## 6. How a try ends
[`OnPlayerPickpocketEnd(pid, victim, actor, outcome, reason, taken)`](/lua/server/callbacks/onplayerpickpocketend/) hears
every ending. `outcome` is `"took"` (`taken` stacks moved), `"nothing"` (the wheel ended with nothing taken, or everything was
vetoed or gone), `"caught"` or `"cancelled"` (the try was broken off). A caught thief is one the game's own roll caught in the
charge, or who failed the tile game: **nothing moves, and what they face is the mode's to decide.**
`reason` says why. For the first three it is the game's own. A caught thief usually says `"detected"` (the victim noticed) or
`"seen"` (someone saw); `"lost"` is the thief walking off or looking away, the game's own break without a crime - so check the
reason before you punish. For `"cancelled"` it is `"broken off"` (the thief's game), one of the server's words - a death, a
departure, a teleport, a mount, a cart, flying, a knock-out, being carried or `"carrying"` someone, a fight, ninety seconds, a
refused claim; the callback's page lists them - or what the
mode gave [`CancelPickpocket(pid, reason)`](/lua/server/functions/cancelpickpocket/), `"mode"` when nothing. That call ends
the try a player is in, the thief or the one robbed, and the thief's game ends its minigame at once.
[`GetPickpocketTarget(pid)`](/lua/server/functions/getpickpockettarget/) answers whom a thief robs now: `"player"` or
`"actor"`, and the id.
The server sends the victim no message. Telling them, fining the thief, calling the guards, or a fight with
[`StartFight`](/lua/server/functions/startfight/) (it needs `[combat] pvp`) are all the mode's. A player in a fight can
neither rob nor be robbed, and a fight that begins ends their try:
```lua
function OnPlayerPickpocketEnd(pid, victim, actor, outcome, reason, taken)
if victim < 0 then return end -- an NPC actor's pockets: nobody to tell
if outcome == "took" then
SendClientMessage(victim, COLOR_SERVER, "Your pockets feel lighter.")
elseif outcome == "caught" and (reason == "detected" or reason == "seen") then
local fine = math.min(20, GetPlayerMoney(pid)) -- the thief pays the victim, up to 20 Groschen
if fine > 0 then
GivePlayerMoney(pid, -fine)
GivePlayerMoney(victim, fine)
SendClientMessage(victim, COLOR_SERVER, GetPlayerName(pid) .. " was caught and paid you " .. fine .. " Groschen.")
end
end
end
```
## 7. NPC actors' pockets
[`SetActorPocket(actorId, items, money)`](/lua/server/functions/setactorpocket/) gives an NPC actor pockets: a list of
`{class = ..., amount = ..., health = ...}` (`health` 0 to 100, `100` when left out) and a purse in Groschen:
```lua
local merchant = CreateActor(nil, x, y, z, 180, "Merchant") -- x, y, z: where it stands
SetActorPocket(merchant, {
{class = "bread", amount = 3},
{class = "apple", amount = 5, health = 80},
}, 12.5) -- and 12.5 Groschen in its purse
```
The prompt shows on an actor with pockets while pickpocketing is on. The offer is made from the list with the same `max_items`,
`max_stack` and `deny`, and the purse is offered whole, up to `money_cap`, whatever `money` says. With `actors` at `false` every
ask for an actor is refused. A take shrinks the pockets and the mode refills them:
[`GetActorPocket`](/lua/server/functions/getactorpocket/) reads what is left, and in `OnPlayerPickpocketEnd` the actor comes
as `actor`, with `victim` at `-1`. An empty list is empty pockets (the thief is told there is nothing to take); `nil` takes them
away.
## The example modes
`freeroam.lua` and `basicrp.lua` keep six of the rules above (`range`, `cooldown`, `max_items`, `max_stack`, `behind_only`,
`money`) in a table (`POCKET` in Freeroam, `PICKPOCKET` in BasicRP, both with `enabled = false`) and switch pickpocketing on
from it; both answer an admin's `/pickpocket on|off|status`. There is no command to rob - the game's prompt is the way.
Freeroam has nothing for a robbed player or a caught thief. BasicRP allows only robbing from behind (`behind_only = true`)
and adds `admins_robbable`, `on_robbed` (`"none"` by default, `"notify"` tells a robbed player) and `on_caught` (for a thief
the victim noticed: `"notify"`, the default, tells both, `"fight"` starts a fight too, `"none"` says nothing); its
`/pickpocket admins on|off` lets admins be robbed. BasicRP ships with every player an admin (`EVERYONE_ADMIN`), so out of the
box nobody can be robbed there: turn that off, set `admins_robbable`, or use `/pickpocket admins on`. `duel_arena.lua` never
switches pickpocketing on.
## What to know
- **A player's game must be able to take part.** A game from an older or unsupported build cannot rob: the server grants it
nothing and says so in its chat. Women rob and are robbed like men.
- **Export the game's tables first.** Without [the game's tables](/guides/game-tables/) the server cannot tell a quick-slot container
or a key ring from any other item, and only class GUIDs work in `deny` and `SetActorPocket`.
- **The prompt's state keys are the server's.** The world state `pocket`, a player's `pocket` and `steal` and an actor's `pocket`
show and hide it; do not set them with [`SetGlobalState`](/lua/server/functions/setglobalstate/),
[`SetPlayerState`](/lua/server/functions/setplayerstate/) or [`SetEntityState`](/lua/server/functions/setentitystate/).
- **A mode reload switches it off again.** Pickpocketing goes back to off with the default rules, everyone can rob and be robbed,
the actors' pockets are gone and a try that runs ends without a word to the mode. The new mode's `OnGameModeInit` sets what it
wants, and it hears `OnPlayerConnect` and, for players in the world, `OnPlayerSpawn` again, so rights set there are set again.
- **Nothing is saved.** Tries, rights, cooldowns and pockets are gone when the server stops.
# Throwing riders and pulling them from the saddle
> A rider thrown by a low branch, a beam or their horse, thrown on the mode's order, or pulled off by a player on foot with the game's take-down move - the fall every player sees, the switches, the chance and the rules, and what the mode hears.
A rider does not have to stay in the saddle. In the game, a rider whose head meets a low branch or a beam at speed is thrown, a
horse can throw its rider, and a character on foot can take a rider down with the game's take-down move. All of it is the
game's own: the fall, the lying still and the get-up play on the rider's screen, and every player who has their body sees the
same fall on it.
A mode decides **whether** it happens, **who** may do it, **how likely** it is and **what it costs**, and can throw or pull a
rider on its own order. Throws are **on** by default, as in the game; pulling riders down is **off** until a mode allows it.
The names are in [the Horses group of the server API](/lua/server/#horses).
## 1. The fall
The rider's own game plays the throw. The server checks it against the horse they rode, has every game that shows their body
play the same fall on it, and calls [`OnPlayerThrown(pid, id, cause, flung)`](/lua/server/callbacks/onplayerthrown/). `id`
is the horse they fell from. `cause` is `THROW_HEAD_HIT` (a branch or a beam), `THROW_HORSE`, `THROW_MODE` (your own order,
section 2) or `THROW_OTHER` (their game threw them and the server could not tell why) - [the constants](/lua/server/constants/#rider-throws).
`flung` is `true` for the game's strong throw, the one a rearing horse gives, backward off the horse's rear. The server accepts a
throw of the game's own only from the horse it knows they rode, and not within three seconds of their last one: any other is just
the rider's own fall on their screen - the others do not see it and the mode is not told.
The callback runs **before** [`OnPlayerDismount`](/lua/server/callbacks/onplayerdismount/) of the same fall, and the server
may still have the rider in the saddle when it does: use `id`, not [`GetPlayerMount`](/lua/server/functions/getplayermount/).
What the fall costs the rider - health, a wound - is for the mode to decide here:
```lua
-- a fall hurts: 10 health, 30 when they were flung - never a death
function OnPlayerThrown(pid, id, cause, flung)
SetPlayerHealth(pid, math.max(1, GetPlayerHealth(pid) - (flung and 30 or 10)))
if cause == THROW_HEAD_HIT then SendClientMessage(pid, COLOR_RED, "Mind the branches!") end
end
```
[`IsPlayerThrown(pid)`](/lua/server/functions/isplayerthrown/) is `true` while the fall lasts: about six seconds, eight when
flung, a little longer while their game still shows the fall (twelve at most), or less if they are in the saddle again by then.
The server lets the rider's body move at a horse's speed meanwhile - a body tumbling along the ground is no speed hack. A death,
a respawn, [`SetPlayerPos`](/lua/server/functions/setplayerpos/) or a new ride ends the allowance at once.
## 2. Throwing a rider on the mode's order
[`ThrowPlayerFromHorse(pid [, flung])`](/lua/server/functions/throwplayerfromhorse/) has a rider's game throw them off their
horse, the same fall seen by everyone. By default they are flung backward off the horse's rear; with `flung` set to `false`
they are dropped where they sit, as a head hit does. It answers `true`, or `false` and a reason you can show: they are not on a
horse, are dead, are already falling or being pulled off, the rules say no (section 3), or their game cannot play the throw.
The order goes to the rider's game, the throw follows a moment later and `OnPlayerThrown` (`THROW_MODE`) says it happened. A game
that declines it never answers: the order lapses after two seconds and the rider stays in the saddle.
```lua
-- a smashing blow on a rider knocks them off the horse
function OnPlayerDamage(pid, attacker, damage, zone, part, weapon, dtype)
if dtype == "smash" and damage >= 25 and GetPlayerMount(pid) then
ThrowPlayerFromHorse(pid)
end
end
```
A rider cannot be knocked out or carried while they ride. Throw them first, and knock them out when the fall is over
([the carrying guide](/guides/carrying-players/)).
## 3. Switching the throws off
Two switches, per rider, both on by default. [`SetPlayerRiderHeadHit(pid, false)`](/lua/server/functions/setplayerriderheadhit/)
lets the rider pass under a low branch or a beam; a horse that throws them and your own `ThrowPlayerFromHorse` still work.
[`SetPlayerRiderThrow(pid, false)`](/lua/server/functions/setplayerriderthrow/) stops every throw: the head hit, the horse and
`ThrowPlayerFromHorse`, which then answers `false` and the reason. A pull-down is no throw and neither switch changes it.
They last until you change them or the player leaves, through respawns, and go to the player's game with every spawn. Set them
in `OnPlayerSpawn`:
```lua
local MASTERS = 2 -- the team of the horse masters
function OnPlayerSpawn(pid)
SetPlayerRiderHeadHit(pid, false) -- the branches are only scenery here
SetPlayerRiderThrow(pid, GetPlayerTeam(pid) ~= MASTERS) -- and nothing at all throws a horse master
end
```
## 4. Letting players pull riders down
The game's own take-down move: a player on foot takes hold of a rider from the horse's side, the two play the game's pair, and
the rider is thrown off - or, when the pull fails, stays in the saddle. Everyone who has the puller, the rider and the horse in
view sees it. It is no blow: it does no damage and [`OnPlayerDamage`](/lua/server/callbacks/onplayerdamage/) does not run.
It is **off for everyone** until a mode allows it, player by player. [`SetPlayerPullDown(pid, true)`](/lua/server/functions/setplayerpulldown/)
shows the game's "Pull down" prompt on a rider for that player - on foot, close to the horse, with a weapon drawn or
bare-handed - and [`GetPlayerPullDown`](/lua/server/functions/getplayerpulldown/) reads the right back. Set it in
`OnPlayerSpawn` like the throw switches: `SetPlayerPullDown(pid, GetPlayerTeam(pid) == 1)` lets team 1 do it. The rider needs
nothing set: anyone in the saddle can be pulled down by a player you allowed, unless your code says otherwise.
When a player uses the prompt, the server checks the ask before your code hears of it:
- the player has the right and is on foot, the rider is in the saddle, both are alive and standing in the world in one virtual
world, and neither is in a pull-down, a fall, a cart, flying, a dice match, a shop or a dialogue. Nothing else about a player is
looked at - not a knock-out, not a carry, not carrying someone - so a mode that wants those refused says no in
`OnPlayerPullDown`;
- the two are in view of each other;
- pvp is on and they are neither teammates nor party members, the rules a blow follows (`[combat] pvp`,
[`SetPlayerTeam`](/lua/server/functions/setplayerteam/), `[party] friendly_fire`);
- the weapon can do it, and the horse is near enough and slow enough for it (below);
- no wall is between them, when `[validation] line_of_sight` is on ([the collision guide](/guides/collision/); it is off by default).
The reach is the distance from the puller to the horse, and the pace is the horse's. Both come from the game's own numbers for
the weapon, with 1.5 m and 1 m/s more allowed because the positions the server goes by are a few frames old:
| In the puller's hand | Reach | The horse's pace, at most |
|---|---|---|
| bare hands, or a weapon not drawn | 3.5 m | 5 m/s |
| a sword, sabre, axe or mace | 4 m | 7 m/s |
| a longsword | 4.5 m | 8 m/s |
| a halberd | 5.5 m | 9 m/s |
A dagger, a flail, a shield, a torch, a bow or a gun cannot pull anyone down. Without [the tables export](/guides/game-tables/)
(`[combat] tables`) the server cannot tell weapons apart and gives any drawn weapon but a torch a sword's numbers.
Then your code has the last word. [`OnPlayerPullDown(pid, victim, id)`](/lua/server/callbacks/onplayerpulldown/) - the
puller, the rider, the horse - decides with what it returns:
| Return | The pull |
|---|---|
| `false` | is refused, and nothing plays |
| a number from `0` to `1` | works with that chance: `1` always, `0` plays the failed pair (a number outside it counts as the nearer end) |
| nothing, `nil`, `true` | works, as `1` |
In C# the callback returns the chance as a number, and a negative one refuses ([the C# API](/csharp/server/)). The
server rolls the outcome itself, so every screen plays the same one. A wait between two pulls, a protection time after a fall and
a price belong here:
```lua
function OnPlayerPullDown(pid, victim, id)
local now = GetServerTime()
if now - (GetPlayerData(pid, "pulled_at") or -60000) < 20000 then return false end -- 20 s between a player's pulls
if now - (GetPlayerData(victim, "fell_at") or -60000) < 30000 then return false end -- a rider is safe for 30 s after a fall
SetPlayerData(pid, "pulled_at", now)
return 0.7 -- seven pulls in ten work
end
function OnPlayerPulledDown(pid, victim, id, success)
if success then SetPlayerData(victim, "fell_at", GetServerTime()) end
end
```
[`OnPlayerPulledDown(pid, victim, id, success)`](/lua/server/callbacks/onplayerpulleddown/) tells how it ended. `success` is
`true` when the rider was thrown off - before the `OnPlayerDismount` of that fall - and `false` when the pull failed and the rider
is still in the saddle (told when the pair ends, about four and a half seconds after the start). It does not run when the
pull-down is cut short before its outcome shows - a death, a disconnect, `SetPlayerPos`, a respawn, a cart seat, a flight - nor
when a pull that was to land never takes the rider out of the saddle. So what you set in `OnPlayerPullDown` should expire by
time, not wait for this callback.
[`IsPlayerInPullDown`](/lua/server/functions/isplayerinpulldown/) is `true` for both players until a couple of seconds after
the pair ends. Meanwhile the server lets both bodies move as the pair moves them, within about eight metres of where they stood.
## 5. Pulling a rider down on the mode's order
[`PullPlayerFromHorse(victim, puller [, success])`](/lua/server/functions/pullplayerfromhorse/) is the same move on your word.
It skips the weapon's reach and the horse's pace, pvp, teams and parties, the wall check, the rider's `SetPlayerRiderThrow`
setting, the puller's right and `OnPlayerPullDown`. `success` set to `false` plays the failed pair and the rider stays in the
saddle. [`OnPlayerPulledDown`](/lua/server/callbacks/onplayerpulleddown/) tells the outcome as for any pull.
It answers `true`, or `false` and a reason ("The rider is not on a horse.", "They are 9.1 m apart (6 m at most)."). Both players
must meet the first two points above - standing, alive, not busy and in view of each other - and the puller be within six
metres of the horse.
```lua
-- /unhorse : a guard beside a rider pulls them off the horse
function OnPlayerCommandText(pid, cmd, args)
if cmd ~= "unhorse" then return false end
local rider, why = sscanf(args, "u")
if rider == false then
SendClientMessage(pid, COLOR_RED, "usage: /unhorse - " .. why)
else
local ok, reason = PullPlayerFromHorse(rider, pid)
if not ok then SendClientMessage(pid, COLOR_RED, reason) end
end
return true
end
```
## The example modes
`freeroam.lua` and `basicrp.lua` in the server folder have a `PULLDOWN` table (`enabled = false`, `chance`), hand the right out
from it in `OnPlayerSpawn` and answer `OnPlayerPullDown`. Freeroam's chance is `1.0`. BasicRP's is `0.7`, with `cooldown = 20`
seconds between one player's pulls, `protect_seconds = 30` in which a rider who was pulled off cannot be pulled again, and
`damage = 0` health the fall takes off the rider (never below 1); its `OnPlayerPulledDown` tells the rider who did it. BasicRP
has no command: the game's prompt is the way. Freeroam gives admins `/throw [player] [plain]`, `/pulldown [puller]
[fail]` and `/pulldowns on|off` to try it alone (`/horse` first, for a rider). Neither mode switches the throws off, and
`duel_arena.lua` leaves all of it alone.
## What to know
- **A player's game must be able to take part.** A game from an older or unsupported build cannot be thrown on your order or be
the puller of a pull-down: the call answers `false` with the reason. A rider's or a watcher's game that cannot play the pair
still takes the rider off the horse when a pull lands, without the animation, and shows nothing of one that fails.
- **A reload does not undo the switches.** A new join starts with throws on and pull-downs off, and nothing is saved. A mode
reload switches none of the three back - the new mode's `OnPlayerSpawn` runs again for everyone in the world, so set them
there, `false` included. Lua has no getter for the two throw switches.
- **A pull-down is a fight.** The two are in a fight from the start ([`OnFightStart`](/lua/server/callbacks/onfightstart/):
`"hit"` for a player's pull, `"mode"` for your order), which ends as any fight does. With `[combat] pvp = false` your order still
plays but starts none.
- **The horse is left alone.** After a throw or a pull it stays in the world, its owner's, with nobody in the saddle. While a
pull-down runs - about six and a half seconds - [`DestroyEntity`](/lua/server/functions/destroyentity/) and
[`SetEntityController`](/lua/server/functions/setentitycontroller/) on it wait until it is over, and
[`MountPlayer`](/lua/server/functions/mountplayer/) onto it answers `false` unless it is already the player's to control.
- **A refusal is told to the puller, never to the rider.** When pvp, a team, a party, the weapon, the reach or the pace stops a
pull, or `OnPlayerPullDown` returns `false`, the server adds a line to the puller's chat ("Hans is too far to pull off the
horse.", "You cannot pull Hans off the horse." for your own `false`), once per line every 15 seconds at most. A wall, a busy
player or a missing right only reach the server log.
- **An error in `OnPlayerPullDown` lets the pull through.** A script error is logged and the call counts as returning nothing,
which is a chance of `1` ([when a script fails](/lua/server/getting-started/#when-a-script-fails)). Put the refusals first.
# Keeping what a player discovered
> Keep the places a character found on the map and the books they read - and how far they studied each - in your game mode's own database, and put them back at the next login.
A character finds things as they play. The **places** they find - villages, towns, camps and other points of interest - show on
the map with their names. The **books** they read are remembered, with how far they studied each. By default a server keeps none
of it: it lasts as long as the visit, and a new visit starts with a map that shows the whole land and no names.
A game mode can keep it. It turns **tracking** on, the server records what each player's game reports, and the mode **saves**
that record in its own database and **puts it back** at the next login - quietly, with no "discovered" notice. It is the way a
mode keeps a character's [stats and skills](/lua/server/#stats-skills-xp-and-perks) too, and it needs the mode's
[database](/guides/databases/). The names are in
[the Discovery group of the server API](/lua/server/#discovery---the-map-and-the-places-a-player-found). The examples
are Lua; a [C# mode](/csharp/server/#iserverapi---the-functions) has the same calls on `IServerApi`.
## 1. What is kept
Each thing is a **kind**. The words are `"places"`, `"reading"`, `"ui"` and `"fog"`, and the constants `DISCOVERY_PLACES`,
`DISCOVERY_READING`, `DISCOVERY_UI` and `DISCOVERY_FOG` are the same ([listed here](/lua/server/constants/#discovery-kinds)):
| Kind | What it holds | Kept |
|---|---|---|
| `places` | the named places (villages, towns, camps), the points of interest and the kinds of them | per level |
| `reading` | the books they read and how far they studied each | once for the whole game |
| `ui` | the map pin they placed and the legend categories they switched off | per level |
| `fog` | the parts of the world map they uncovered - see [The map](#what-to-know) | per level |
Once a kind is tracked, a player's game reports what it holds of it a few seconds after each spawn and again when it changes. The
server keeps that as the player's **record**, a **union** that only grows: every report is added to what the record holds, and
nothing a report leaves out is taken away, so a late report or a half-finished restore can never make it smaller. What you save
is **data the server keeps for you** - for each kind a text in a fixed, versioned layout, ready for a database column. Never edit
it. A record saved today is still readable after a server update.
## 2. Turning it on
[`SetDiscoveryTracking(kinds)`](/lua/server/functions/setdiscoverytracking/) names the kinds the players' games report:
`true` for every kind, one name, or a list of names. `nil`, `false` and `{}` track nothing. Call it when the mode starts. It
answers `false`, and changes nothing, when a word is no kind.
```lua
function OnGameModeInit()
SetDiscoveryTracking({ DISCOVERY_PLACES, DISCOVERY_READING })
end
```
**Nothing is tracked until the mode says so.** With nothing tracked, no message is sent and no player's game does any work. A
reload of the mode starts tracking from nothing again - the new mode sets its own - but the players' records stay.
[`SetPlayerDiscoveryTracking(pid, kinds)`](/lua/server/functions/setplayerdiscoverytracking/) gives one player a set of their
own: `{}` in `OnPlayerConnect` keeps a guest's game from reporting anything, `nil` hands them back to the server-wide set.
[`GetDiscoveryTracking`](/lua/server/functions/getdiscoverytracking/) and
[`GetPlayerDiscoveryTracking`](/lua/server/functions/getplayerdiscoverytracking/) read them back.
## 3. Saving a character
[`GetPlayerDiscovery(pid)`](/lua/server/functions/getplayerdiscovery/) answers the player's record:
```lua
local record = GetPlayerDiscovery(pid)
-- record.level "klaster" - the level the blocks are of
-- record.capable { "places", "reading" } - what this player's game can do (empty until its first report)
-- record.places { format = 1, data = "", bytes = 612, updated = 1761994000 }
-- record.reading { format = 1, data = "", bytes = 88, updated = 1761994120 }
```
A kind with nothing reported or set is absent, so test for it (`if record.places then`). `data` is the text to save, up to about
64,000 characters, which fits a `TEXT` column. `format` is the version of its layout (1 today): save it with the data and give it
back as it was. `bytes` and `updated` (seconds since 1970) say how big the block is and when it last changed. A connected player
always has a record, empty until their game has reported or the mode has set something, so read it again after the game has
reported, not before.
**When to save.** [`OnPlayerDiscoveryChange(pid, kind, level)`](/lua/server/callbacks/onplayerdiscoverychange/) says a kind
grew, or that the player's game reported it for the first time, and the record already holds the new state. It runs at most once
a kind every five seconds - a walk across the map is a few calls - and not for the mode's own
[`SetPlayerDiscovery`](/lua/server/functions/setplayerdiscovery/). `level` is empty for the reading. Save on a timer too, and
in [`OnPlayerDisconnect`](/lua/server/callbacks/onplayerdisconnect/), where the record holds what the player found in their
last seconds. A change still waiting for its five seconds is told just before the goodbye, and before a reloaded or stopped
mode's `OnGameModeExit`, so a mode that saves only what it was told misses nothing. Write only the blocks that differ from your
last save.
**Never write before the restore has gone out.** Until then the record holds only what this game reports - nothing before its
first report - and saving that over the stored character would lose it. So write only once the saved record was put back
(section 4); from then on the record is the saved one united with the game's, and what you write is never less than what was
stored. A character with nothing saved yet has nothing to put back: write once
[`IsPlayerDiscoveryKnown(pid, kind)`](/lua/server/functions/isplayerdiscoveryknown/) says its game has reported, so the
first save is the game's own state.
The mode below keeps one row per character, level and kind, the books under an empty level. It keys a character by name for
brevity (a mode with accounts uses its account's id) and uses the database calls of the [databases guide](/guides/databases/):
```lua
local db = GetDatabase()
local KEPT = { DISCOVERY_PLACES, DISCOVERY_READING } -- the kinds this mode keeps
-- pid -> { who = the character, ready = may be written, fresh = nothing saved yet, dirty = grew since the last save,
-- stored = { ["kind|level"] = the text last written or read } }
local players = {}
function OnGameModeInit()
if not db then Log("no database: what players discover is not kept") return end
db:ExecuteSync("CREATE TABLE IF NOT EXISTS player_discovery (who VARCHAR(24) NOT NULL, level VARCHAR(32) NOT NULL, " ..
"kind VARCHAR(12) NOT NULL, format SMALLINT NOT NULL, data TEXT NOT NULL, PRIMARY KEY (who, level, kind))")
SetDiscoveryTracking(KEPT)
SetTimer(saveAllDiscovery, 60000, true) -- every minute, for whoever found something since
end
function OnPlayerConnect(pid)
players[pid] = { stored = {} }
restoreDiscovery(pid, GetPlayerName(pid):lower()) -- section 4; with passwords, once the password is right
end
function OnPlayerDiscoveryChange(pid, kind, level)
if players[pid] then players[pid].dirty = true end
end
function OnPlayerDisconnect(pid, reason)
saveDiscovery(pid) -- what they found in their last seconds is in the record
players[pid] = nil
end
function OnGameModeExit() -- the server stops, or the mode is reloaded
for pid in pairs(players) do saveDiscovery(pid) end
end
```
The other half writes. The minute's round writes the characters marked `dirty`, and the goodbye writes whoever leaves. A block
goes out only when its text differs from the one in `stored`, and a failed write puts everything back to be written again:
```lua
local function reported(pid) -- has their game reported any kind we keep?
for _, kind in ipairs(KEPT) do
if IsPlayerDiscoveryKnown(pid, kind) then return true end
end
end
function saveDiscovery(pid)
local p = players[pid]
if not (p and p.who) then return end
if p.fresh and reported(pid) then p.ready = true end -- a new character: its game's own first report is the start
local record = p.ready and GetPlayerDiscovery(pid) -- else nothing is written: the saved record is not back yet
if not record then return end
p.dirty = nil
local batch = {}
for _, kind in ipairs(KEPT) do
local block = record[kind]
local level = kind == DISCOVERY_READING and "" or record.level -- the books are the same on every level
local key = kind .. "|" .. level
if block and block.data ~= p.stored[key] then -- only what differs from the last save
p.stored[key] = block.data
batch[#batch + 1] = { "DELETE FROM player_discovery WHERE who = @w AND level = @l AND kind = @k",
{ w = p.who, l = level, k = kind } }
batch[#batch + 1] = { "INSERT INTO player_discovery (who, level, kind, format, data) VALUES (@w, @l, @k, @f, @d)",
{ w = p.who, l = level, k = kind, f = block.format, d = block.data } }
end
end
if #batch == 0 then return end
db:Batch(batch, function(ok, err)
if ok then return end
Log("player_discovery not saved: " .. tostring(err))
p.stored, p.dirty = {}, true -- the next round writes it all again
end)
end
function saveAllDiscovery()
for pid, p in pairs(players) do
if p.dirty then saveDiscovery(pid) end
end
end
```
## 4. Putting it back at the login
[`SetPlayerDiscovery(pid, record)`](/lua/server/functions/setplayerdiscovery/) takes a record as `GetPlayerDiscovery` gives
it - `{ level = "klaster", places = { format = 1, data = "..." }, reading = { ... } }` - and the kinds it lacks are left alone.
The server takes it at once (`GetPlayerDiscovery` right afterwards answers it) and sends it to the player's game, which **adds**
it to what it already holds. The game ends up with the saved discoveries and anything found since the player joined, and no
notice of a discovery shows. A restore made before the player is in the world waits for their spawn, so the login may come
first, and a game that reports before the restore lands is no trouble: the two are united.
It answers `false` for a player who is not there, or when a block was skipped - text that is no block of that kind, a format the
server does not know, a block over the size limit. The other blocks are applied all the same. A skipped block is usually a saved
row you cannot read, so the mode below writes nothing for that character this visit rather than replace it.
```lua
-- when the mode knows which character this is: OnPlayerConnect on a server without passwords, else once the password is right
function restoreDiscovery(pid, who)
local p = players[pid]
if not (db and p) then return end
p.who = who
local level = GetLevel():lower()
db:Query("SELECT kind, level, format, data FROM player_discovery WHERE who = @w AND (level = @l OR level = '')",
{ w = who, l = level }, function(rows, err)
if err then Log("player_discovery not read: " .. err) return end -- nothing is written this visit
if players[pid] ~= p then return end -- they left meanwhile; the id may be someone else's now
if #rows == 0 then
p.fresh = true -- nothing saved: their game's own first report starts the record
return
end
local record = { level = level }
for _, row in ipairs(rows) do
record[row.kind] = { format = row.format, data = row.data }
p.stored[row.kind .. "|" .. row.level] = row.data
end
if SetPlayerDiscovery(pid, record) then
p.ready, p.dirty = true, true -- the record is the saved one united with the game's: the next round writes what differs
else
Log("part of what " .. GetPlayerName(pid) .. " found could not be put back: nothing is saved this visit")
end
end)
end
```
The player's game answers every restore a little later, and
[`OnPlayerDiscoveryRestore(pid, kind, ok)`](/lua/server/callbacks/onplayerdiscoveryrestore/) tells the mode: `ok` is `false`
when the game could not do that kind. The record kept what the mode set either way, so the callback is for a log line or a retry
after the next spawn.
## The example modes
`basicrp.lua` in the server folder is the worked example. Its `DISCOVERY` table has a rule for each kind it can keep, `places` and
`reading` (a book read once gives no experience again after a return), both off as shipped: no table is made, nobody's game is
asked for anything and the map is the game's own. Turn a rule on and BasicRP makes its `player_discovery` table by itself, asks
the players' games for those kinds, saves what grew on the minute's round and when the player leaves, and puts the rows back at
the next login. That is this page's pattern, guards included: a read that fails or a row the server cannot read saves nothing
that visit. It does not keep the pin and the legend. `freeroam.lua` and `duel_arena.lua` keep nothing.
The table's third rule, `explorer`, is about the game's **Explorer** perk, which shows a character the whole map at once. With
`explorer = "strip"` BasicRP leaves that perk out of the perks it puts back at the login (it stays among the character's saved
perks), so the map fills as the character finds places. `"keep"` is the default. [The perk list](/reference/perks/) has the
perk's ID.
## What to know
- **One game, several characters.** A mode that lets one game play several characters in turn calls
[`ForgetPlayerDiscovery(pid, kinds)`](/lua/server/functions/forgetplayerdiscovery/) at each switch (every kind when left
out) and then restores the next character's record. Or it passes `true` as the third argument of `SetPlayerDiscovery` -
`replace` - and the record becomes exactly the one given. Every kind can be forgotten: the places go back to unknown, the
books unread, the pin and the legend's switches go and the fog clouds the map again, so the second character's map shows only
what it found. The place the player stands in is found again as soon as they walk in or out of it.
- **Places belong to a level, books to the character.** A server runs one level
([`level`](/getting-started/server-config/#server) in `server.toml`), and its record holds that level's places and the
books. A block of another level that the mode sets is kept in the record and never sent to the game.
`GetPlayerDiscovery(pid, level)` reads another level's record. The levels are [listed here](/reference/levels/).
- **A player's game may not be able to.** `capable` in the record lists what their game can do, and is empty until its first
report. A build of the game that KCD:MP does not know reports nothing and applies nothing. A game that closed while it read or
put back a kind leaves that kind alone on that PC until KCD:MP or the game is updated. A restore it cannot do is answered
`false`. The record keeps what you set, so saving it again changes nothing and nothing is lost.
- **The map pin and the legend are the player's own marks**, not discoveries: the newest report replaces the record instead of
growing it, and a restore replaces the pin and the legend the game shows.
- **Story quests.** The game's own story quests sleep unless the mode runs them
([`SetStoryQuests`](/lua/server/functions/setstoryquests/)). With them on, a quest can undo a place a player found, so a
mode that keeps places keeps them off. The server says so in its log when both are on.
- **The map.** A server with `map_fog = true` under [`[ui]`](/getting-started/server-config/#ui) keeps the game's fog of war:
every game starts clouded and the clouds lift where the player walks. Track `"fog"` and the record keeps what each character
uncovered, so a returning player's map shows it again. Without `map_fog`, the default, the whole land shows from the start and the
fog's record is simply the whole map - tracking it there saves nothing useful.
[`RevealPlayerMap`](/lua/server/functions/revealplayermap/) lifts the whole fog for one player, and a mode that saves the fog
then saves the whole map for that character. What a restored `places` record adds are the names of the places.
`RevealPlayerMap(pid, "places")` also asks for the level's places to be found, so the revealed map shows their names. No player's
game does that part in this version: the map is revealed without the names.
# Keeping a character's stats, skills and perks
> Keep each character's stats, skills, experience, perk points, perks, nourishment and energy in your game mode's own database and put them back at the next login, decide how much experience players earn, and give horses stats of their own.
Everything the game's character screen shows is the game mode's: the **stats** (strength, agility, vitality, speech, the
story's progress and the Blacksmith's prestige), the **skills**, the **experience** gathered toward each next level, the
**perk points** not spent yet, the **perks**, and two states of the character, the **nourishment** and the **energy**. By
default a server keeps none of it between visits: every visit starts with the game's own fresh character.
A game mode can keep it. The server keeps a **record** of each player's progression as their game reports it, the mode
**saves** that record in its own database and **puts it back** at the next login. It needs the mode's
[database](/guides/databases/). The names are in
[the Stats, skills, XP and perks group of the server API](/lua/server/#stats-skills-xp-and-perks). The examples are
Lua; a [C# mode](/csharp/server/#iserverapi---the-functions) has the same calls on `IServerApi`.
## 1. What is kept
[`GetPlayerProgress(pid)`](/lua/server/functions/getplayerprogress/) answers the record in one table:
```lua
local p = GetPlayerProgress(pid)
-- p.stats.strength { level = 12, xp = 40.5, next = 500, points = 1 }
-- p.skills.weapon_sword { level = 8, xp = 0, next = 290, points = 2 }
-- p.stats.mainlevel { level = 9, xp = 0, next = 0, points = 3 } - the main level, and the pool of points of no stat or skill
-- p.perks { "2993585c-40c9-42e3-ac45-b837f3bc50f7", ... } - the perks the character holds, by ID
-- p.nourishment 98.2 - nil until the player's game has reported
-- p.energy 97.0
```
| Part | What it is |
|---|---|
| a stat or skill | `level` from 0 to 30, `xp` gathered toward the next level, `next` what the next level takes in all, `points` not spent yet ([the names](/reference/stats-and-skills/)) |
| `stats.mainlevel` | the main level - the game works it out from the other levels, so it is read only; its `points` are the pool that belongs to no stat or skill |
| `skills.fencing` | Warfare - the game works it out from the weapon skills, so only its `points` are the mode's |
| `perks` | the perks the character holds, by ID ([the perks](/reference/perks/)) |
| `nourishment`, `energy` | how fed and how rested the character is |
The player's game reports its progression a few seconds after each spawn, the moment a level, a perk or perk points change,
within seconds for experience and the two states, and the whole of it again every minute. A stat or skill the server has not
heard of yet reads as level 0, so read the record after the game has reported, not before.
## 2. The experience players earn
**Nobody gains experience unless the mode says so.** The game awards experience for a fight, a ride, a book or a drink; the
server's rate multiplies every award, and the rate is `0` by default.
[`SetXPRate(rate)`](/lua/server/functions/setxprate/) sets it for everyone - `1` is the game's own, `2` twice as much, up to
`100` - and [`SetPlayerXPRate(pid, rate)`](/lua/server/functions/setplayerxprate/) for one player (`nil` gives them back the
server-wide rate).
```lua
function OnGameModeInit()
SetXPRate(1) -- the game's own experience counts
end
```
A mode that wants a word on every award defines
[`OnPlayerGainXP(pid, kind, name, xp)`](/lua/server/callbacks/onplayergainxp/). `kind` is `"stat"` or `"skill"`, `xp` the
award already times the rate. Return nothing to give it, `false` to refuse it, or a number to give that much instead:
```lua
local apprentices = {} -- the mode's own: pid -> true for the players learning at the guild
function OnPlayerGainXP(pid, kind, name, xp)
if name == "drinking" then return false end -- no drinking experience on this server
if apprentices[pid] and kind == "skill" then return xp * 2 end
end
```
A rate of `0` gives nothing and asks nothing, so a mode that defines the callback sets a rate too. Experience the mode hands out
itself goes the game's way, with the level-up notice and the perk points:
[`AddPlayerStatXP(pid, stat, xp)`](/lua/server/functions/addplayerstatxp/) and
[`AddPlayerSkillXP(pid, skill, xp)`](/lua/server/functions/addplayerskillxp/). A level a player earns reaches
[`OnPlayerLevelUp(pid, kind, name, level)`](/lua/server/callbacks/onplayerlevelup/) - the main level too, as the stat
`mainlevel`.
## 3. Saving a character
[`OnPlayerProgressChange(pid, levels)`](/lua/server/callbacks/onplayerprogresschange/) says the record changed. `levels` is
`true` when a level, a perk or perk points changed - save now, it is the character - and `false` when only experience or the
two states moved, which a timer can save. It also runs once for the first report after the player joins. The mode's own sets
do not call it.
**Never write before the restore has gone out.** Until the saved progression is put back (section 4) the record holds the
game's fresh character, and writing that over the stored one would lose it. A character with nothing saved yet starts from its
game's own first report.
The mode below keeps one row per stat and skill and one per perk. It keys a character by name for brevity (a mode with
accounts uses its account's id) and uses the database calls of the [databases guide](/guides/databases/):
```lua
local db = GetDatabase()
-- pid -> { who = the character, ready = may be written, fresh = nothing saved yet, dirty = moved since the last save,
-- stored = { ["stat:strength"] = "12:40.5:1", perks = "id,id,..." } - what the tables hold now }
local players = {}
local function key(v) return string.format("%d:%.1f:%d", v.level, v.xp, v.points) end
function OnGameModeInit()
SetXPRate(1)
if not db then Log("no database: progression is not kept") return end
db:ExecuteSync("CREATE TABLE IF NOT EXISTS player_progress (who VARCHAR(24) NOT NULL, kind VARCHAR(8) NOT NULL, " ..
"name VARCHAR(32) NOT NULL, level SMALLINT NOT NULL, xp DOUBLE PRECISION NOT NULL, points SMALLINT NOT NULL, " ..
"PRIMARY KEY (who, kind, name))")
db:ExecuteSync("CREATE TABLE IF NOT EXISTS player_perks (who VARCHAR(24) NOT NULL, perk VARCHAR(36) NOT NULL, " ..
"PRIMARY KEY (who, perk))")
SetTimer(saveAllProgress, 60000, true) -- every minute, for whoever gained experience since
end
function OnPlayerConnect(pid)
players[pid] = { stored = {} }
restoreProgress(pid, GetPlayerName(pid):lower()) -- section 4; with passwords, once the password is right
end
function OnPlayerProgressChange(pid, levels)
local p = players[pid]
if not p then return end
if p.fresh then p.ready = true end -- a new character: its game's first report is where it starts
if levels then saveProgress(pid) else p.dirty = true end
end
function OnPlayerDisconnect(pid, reason)
saveProgress(pid)
players[pid] = nil
end
function OnGameModeExit() -- the server stops, or the mode is reloaded
for pid in pairs(players) do saveProgress(pid) end
end
function saveProgress(pid)
local p = players[pid]
local now = p and p.who and p.ready and GetPlayerProgress(pid)
if not now then return end -- not back yet: nothing is written
p.dirty = nil
local batch = {}
for _, group in ipairs({ { "stat", now.stats }, { "skill", now.skills } }) do
for name, v in pairs(group[2]) do
local id = group[1] .. ":" .. name
if p.stored[id] ~= key(v) then -- only what differs from the last save
p.stored[id] = key(v)
batch[#batch + 1] = { "DELETE FROM player_progress WHERE who = @w AND kind = @k AND name = @n",
{ w = p.who, k = group[1], n = name } }
batch[#batch + 1] = { "INSERT INTO player_progress (who, kind, name, level, xp, points) VALUES (@w, @k, @n, @l, @x, @p)",
{ w = p.who, k = group[1], n = name, l = v.level, x = v.xp, p = v.points } }
end
end
end
local perks = table.concat(now.perks, ",")
if perks ~= p.stored.perks then
p.stored.perks = perks
batch[#batch + 1] = { "DELETE FROM player_perks WHERE who = @w", { w = p.who } }
for _, perk in ipairs(now.perks) do
batch[#batch + 1] = { "INSERT INTO player_perks (who, perk) VALUES (@w, @g)", { w = p.who, g = perk } }
end
end
if #batch == 0 then return end
db:Batch(batch, function(ok, err)
if ok then return end
Log("progression not saved: " .. tostring(err))
p.stored, p.dirty = {}, true -- the next round writes it all again
end)
end
function saveAllProgress()
for pid, p in pairs(players) do
if p.dirty then saveProgress(pid) end
end
end
```
## 4. Putting it back at the login
[`SetPlayerProgress(pid, progress)`](/lua/server/functions/setplayerprogress/) takes a table of the shape
`GetPlayerProgress` gives, and every part is optional. It is a **whole restore** and exact:
- every stat and skill listed is set as it is - its level, its experience and its perk points (none counts as `0`), lowering
too, and without the game's level-up notice. An entry without a `level` is skipped;
- `stats.mainlevel` puts back only the pool of perk points, `skills.fencing` only Warfare's points: the game works both
levels out from the others;
- `perks`, when given, makes the character's perks exactly that list;
- `nourishment` and `energy` are set when given.
The server takes it at once (`GetPlayerProgress` right afterwards answers it) and sends it to the player's game. A restore made
before the player is in the world waits for their spawn, and a report their game sent before the restore landed never
overwrites it.
```lua
-- when the mode knows which character this is: OnPlayerConnect on a server without passwords, else once the password is right
function restoreProgress(pid, who)
local p = players[pid]
if not (db and p) then return end
p.who = who
db:Query("SELECT kind, name, level, xp, points FROM player_progress WHERE who = @w", { w = who }, function(rows, err)
if err then Log("progression not read: " .. err) return end -- nothing is written this visit
if players[pid] ~= p then return end -- they left meanwhile
if #rows == 0 then
p.fresh = true -- nothing saved: the game's own start is the first save
return
end
local progress = { stats = {}, skills = {} }
for _, r in ipairs(rows) do
local v = { level = r.level, xp = r.xp, points = r.points }
progress[r.kind == "stat" and "stats" or "skills"][r.name] = v
p.stored[r.kind .. ":" .. r.name] = key(v)
end
db:Query("SELECT perk FROM player_perks WHERE who = @w", { w = who }, function(perkRows, perkErr)
if perkErr or players[pid] ~= p then return end
progress.perks = {}
for _, r in ipairs(perkRows) do progress.perks[#progress.perks + 1] = r.perk end
table.sort(progress.perks)
p.stored.perks = table.concat(progress.perks, ",")
SetPlayerProgress(pid, progress)
p.ready = true -- from here on the record is the saved character
end)
end)
end
```
**The perk list replaces the character's perks.** Every player is given the perfect block, the riposte and the master strikes
when they first spawn, and the list `GetPlayerProgress` gives holds them. A list the mode writes itself takes them away unless it
names them too. The perks a fresh character holds beyond the list are taken away only once the server has heard which perks
they hold - a few seconds after the spawn. A restore made before that adds the missing ones; a second `SetPlayerProgress` after
the first `OnPlayerProgressChange` makes the list exact.
## 5. Changing a character during play
| Call | What it does |
|---|---|
| [`SetPlayerStat(pid, stat, level [, xp])`](/lua/server/functions/setplayerstat/), [`SetPlayerSkill(pid, skill, level [, xp])`](/lua/server/functions/setplayerskill/) | the level and the experience exactly, lowering too, no notice; the perk points stay |
| [`AddPlayerStatXP`](/lua/server/functions/addplayerstatxp/), [`AddPlayerSkillXP`](/lua/server/functions/addplayerskillxp/) | experience the game's way: the level-up notice and the perk points |
| [`SetPlayerPerkPoints(pid, name, points)`](/lua/server/functions/setplayerperkpoints/) | a stat's or a skill's unspent points, or the main level's pool (`mainlevel`) |
| [`AddPlayerPerk`](/lua/server/functions/addplayerperk/), [`RemovePlayerPerk`](/lua/server/functions/removeplayerperk/), [`HasPlayerPerk`](/lua/server/functions/hasplayerperk/) | a perk by ID, or by name once the [game's tables](/guides/game-tables/) are exported; [`GetPerkName`](/lua/server/functions/getperkname/) answers the name |
| [`SetPlayerNourishment`](/lua/server/functions/setplayernourishment/), [`SetPlayerEnergy`](/lua/server/functions/setplayerenergy/) | how fed and how rested, up to the maximum the game reported |
A stat or skill a mode raises shows on the other players' screens too: the character's body there takes the same level, so the
game treats it the same way. A bow's draw, for one, holds or drops by the archer's strength against the bow on every screen.
## 6. Horses
A horse has strength, agility, vitality and courage of its own:
[`SetHorseStat(id, stat, level)`](/lua/server/functions/sethorsestat/) sets one from 0 to 30 on every copy of the horse - its
rider's and everyone's who sees it - and carry capacity, speed, stamina and courage follow at once.
[`GetHorseStat`](/lua/server/functions/gethorsestat/) and [`GetHorseStats`](/lua/server/functions/gethorsestats/) read
them, `0` or an empty table for a horse never set (it has the game's own). The stats live as long as the horse: a mode that
keeps its players' horses saves them with the horse's row and sets them again when it makes the horse anew.
## The example modes
`basicrp.lua` in the server folder is the worked example: it saves each character's stats, skills, experience, perk points,
perks, nourishment and energy (`player_progress`, `player_perks` and two columns of its `players` table), puts them back at the
login, and keeps the stats an admin gave a horse (`horse_stats`). Its `PROGRESS` table holds the rate (`xp_rate = 1`). Everyone
has `/skills [name]`; admins have `/setlevel`, `/givexp`, `/perk`, `/perkpoints`, `/xprate` and `/horsestat`. `freeroam.lua`
gives the game's own experience, `/skills` and the admin commands but `/horsestat`, and keeps nothing; `duel_arena.lua` keeps
every duelist equal with a rate of `0`.
## What to know
- **The rate is 0 by default.** A mode of your own that never calls `SetXPRate` has nobody gaining experience.
- **`[spawn] stat_level`** in [`server.toml`](/getting-started/server-config/) raises the four core stats at every spawn and
never lowers them. A mode that keeps its players' levels leaves it at `0`.
- **The progression is checked.** The record is what the player's game reports, and the server's
[audit](/getting-started/server-config/#audit) judges it against what it can explain. With `[audit] mode = "enforce"` a
rise nothing explains is put back. A server whose story quests award experience vouches for it in
[`OnPlayerProgressViolation`](/lua/server/callbacks/onplayerprogressviolation/). **The server ships with
`[audit] mode = "observe"`**, which judges and logs but puts nothing back: a value a player's game forges would be the one the
record holds and your mode saves. A mode that keeps characters should not rely on the record until the server owner runs `enforce`.
- **Perk names need the tables.** Perk IDs work on every server; names (`AddPlayerPerk(pid, "Iron Grip")`) need the
[exported tables](/guides/game-tables/) of this version.
# Playing on Linux and Steam Deck
> How to play KCD:MP on Linux through Steam's Proton - the KCD:MP launcher as a non-Steam game, the WebView2 component it needs, the game folder, and the logs to send when something goes wrong.
Kingdom Come: Deliverance II has no Linux version, but it runs very well on Linux through **Proton**, the compatibility
layer built into Steam. KCD:MP runs the same way: the KCD:MP launcher runs under Proton like any Windows program and starts
the game from there. Players use it this way today. It is not part of our own testing yet, so if something on this page
does not match what you see, tell us on Discord (the last section says what to send).
You need the **Steam** version of the game. The steps below take about ten minutes the first time.
## What you need
- Steam for Linux, with the game installed and Steam Play (Proton) turned on for it.
- **Proton-GE**, a community build of Proton - the launcher's window is known to work with it. The easiest way to get it is
**ProtonUp-Qt** (on Flathub, or the Discover store on a Steam Deck): pick Proton-GE and install the latest version. Steam's
own Proton and Proton Experimental may work too, but the launcher's window has not been seen working with them.
- **Protontricks** (on Flathub as *Protontricks*, or from your distribution). If you use the Flatpak, also **Flatseal** to
give it access to your games (step 5).
- KCD:MP itself, from [kcd-mp.com/download](https://kcd-mp.com/download).
On a Steam Deck, do steps 1 to 6 in **Desktop Mode**. Once it works, you can start KCD:MP from your library in Game Mode.
## 1. Start the game once
Start Kingdom Come: Deliverance II from Steam as you normally would, wait for its main menu, and quit. This checks that the
game itself runs on your system before KCD:MP is added.
## 2. Unzip KCD:MP
Unzip the KCD:MP download into a folder of its own, for example `~/Games/KCD-MP`. Do not put it into the game's `Mods`
folder.
## 3. Add the launcher to Steam
In Steam, choose **Add a Game → Add a Non-Steam Game...** (bottom left of the library), click **Browse**, pick
`KcdMp_launcher.exe` from the folder of step 2 and add it.
Then right-click the new entry → **Properties** → **Compatibility**, tick **Force the use of a specific Steam Play
compatibility tool** and choose the **Proton-GE** version you installed.
## 4. Start it once
Start the new entry from Steam. The first start sets up a Proton folder of its own for the launcher, which takes a moment,
and then shows a message that the window needs the **WebView2** runtime. Close the message - that is expected, and the next
step installs it.
## 5. Install WebView2 with Protontricks
If you use the Flatpak version of Protontricks, open **Flatseal** first, pick **Protontricks** and, under *Filesystem*, give
it access to the drive or folder that holds your Steam library (your home folder is usually enough; an SD card or a second
drive needs its own entry).
Then open **Protontricks**:
1. Pick the launcher's entry - it shows as a non-Steam shortcut named `KcdMp_launcher.exe`.
2. Choose **Select the default wineprefix**.
3. Choose **Install a Windows DLL or component**.
4. Tick **webview2** in the list and press **OK**.
Follow the installer to the end. Some errors may show on the way; they can usually be ignored.
## 6. Start the launcher and join
Start the launcher's entry from Steam again: the KCD:MP window opens with the server list.
The launcher looks for the game in your Steam libraries. If it says it cannot find the game, open **Settings → Game folder**
and enter the game's folder with `Z:` in front of the Linux path - for the default Steam library:
```
Z:\home\\.local\share\Steam\steamapps\common\KingdomComeDeliverance2
```
On a Steam Deck the user name is `deck`; a game on an SD card is under `Z:\run\media\...`. In Steam, right-click the game →
**Manage → Browse local files** shows you the folder.
Pick a server and press **Join**. Keep Steam running while you play - the game checks your copy through Steam.
## Good to know
- The game runs inside the launcher's Proton folder, not the game's usual one, so your graphics options and key bindings
start from the defaults the first time, and the first minutes may stutter while the game prepares its shaders.
- Steam shows the launcher's entry, not the game, as what you are playing.
- On a Steam Deck the chat needs a keyboard: press **Steam + X** for the on-screen one.
- Voice chat and the servers' own web pages have not been checked on Linux yet. If they work - or don't - for you, tell us.
## If something goes wrong
Ask for help on our Discord and send these, so we can see what happened:
- **Which steps worked**, and what you saw at the one that did not.
- **Your system**: PC or Steam Deck, your distribution, your graphics card and driver version, the Proton version, and
whether Steam is the Flatpak.
- **`client.log`** - KCD:MP's own log, in the launcher's Proton folder:
`~/.local/share/Steam/steamapps/compatdata//pfx/drive_c/users/steamuser/AppData/Local/KcdMp/client.log`.
`` is the launcher's shortcut; `protontricks -l` (or `flatpak run com.github.Matoking.protontricks -l`) lists it
next to `KcdMp_launcher.exe`. Any `crash-....txt` in the same folder too.
- **`kcd.log`** - the game's own log, in the game's folder.
- **Proton's log**, if the game does not start at all: in the launcher entry's **Properties → General → Launch Options**,
enter `PROTON_LOG=1 %command%`, start it again, and send `steam-.log` from your home folder. Clear the line
afterwards.
Send only these files: the others in that `KcdMp` folder hold your player key and the passwords of servers you joined.