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.
Lifecycle
Section titled “Lifecycle”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 VMfunction 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.
Players
Section titled “Players”function OnPlayerConnect(pid) end -- handshake done; the client is loading the levelfunction OnPlayerRequestSpawn(pid) return true endfunction OnPlayerSpawn(pid) end -- in the world, replicated to the othersfunction 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.
Chat and commands
Section titled “Chat and commands”function OnPlayerText(pid, text) return true end -- false: the line is shown to nobodyfunction 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.
Combat
Section titled “Combat”function OnPlayerDamage(pid, attacker, damage, zone, part, weapon, dtype) endfunction OnPlayerInjury(pid, attacker, part) endfunction OnPlayerDeath(pid, attacker) endfunction OnFightStart(a, b, reason) endfunction OnFightEnd(a, b, reason) endOnPlayerDamage(pid, attacker, damage, zone, part, weapon, dtype) — a hit the server
accepted on pid. attacker is -1 for none. part is 1–6 (BODY_PART_HEAD …
BODY_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 zonefunction OnPlayerLeaveZone(pid, zone) end -- ... and left it“Left” covers walking out, being moved out, a despawn, a respawn elsewhere and a disconnect. See Zones.
Script events
Section titled “Script events”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.
Items, horses and drops
Section titled “Items, horses and drops”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 itfunction OnPlayerUseItem(pid, class, health, buff) end -- the player consumed an itemfunction 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).
Doors and containers
Section titled “Doors and containers”function OnPlayerUseDoor(pid, key, open, locked) end -- the player's game opened / closed / locked / unlocked a level doorfunction 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 leftopen 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.
The inventory audit
Section titled “The inventory audit”function OnPlayerAuditViolation(pid, class, amount, allowed) endThe 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.
NPC actors
Section titled “NPC actors”function OnActorArrive(id) end -- reached its MoveActor targetfunction OnActorDamage(id, attacker, damage, zone, part, weapon, dtype) endfunction OnActorDeath(id, attacker) end -- attacker -1 = SetActorHealthfunction OnActorAttack(id, pid, damage, zone, part, weapon, dtype) end -- the actor's blow landed on pidOnActorDamage 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).
