Skip to content

Overview

A game mode runs on the server. When it needs something on a player’s screen the HUD cannot do — a marker, a sound, a bit of UI, a value read from the game — it ships a client script: a Lua file that runs inside the player’s game, in the game’s own Lua state, with the kcd2mp table the mod adds.

A client script is a file on the player’s machine, run through:

  • the F9 console: kcd2mp_exec <file.lua> — relative paths resolve against the mod DLL’s folder;
  • the game’s command line: -kcd2mp_exec <file.lua> — run once, two seconds after the level is ready (the launcher passes anything after -- to the game: launcher --connect host:7777 --name Henry -- -kcd2mp_exec my-mode.lua).

The script runs on the game thread. Everything the game’s Lua exposes is there (System.LogAlways, System.ExecuteCommand, the entity system), plus kcd2mp.

kcd2mp.player_id this client’s pid, set at the welcome
kcd2mp.on_event(name, fn) a handler for one named event from the mode — Events
kcd2mp.on_any_event a catch-all function(name, payload) for the rest
kcd2mp.send_event(name, payload) to the mode’s OnClientEvent
kcd2mp.state.global, .player[pid], .entity[netId] the replicated state bags, as strings
kcd2mp.on_state(key, fn), kcd2mp.on_state_change hooks on state changes
kcd2mp.labels[netId] the label over a remote body: {text, coloured, r, g, b}, from SetPlayerNameplate / SetPlayerColour
kcd2mp.level() the level’s name, or nil
kcd2mp.player_pos() the local player’s position as "x y z", or "none"

Handler errors are caught and logged ([kcd2mp] event 'name' handler: ...), never fatal to the game.

my-mode.lua
System.LogAlways("[my-mode] client script up as player " .. tostring(kcd2mp.player_id))
kcd2mp.on_event("marker", function(payload)
local x, y, z = payload:match("^([^,]+),([^,]+),(.+)$")
System.LogAlways("[my-mode] marker at " .. x .. " " .. y .. " " .. z)
end)
kcd2mp.on_state("round", function(value)
System.LogAlways("[my-mode] round " .. tostring(value))
end)
kcd2mp.send_event("ready", "")