Server configuration
The server reads server.toml from its working directory (or --config <path>). 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]
Section titled “[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). 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, 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://<host>:<port>/ 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. 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 |
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]
Section titled “[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); 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]
Section titled “[gamemode.database]”The game mode’s own database, for whatever it wants to keep - accounts, factions, scores, the rules on doors and chests
(GetDatabase; the databases guide 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]
Section titled “[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), 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 <folder> 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]
Section titled “[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). 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. 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. 0 = the network library’s own 1 MB; at most 256 |
Choosing reliable_rate
Section titled “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
Section titled “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
Section titled “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
Section titled “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=16777216and 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
Section titled “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:
[server]port = 7777network_threads = 3 # also listens on 7778 and 7779Every 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.
| 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]
Section titled “[world]”| Key | Default | |
|---|---|---|
time |
"07:30" |
the world clock at start (SetWorldTime) |
time_ratio |
15.0 |
game seconds per real second; 0 freezes the clock (SetTimeRatio) |
rain |
-1.0 |
rain forced on every client, 0 .. 1; below 0 = the weather’s own (SetRain) |
weather |
"" |
the sky every client follows - a preset or a profile; "" = the server rotates the presets every few game hours (SetWeather; the game mode sets how often each comes up - 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, the map API); {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]
Section titled “[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). The loot tables come with the tables export
(Exporting the game’s 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, 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]
Section titled “[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). 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 - 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). 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) |
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). While the level’s file is there the NPC actors walk around walls and 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 or 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). While the level’s file is there 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 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]
Section titled “[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]
Section titled “[commands]”Admins are the game mode’s: it promotes them with 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 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]
Section titled “[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, 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,
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. 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 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 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). 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) 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), 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) and keeps it through a death, and a shop sells against the money the records explain (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): 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
Section titled “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, 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 or
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_presetandweapon_preset(and a mode’sSetSpawnInfopresets), as the tables export lists them, and[spawn] items. A mode that dresses players withSetPlayerInventoryinstead 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); an item made by code outside the game is never credited - it is aforeign_callhint, and underenforcethe 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’sCreateHorse) 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
Section titled “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 '<class>' 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.<where>, native.<kind>, 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 <kind> (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
Section titled “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 -
0until a mode turns XP on withSetXPRate, so on a server that never does, any rise is unexplained - what the game mode gave (AddPlayerSkillXP,SetPlayerStat,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. Withenforce, 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). 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) 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) - a forged value is never the one that is
saved, and a level that was put back is never announced (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/SetPlayerEnergyabove 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 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 has the counts for a mode that wants to decide for
itself - its gate_down, read in 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
observewould 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
observeas well;progress_acksays 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:
- 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. - A server with story quests on (
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 inOnPlayerProgressViolation(returnfalse). A story server that switches toenforcemust do that; nothing is ever proven against a player on a story server. - 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.
- 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.
- 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]
Section titled “[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. 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]
Section titled “[combat]”The model is the Combat guide.
| 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 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); 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 |
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); "" (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, 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.
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) |
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]
Section titled “[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 <name|id> 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]
Section titled “[party]”The groups the server keeps for the game mode’s own commands (the parties guide): 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, or the party component of Change the 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]
Section titled “[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 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 - 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);
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.
[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]
Section titled “[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);
a game mode can make its own with AddCartRoute, put free carts anywhere
with CreateCart and decide what each is made of - the body, the wheels, the load,
the horses (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 sets one cart’s own) |
driver_clothing |
"24e4aa5b-cd2c-4dba-9426-b63e674b7037" |
the clothing preset of the wagoners on the routes (the clothing presets) |
[effects]
Section titled “[effects]”| Key | Default | |
|---|---|---|
range |
150.0 |
metres from the point within which players get a mode’s effect, decal or sound (SpawnEffect, SpawnDecal, PlaySound); 0 = everyone in that virtual world |
[actors]
Section titled “[actors]”How an NPC actor 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) stops it there - a walk ends at the door (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]
Section titled “[spawn]”| Key | Default | |
|---|---|---|
x, y, z, yaw |
1280.0, 1088.0, -1.0, 0.0 |
the default spawn (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; a name or an id works too) |
clothing_preset |
"" |
the outfit players spawn in (clothing presets); "" = they start naked - a game mode dresses them with 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); a respawn with the same presets keeps what they carry |
weapon_preset |
"" |
their weapons (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) |
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 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, SetSpawnInfo) |
