Skip to content

Events and Messages

Liam Robinson edited this page Nov 8, 2026 · 1 revision

Events and Messages

Scripts talk to the game, to each other and across the network through events, element data and exports.

Events

addEvent("teams:changed")                          -- declare; only scripts on this side raise it

addEventHandler("teams:changed", root, function(team)
    print("teams: " .. (getPlayerName(source) or "?") .. " is " .. team)
end)

triggerEvent("teams:changed", source, new)         -- raise it on an element (here in a handler)
  • A handler on an element hears events raised on that element and, with propagate (the default), on its parents and children. A handler on root hears everything.
  • Handlers run oldest first, each with copies of the arguments. In a handler, source is the element the event was raised on, this the element the handler is on, eventName the name, and on the server client the sending player for a remote event.
  • cancelEvent() in a handler makes triggerEvent return false; what that means is up to the event.
  • Events may nest 16 deep.
  • Names: 1 to 128 characters of A-Z a-z 0-9 _ . : -. Prefix your events with your resource's name.

Between the server and players' games

-- server (from the welcome resource on Server Resources)
addEvent("welcome:hello", true)                    -- true: players' games may raise it
addEventHandler("welcome:hello", root, function(text)
    triggerClientEvent(client, "welcome:reply", root, "Hello back from the server")
end)

-- client
addEvent("welcome:reply", true)
addEventHandler("welcome:reply", root, function(text)
    line = text
end)
triggerServerEvent("welcome:hello", localPlayer, "hi")
Server to games Game to server
call triggerClientEvent(sendTo, name, source, ...) triggerServerEvent(name, source, ...)
arguments 64 KiB 64 KiB
large data triggerLatentClientEvent(sendTo, name, bandwidth, source, ...) triggerLatentServerEvent(name, bandwidth, source, ...)
latent size, rate up to 16 MiB at 500 B/s to 1 MB/s; 16 under way per package the same; a game sends at most 64 KiB/s of latent data

Every message from a player's game is checked by the server:

  1. The event must be declared remote by the same resource (addEvent(name, true)). Another resource's remote event cannot be raised for it. An event that is not taken raises onPlayerTriggerInvalidEvent on the sender.
  2. The source must be the sender's own player element or an element it owns (setElementOwner).
  3. The arguments must decode strictly within the limits.
  4. Each player may send 50 events a second (a burst of 200), 128 KiB a second (a burst of 384 KiB) and 75 element data writes a second (a burst of 125). Messages over that are dropped and onPlayerEventFlood is raised on the player; a game that floods far beyond it is disconnected.

A message from the server to everyone reaches only the players in the instance its source belongs to; a source outside any instance reaches every player.

Element data

-- server
addEventHandler("onPlayerJoin", root, function()
    -- a value every game sees, which the player's own game may change
    setElementData(source, "team", "red")
    setElementDataClientWritable(source, "team", true)
end)

addEventHandler("onElementDataChange", root, function(key, old, new)
    if key ~= "team" then
        return
    end
    if new ~= "red" and new ~= "blue" then
        print("teams: refused " .. tostring(new))
        cancelEvent() -- the old value comes back, and the player's game is sent it
        return
    end
    triggerEvent("teams:changed", source, new)
end)

-- client
setElementData(localPlayer, "team", "green") -- the server refuses this one

A player's game that writes "green" and later "blue" sees its value go green, back to red, then blue; the server logs:

scripts: teams: teams: Player is red
scripts: client 1: refused: a handler cancelled the write of "team"
scripts: teams: teams: refused green
scripts: teams: teams: Player is blue
sync Sent to
"broadcast" (default) every player's game
"subscribers" the players added with addElementDataSubscriber (they get the value at once)
"local" nobody

A player's game may write a key only if the server opened it on that element with setElementDataClientWritable and the element is that player's or owned by it. Anything else is refused and the server's value is sent back.

Exports

-- in resource "scores"
exports("best", function(name: string): number?
    return best[name]
end)

-- in any other resource on the same side
local points = exports.scores:best("Snow Fox")

Arguments and results are copied (1 MiB each way); functions cannot be passed. The called function runs under its own resource's budget. Call with :. Export calls nest at most 8 deep. If the other resource is not running the call raises an error. Name the resource in [dependencies] require so it starts first.

Engine events (server)

Event Raised on Arguments When
onResourceStart the resource's root the resource started (use resourceRoot to hear only your own)
onResourceStop the resource's root it is stopping; its storage is written after
onResourcePreStart root resource another resource is about to start; cancelEvent() keeps it from starting, only with the right function.stopResource
onResourceStateChange root resource, old, new a resource moved between loaded, starting, running, stopping, stopped
onElementDataChange the element key, old, new data changed; client is the writing player; cancelEvent() restores the old value
onPlayerConnecting root name, keyId, join a player passed the server's own checks and waits for a slot; keyId is the 64-hex player id. cancelEvent() refuses; see deferJoin
onPlayerJoin the player the player joined
onPlayerQuit the player reason the player left
onPlayerResourceStart the player resource the player's game started a resource's client scripts
onRoundStart the instance round, drop a round began
onRoundEnd the instance round a round ended
onRiderCheckpoint the rider checkpoint?, index, clock the rider passed a split (checkpoint is nil for the level's own gates, whose index counts from 0; clock in ms)
onRiderFinish the rider clock, points, failed the rider's run ended
onMarkerHit the marker or trigger rider, forward a rider hit it
onPlayerTriggerInvalidEvent the player eventName, isAdded, isRemote, resource the player's game sent an event that was not taken
onPlayerEventFlood the player dropped messages from the player were dropped for its rate
onShutdown root roundEndSecs the server is stopping; players are told after your handlers ran

Engine events (client)

Event Raised on Arguments When
onClientResourceStart, onClientResourceStop the resource's root start and stop
onClientElementDataChange the element key, old, new data changed (a value from the server cannot be cancelled)
onClientRender root every frame; the only place draw.* works
onClientKey root key, down a bindable key (Client Script API)
onClientRiderLand the local rider airTime, bail the rider touched down (seconds in the air; whether it bailed)
onClientTrick the local rider name, points a trick scored
onClientMarkerHit the marker rider, forward the local rider hit a marker

Clone this wiki locally