KCDS resources: instructions for Codex
Save this file as AGENTS.md in your resource's folder, or in the server's resources folder to cover all of
them. Codex reads AGENTS.md when it starts working there, and so do other coding assistants that follow that
convention. It then writes KCDS resources the way the SDK expects them. The same text for Claude Code is
CLAUDE.md.
When Codex runs without web access, put the SDK's declarations for your language into the project (see Look it up, never guess) so it can search them.
Everything below comes from the KCDS documentation at https://kcdsynced.com/docs. When this file and the documentation disagree, the documentation is right.
What you are writing
KCDS adds multiplayer to Kingdom Come: Deliverance II. A server's rules, commands and content come from
resources. A resource is a folder in the server's resources folder whose package.json has a "kcds"
section. Its server scripts run on the server, and its client scripts are downloaded by each player and run in
their game. Nobody builds the server or the mod to write a resource.
A resource's sides can be in JavaScript (CommonJS), TypeScript compiled to CommonJS, Lua 5.4 or C# (.NET 9,
built to .dll files). Each side uses one language, picked from its scripts' extensions or set with
"language", so a Lua server can go with a JavaScript client.
Look it up, never guess
- Every type has a reference page at
https://kcdsynced.com/docs/api/<type>, with the type's name in lower case:/docs/api/player,/docs/api/npc,/docs/api/hud. Read the page before you use a type. A member that is not on its page does not exist. - Each reference page says whether a type works in server scripts, in client scripts or on both sides. Client
types such as
Hud,Key,WebandNativeUIare missing from server scripts. - Use the SDK of the version the server runs. The declarations list every member with its description, so
search them when the web is out of reach:
- TypeScript and JavaScript:
node_modules/@kcds/types/(npm install --save-dev @kcds/types@<version>) - Lua:
kcds-server.luaandkcds-client.luafrom thelua/folder ofKCDS-Types-<version>.zip - C#:
Kcds.Sdk.dllandKcds.Sdk.xmlfrom thecsharp/folder of the same zip
- TypeScript and JavaScript:
- The guide pages are listed under Where to read more.
The manifest
{
"name": "market",
"version": "1.0.0",
"kcds": {
"serverScripts": ["server/main.js"],
"clientScripts": ["client/main.js"],
"sharedScripts": ["shared/items.js"],
"files": ["ui/**"],
"exports": ["priceOf"],
"requires": ["economy"],
"errorBehavior": "stop"
}
}- A resource is known by its
name, not by its folder. The console commandensure marketuses the name. serverScripts,clientScriptsandsharedScriptsrun in the listed order.sharedScriptsrun first on each side."server": "server.js"and"client": "client.js"are short for a list of one script.filesholds the files that are sent to players but do not run, such as pages, pictures and data.exportslists the names a resource offers to other resources.requiresnames the resources that must run first, and{ "name": "economy", "optional": true }names one that may be missing.errorBehaviorsets what an uncaught error does:stop(the default),restartorcontinue.- A C# side lists the
.dllfiles it was built into, never.cssources.
The API
The SDK is the global kcds object, so you need no require or import for it. It has six domains:
kcds.core (events, chat, sharing, runtime), kcds.math (vectors, quaternions, colours), kcds.players,
kcds.characters (NPCs, animals, dialogue, quests, shops), kcds.world (places, objects, world systems)
and kcds.ui (screens, input, camera, client only).
- Always write the full path, such as
kcds.world.Door, or unpack it at the top of a file:const { Door } = kcds.world;. A bareDooris aReferenceError. - In Lua, constructors are
Vector3.new(...), objects use:(player:emit(...)), lists start at 1, andnilstands fornull. - In C#, names start with a capital letter (
player.Nickname,Hud.ShowNotification), but event names stay as they are ("playerReady"). The template imports the namespacesKcds.Core,Kcds.Players,Kcds.Worldand the rest. Values without a type areJsValue.
The two sides talk through events
| From | Send with | Receive with |
|---|---|---|
| client to server | kcds.core.Events.toServer(name, payload) | kcds.core.Events.fromClient(name, (player, payload) => ...) on the server |
| server to one | player.emit(name, payload) | kcds.core.Events.on(name, ...) in the client |
| server to all | kcds.core.Events.broadcast(name, payload) | kcds.core.Events.on(name, ...) in the client |
| within one side | kcds.core.Events.emit(name, ...args) | kcds.core.Events.on(name, ...) |
Events.onon the server never hears players. Events from players arrive only throughEvents.fromClient.- The server fills in the first argument of a
fromClienthandler, so the sending player can be trusted. The payload cannot: a modified game can send anything, so check its type, length and range before your rules use it. - A payload is JSON of at most 32 KB. A bigger one throws
Event payload too long. - Prefix your own event names with the resource's name, such as
market:buy.
// server kcds.core.Events.fromClient('hello', (sender, payload) => { if (!payload || typeof payload.message !== 'string' || payload.message.length > 128) { return; } kcds.core.Chat.sendToPlayer(sender, payload.message); });
// server kcds.core.Events.fromClient('hello', (sender, payload) => { if (!payload || typeof payload.message !== 'string' || payload.message.length > 128) { return; } kcds.core.Chat.sendToPlayer(sender, payload.message); });
-- server kcds.core.Events.fromClient("hello", function(sender, payload) if type(payload) ~= "userdata" or type(payload.message) ~= "string" or #payload.message > 128 then return end kcds.core.Chat.sendToPlayer(sender, payload.message) end)
// server Events.FromClient("hello", (Player sender, JsValue payload) => { var message = payload["message"]; if (message.Type != "string" || message.AsString()!.Length > 128) { return; } Chat.SendToPlayer(sender, message); });
Rules to follow
- The server decides. Money, items, permissions and anything else that matters is checked and changed in server scripts. A client script asks, and the server answers.
- Nothing private goes to players. Players receive
clientScripts,sharedScripts,files, every file those scripts load, and everything understream/andreplace/. Keep passwords, keys and server settings in server files only. Keep the client code in a folder of its own, such asclient/, apart fromserver/. - Commands.
playerCommand(player, command, args)gets every command, so comparecommandfirst. A command arrives without its slash, and its arguments are split at spaces and keep their case. - Refusing. Events that ask before something happens, such as
doorInteract,playerLootorplayerConsuming, are refused when a handler returnsfalse, but not when it returns a promise offalse. The Events page lists them. - Start and stop.
resourceStart(name)andresourceStop(name)fire for every resource, so comparenamewith your own. KCDS waits for async handlers, up to 30 seconds at the start and 10 at the stop. Handlers, timers, exports and anything the resource spawned are removed when it stops, so no clean-up code is needed for them. - Client restarts. A player's client scripts can start again from nothing, and the server then gets
playerResourcesReadyagain. Send what a client needs from that event, not only on the first join. - Saving. Nothing in memory survives a restart of the resource or the server. In JavaScript, save under
kcds.core.Runtime.serverDir, because the resource's own folder may be replaced. Lua and C# scripts have no files: call the exports of a JavaScript resource that saves, or a web service. - Errors. An uncaught error stops the resource by default. Catch errors where you can handle them,
especially around
await. SDK functions throwTypeErrorfor a wrong type andRangeErrorfor a value out of range, naming the argument. - Between resources. Exports and imports share values and functions on the same side
(Imports and exports).
kcds.core.Messagescalls another resource by name and may wait for its reply (Messages).entity.statecarries values the server sets on players, NPCs, horses and dogs, with the scopesownerandserverfor values others must not see (Shared state). - What a script can reach. Server scripts in JavaScript have Node's modules, packages from the
resource's
node_modules,fetchandWebSocket. Lua server scripts havefetchandWebSocket, and C# server scripts haveNetwork.Fetch. Client scripts can load only the resource's own.js,.cjsand.jsonfiles (in Lua, its own.luafiles): no Node modules, noprocessand no disk. - Time limits. A call into Lua fails after 5 seconds without returning. C# has no limit, so an endless
loop holds up every script of its side. Keep handlers short and use timers or
awaitto wait.
A small resource in each language
package.json for JavaScript (for Lua, name the scripts server.lua and client.lua):
{
"name": "hello",
"version": "1.0.0",
"kcds": {
"serverScripts": ["server.js"],
"clientScripts": ["client.js"]
}
}The server script welcomes players and answers /hello:
const { Events, Chat } = kcds.core; const { Color } = kcds.math; // Runs when a player has joined and loaded the world. Events.on('playerReady', (player) => { Chat.sendToPlayer(player, `Welcome, ${player.nickname}!`, { author: 'Server' }); }); // Runs for "/hello" or "/hello some words" in the chat. Events.on('playerCommand', (player, command, args) => { if (command.toLowerCase() !== 'hello') { return; } const reply = args.length > 0 ? args.join(' ') : 'Hello!'; Chat.sendToPlayer(player, reply, { author: 'Server', color: Color.fromRGB(255, 210, 77), }); });
const { Events, Chat } = kcds.core; const { Color } = kcds.math; // Runs when a player has joined and loaded the world. Events.on('playerReady', (player) => { Chat.sendToPlayer(player, `Welcome, ${player.nickname}!`, { author: 'Server' }); }); // Runs for "/hello" or "/hello some words" in the chat. Events.on('playerCommand', (player, command, args) => { if (command.toLowerCase() !== 'hello') { return; } const reply = args.length > 0 ? args.join(' ') : 'Hello!'; Chat.sendToPlayer(player, reply, { author: 'Server', color: Color.fromRGB(255, 210, 77), }); });
local Events, Chat = kcds.core.Events, kcds.core.Chat local Color = kcds.math.Color -- Runs when a player has joined and loaded the world. Events.on("playerReady", function(player) Chat.sendToPlayer(player, "Welcome, " .. player.nickname .. "!", { author = "Server" }) end) -- Runs for "/hello" or "/hello some words" in the chat. Events.on("playerCommand", function(player, command, args) if command:lower() ~= "hello" then return end local reply = #args > 0 and table.concat(args, " ") or "Hello!" Chat.sendToPlayer(player, reply, { author = "Server", color = Color.fromRGB(255, 210, 77), }) end)
// Runs when a player has joined and loaded the world. Events.On("playerReady", (Player player) => { Chat.SendToPlayer(player, quot;Welcome, {player.Nickname}!", new { author = "Server" }); }); // Runs for "/hello" or "/hello some words" in the chat. Events.On("playerCommand", (Player player, string command, string[] args) => { if (command.ToLowerInvariant() != "hello") { return; } var reply = args.Length > 0 ? string.Join(" ", args) : "Hello!"; Chat.SendToPlayer(player, reply, new { author = "Server", color = Color.FromRGB(255, 210, 77), }); });
The client script asks the server for the player count when F7 is pressed:
// client const { Events } = kcds.core; const { Hud, Key } = kcds.ui; Events.on('playerReady', () => { Hud.showNotification('The hello resource is running.'); }); // F7 asks the server how many players are online. Key.bind('f7', () => { Events.toServer('hello:count'); }); Events.on('hello:count', (data) => { Hud.showNotification(`${data.players} players online`); });
// client const { Events } = kcds.core; const { Hud, Key } = kcds.ui; Events.on('playerReady', () => { Hud.showNotification('The hello resource is running.'); }); // F7 asks the server how many players are online. Key.bind('f7', () => { Events.toServer('hello:count'); }); Events.on('hello:count', (data) => { Hud.showNotification(`${data.players} players online`); });
-- client local Events = kcds.core.Events local Hud, Key = kcds.ui.Hud, kcds.ui.Key Events.on("playerReady", function() Hud.showNotification("The hello resource is running.") end) -- F7 asks the server how many players are online. Key.bind("f7", function() Events.toServer("hello:count") end) Events.on("hello:count", function(data) Hud.showNotification(data.players .. " players online") end)
// client Events.On("playerReady", () => { Hud.ShowNotification("The hello resource is running."); }); // F7 asks the server how many players are online. Key.Bind("f7", new Action(() => { Events.ToServer("hello:count"); })); Events.On("hello:count", (JsValue data) => { Hud.ShowNotification(quot;{data["players"]} players online"); });
And the server answers it:
kcds.core.Events.fromClient('hello:count', (player) => { player.emit('hello:count', { players: kcds.players.Player.all().length }); });
kcds.core.Events.fromClient('hello:count', (player) => { player.emit('hello:count', { players: kcds.players.Player.all().length }); });
kcds.core.Events.fromClient("hello:count", function(player) player:emit("hello:count", { players = #kcds.players.Player.all() }) end)
Events.FromClient("hello:count", (Player player) => { player.Emit("hello:count", new { players = Player.All().Length }); });
A C# resource starts from csharp/template in KCDS-Types-<version>.zip. Each script there is a public
class deriving from Script, and its code goes in OnStart. Build each side with dotnet build -c Release,
and list bin/server/Server.dll and bin/client/Client.dll in the manifest. See
C#.
A TypeScript resource compiles each side with its own tsconfig, one with "types": ["@kcds/types/server"]
and one with "types": ["@kcds/types/client"], to CommonJS in dist/, and the manifest lists the compiled
files. See TypeScript.
Testing a change
- Save the files, and build them for TypeScript or C#.
- Enter
ensure <name>in the server console, or press Restart on the Resources tab of KCDS-Admin. The resource starts again with its latest files, and online players get the new client part without restarting their game. - Server output is in the server console and the Console tab of KCDS-Admin. Client output is in the
player's game log,
client-<process id>.login the KCDS folder (%LOCALAPPDATA%\Programs\KCDSwith the installer).
console.log joins its values as text, so log JSON.stringify(value) (in Lua, json.encode(value)) to see
an object.
| Symptom | Likely cause |
|---|---|
ensure hello does nothing, not even started | the console needs the name from package.json, not the folder name |
ReferenceError: Door is not defined | the type needs its full path, kcds.world.Door |
Cannot read properties of undefined on kcds.ui.Hud | a client type used in a server script |
| a client event never reaches the server | the server used Events.on, but events from players need Events.fromClient |
Events.toServer works but no answer appears | answer with player.emit or Events.broadcast, and listen with Events.on in the client |
| a command handler runs for every command | playerCommand gets all commands, so compare command first |
Where to read more
- Start: Your first resource, Project structure, Resources, Client resources, API overview, API changes
- Languages: TypeScript, Lua, C#
- Scripting: Events, Imports and exports, Messages, Shared state, Chat and texts, Timers and runtime, Math
- Players: Players, Restricting a player, Skills and perks, Custom buffs, Animations, Voice chat
- World and objects: NPCs, Horses and dogs, Time and weather, Areas, Markers and blips, Props, Doors and gates, Stashes
- Gameplay: Inventory and items, Equipment, Quests and conversations, Shops and trade, Crafting
- Client: HUD and messages, Action hints, Web views, Keys and controls, Camera, Sounds and game files
- Trying and fixing: Trying code quickly, Debugging resources