Skip to content

Callbacks

A game mode is a set of global functions the server calls. All of them are optional; define the ones you need. They run on the simulation thread, inside a tick, one at a time — see the tick.

Where a callback returns something, the return decides what the server does next; returning nothing keeps the default.

function OnGameModeInit() end -- after the script loaded (and after every hot reload)
function OnGameModeExit() end -- at shutdown, or just before a hot reload disposes this VM
function OnTick(dt) end -- every simulation tick; dt in seconds (0.033 at 30 Hz)

OnTick is the one callback that runs whether or not anything happens. Keep it cheap — a slow tick delays everyone and shows up in the tick budget report; timers are the better home for periodic work.

function OnPlayerConnect(pid) end -- handshake done; the client is loading the level
function OnPlayerRequestSpawn(pid) return true end
function OnPlayerSpawn(pid) end -- in the world, replicated to the others
function OnPlayerDisconnect(pid, reason) end -- reason: "disconnected", "timed out", "kicked: ..."
function OnPlayerLogin(pid) end -- the player proved a registered name (/login) or claimed it (/register)

OnPlayerRequestSpawn(pid) — the level is loaded on the client. Return false to hold the player (the client shows “waiting for the spawn”) until the mode calls SpawnPlayer(pid); anything else spawns them now, at the point set by SetSpawnInfo or the server’s default spawn from server.toml. OnPlayerSpawn follows either way.

OnPlayerDisconnect(pid, reason) — the name is still readable inside the callback; the id is free for the next player after it returns.

function OnPlayerText(pid, text) return true end -- false: the line is shown to nobody
function OnPlayerCommandText(pid, cmd, args) return false end -- "/tp 1 2 3" arrives as ("tp", "1 2 3")

OnPlayerCommandText — return true when handled. Otherwise the server’s built-in commands answer, then Unknown command: /x. A mode overrides a built-in by handling its name; /register, /login, /fight and /peace are the server’s own and never reach the mode.

function OnPlayerDamage(pid, attacker, damage, zone, part, weapon, dtype) end
function OnPlayerInjury(pid, attacker, part) end
function OnPlayerDeath(pid, attacker) end
function OnFightStart(a, b, reason) end
function OnFightEnd(a, b, reason) end

OnPlayerDamage(pid, attacker, damage, zone, part, weapon, dtype) — a hit the server accepted on pid. attacker is -1 for none. part is 16 (BODY_PART_HEADBODY_PART_LEG_RIGHT, 0 unknown; GetBodyPartName(part)), weapon the attacker’s weapon name from the tables ("" bare-handed or without them), dtype "stab", "slash" or "smash" ("" unknown). Return a number to change the damage (0 cancels it), false to cancel it, nothing to keep it. A hit between two players of one team is refused before this callback and starts no fight.

OnPlayerInjury(pid, attacker, part) — the hit injured the part: an arm means weaker swings, head or torso slower stamina.

OnPlayerDeath(pid, attacker) — health reached 0 (a hit, a bleed-out, SetPlayerHealth); the respawn follows after [combat] respawn_seconds.

OnFightStart(a, b, reason) — the two fight from now on; their clients let the lock-on pick each other’s body. reason: "hit" (a swing reached a body), "command" (/fight), "mode" (StartFight). OnFightEnd(a, b, reason): "timeout" ([combat] fight_timeout), "death", "left", "peace", "mode".

function OnPlayerEnterZone(pid, zone) end -- the player's position of record entered the zone
function OnPlayerLeaveZone(pid, zone) end -- ... and left it

“Left” covers walking out, being moved out, a despawn, a respawn elsewhere and a disconnect. See Zones.

function OnClientEvent(pid, name, payload) end -- the player's client script sent kcd2mp.send_event(name, payload)

At most 30 a second and 4 KB each per client. See Script events.

function OnPlayerMount(pid, id) end -- the player is in the saddle of horse `id`
function OnPlayerDismount(pid, id) end -- off the horse (or gone while riding)
function OnPlayerPickup(pid, id) return true end -- false refuses the pickup (the item goes back)
function OnPlayerDrop(pid, id) end -- the player dropped an item; the world made pickup `id` of it
function OnPlayerUseItem(pid, class, health, buff) end -- the player consumed an item
function OnPlayerDrunk(pid, drunk) end -- crossed the drunk threshold (true) or fell back (false)

OnPlayerDrop(pid, id)GetEntityTemplate(id) is the item’s class; DestroyEntity(id) removes it from everyone.

OnPlayerUseItem(pid, class, health, buff) — a potion, food, an ointment: the server is about to add health to the player’s vitals and give them the buff buff (a GUID, "" = none) from the tables. Return false to cancel both (the item is gone from their inventory anyway).

function OnPlayerUseDoor(pid, key, open, locked) end -- the player's game opened / closed / locked / unlocked a level door
function OnPlayerOpenContainer(pid, key) end -- the player wants to open a stash (nothing opened yet)
function OnPlayerCloseContainer(pid, key) end -- they closed it; GetContainerItems(key) is what they left

open and locked are the state as it is now on the player’s client. Return false from OnPlayerUseDoor to refuse — their door is put back and nobody else hears of it; false from OnPlayerOpenContainer refuses the open. See Doors and containers.

function OnPlayerAuditViolation(pid, class, amount, allowed) end

The audit ([audit] in server.toml) found the player holding amount of a guarded item class where the server’s records explain allowed. Return false to vouch for it; otherwise the [audit] action follows — log, kick or ban.

function OnActorArrive(id) end -- reached its MoveActor target
function OnActorDamage(id, attacker, damage, zone, part, weapon, dtype) end
function OnActorDeath(id, attacker) end -- attacker -1 = SetActorHealth
function OnActorAttack(id, pid, damage, zone, part, weapon, dtype) end -- the actor's blow landed on pid

OnActorDamage and OnActorAttack have the shape of OnPlayerDamage, return value included: a number changes the damage, 0 or false cancels it. Without an OnActorAttack the blow arrives as OnPlayerDamage with attacker -1. A dead actor is a corpse until DestroyEntity(id).