Getting started
A client script is a Lua file that runs inside a player’s game, in the game’s own Lua state, next to the KCD:MP client. It is the game mode’s half on the player’s screen: a marker in the world, a sound, a bit of UI, a value read from the game that the server cannot see. A game mode works without one - the server’s chat lines, GameTexts and HUD texts need nothing on the client - and most modes need none. A mode that wants more puts its client files next to itself, and the server sends them to every player who joins.
How a script reaches the player
Section titled “How a script reaches the player”The server sends it. A mode with a client half lives in a folder of its own, the way a C# plugin does, with the client files
in client/ next to it:
gamemodes/arena/arena.lua the server half - [gamemode] script names itgamemodes/arena/client/*.lua the client half - sent to every joining player, run in name orderNothing is asked of the players: the files arrive with the join, before the roster and the mode’s first events, and run
once the level is ready - so a handler registered at the top of a file sees the event the mode sends from OnPlayerSpawn.
The launcher and the game say so when a server does this (“runs 2 client scripts in your game, sandboxed to the game”).
[client] scripts in the server’s configuration names another folder (a mode in a single file, or the built-in one, has no
client/ of its own). The limits: 64 files, 256 KB each, 2 MB together. /reload on the server sends the folder again -
the running scripts are dropped first (their handlers, their timers), then the new ones run; the watch setting does the
same by itself when a client file changes on disk.
A script runs once, when it arrives; what it defines stays for the session and is gone with the game or replaced by the next
/reload. Several files of one folder share one environment: a function the first file defines, the second can call.
For development, a file on the player’s own disk can still be run by hand: KcdMp_exec in the
F9 window, or -KcdMp_exec <file> in the launcher’s Game arguments. Such a file has the game’s whole Lua, no sandbox -
it is the player’s own.
What the script has
Section titled “What the script has”A script the server sent runs in a sandbox: the boundary is the game. It has everything it needs to draw, read the world
and talk to the server, and nothing that reaches the player’s computer beyond the game - no files, no other programs, no
browser, no console, no saves. What is there is listed below; a name that is not listed is not there (nil), and a
script that wants one more should say so - the list grows on request.
The KcdMp table
Section titled “The KcdMp table”The API on this side, every member on its own page:
- script events to and from the game mode -
KcdMp.on_event,KcdMp.send_event; - the state bags the mode set -
KcdMp.state,KcdMp.on_state,KcdMp.player_id; - helpers and console commands.
The game’s own Lua, as the sandbox gives it
Section titled “The game’s own Lua, as the sandbox gives it”The script runs in the game’s Lua state, in an environment of its own. What it holds:
| the base library | assert, error, ipairs, next, pairs, pcall, select, tonumber, tostring, type, unpack, xpcall, rawequal, rawget, rawset, setmetatable, getmetatable (not on strings), collectgarbage, loadstring (its chunks run in the sandbox too), print (into the client log) |
string, math, table, coroutine |
copies of the game’s - a script may change its own copy, not the game’s |
os.clock(), os.time() |
the two the game has |
System |
the log (LogAlways, Log, Warning, Error), the drawing for this frame (DrawLabel(pos, size, text, r, g, b, a), DrawLine(a, b, r, g, b, a), DrawText, Draw2DLine), the entities (GetEntity, GetEntityByName, GetEntities, GetEntitiesByClass, GetEntitiesInSphere, GetNearestEntityByClass, GetPhysicalEntitiesInBox, SpawnEntity, RemoveEntity …), the world (RayTraceCheck, RayWorldIntersection, GetTerrainElevation({x=, y=, z=}), IsPointIndoors, IsPointVisible, ProjectToScreen, the view camera’s GetViewCameraPos / Dir / Fov, GetViewport), the clocks (GetCurrTime, GetFrameTime, GetFrameID, GetLocalOSTime), the look of the world (SetPostProcessFxParam, SetWind, the ambient colour, the sky highlight, ActivateLight) |
Script.SetTimer(ms, fn), Script.SetTimerForFunction(ms, fn), Script.KillTimer(id) |
the game’s own timers - one shot; call again for a loop. A /reload kills the ones a script left running |
Game |
the game’s own table without its saves, loads and the recording |
Calendar |
the game’s clock - GetWorldHourOfDay(), GetWorldTime() (game seconds since day zero) |
UIAction |
the game’s own UI elements from Lua - ShowElement, HideElement, CallFunction, SetVariable, the listeners: the road to UI in the game’s own look |
Particle |
SpawnEffect, CreateDecal |
player |
the local player entity: player:GetWorldPos(), player.soul (GetStatLevel, GetSkillLevel, HasPerk …), player.actor, player.human (IsMounted, IsWeaponDrawn …), player.inventory (GetInventoryTable, HasItem …) |
KcdMp |
the API - the events, the state bags, the helpers |
What is not there, on purpose: dofile, loadfile, require (any file on the disk), io, package, debug,
getfenv / setfenv, System.ExecuteCommand and the console, System.BrowseURL, System.Quit, System.LoadTextFile,
the saves, Script.LoadScript. The entity tables (player, what System.GetEntity returns) are the game’s own with
every function of theirs - its scripts (Scripts.pak) show the names and signatures, and a list of them is planned for
this reference. Two warnings: inside the game the script has the game’s power over the player’s own client, so it can
still break their game; and a script that changes the player’s own health, items or position is cheating in the server’s
eyes - the server judges what the client reports, and the inventory audit
is one of the judges. Values, combat, health and items stay the server’s: a client script gets no say in them.
Not an API
Section titled “Not an API”The KcdMp table also holds the client’s own machinery - KcdMp.remote (the other players’ bodies by net
id), KcdMp.labels, the locomotion and animation helpers. They change without notice; a script that reads them should
expect to be fixed after an update.
A first script
Section titled “A first script”The marker round trip: the mode sends a point, the script draws a label there until the player is close, then tells the mode.
The server folder ships it whole as the marker example mode (gamemodes/marker/marker.lua with client/marker.lua: a
checkpoint run around the spawn, laps counted by the server).
-- gamemodes/marker/client/marker.lualocal marker -- {x=, y=, z=} or nil
KcdMp.on_event("marker", function(payload) local x, y, z = payload:match("^([^,]+),([^,]+),(.+)$") marker = {x = tonumber(x), y = tonumber(y), z = tonumber(z)} print("marker at " .. payload)end)
-- the drawing functions show for one frame: draw from the frame hook, not a timerKcdMp.on_frame(function() if marker and player then local p = player:GetWorldPos() local d = math.sqrt((p.x - marker.x) ^ 2 + (p.y - marker.y) ^ 2) System.DrawLabel({x = marker.x, y = marker.y, z = marker.z + 1.5}, 1.4, string.format("%.0f m", d), 1, 0.85, 0.3, 1) if d < 2 then KcdMp.send_event("marker_reached", "") marker = nil end endend)-- gamemodes/marker/marker.lua, the game mode's sidefunction OnPlayerSpawn(pid) SendClientEvent(pid, "marker", "1290.0,1095.0,26.3")end
function OnClientEvent(pid, name, payload) -- the client says it is there; the mode checks GetPlayerPos before it counts if name == "marker_reached" then GameText(pid, "Checkpoint!", 1500) endendLimits and errors
Section titled “Limits and errors”- A script may send at most 30 events a second and 4 KB each to the server; more is dropped.
- A client half is at most 64 files, 256 KB each, 2 MB together; a file over the limit is left out with a line in the server’s log.
- An error inside an event or state handler is logged (
[KcdMp] event 'name' handler: ...) and the handler is left in place; a frame handler that fails is logged and removed; a syntax or load-time error in a file is logged with the file’s name and line ([KcdMp] client script marker.lua: marker.lua:12: ...) and the other files still run. print(...)andSystem.LogAlways("[KcdMp] ...")land in the KCD:MP client log,%LOCALAPPDATA%\KcdMp\client.log; the F9 window, where the server allows it, shows the console’s own output.- The game’s console splits a line on
;and treatsa=bas an assignment: a text passed through a console command must avoid both. The script events encode their payload, so they carry anything.
