Appearance
events
Messages between scripts. This is how the switch by the door tells the door to open, and how a personal HUD talks to a shop.
luau
local events = require("events")Two channels, and the difference is who can hear
There are four verbs and they come in two pairs. Picking the wrong pair is the most common confusion on this page, so start here.
| Pair | Reaches | Use it for |
|---|---|---|
emit / listen | scripts of the same owner | your own build talking to itself |
send / open | any owner who opted in | your HUD talking to somebody else's shop |
emit is private to you. Another creator's scripts never hear it, ever, even if they guess the channel name. A switch and its door are yours, so this is the pair you want almost every time.
send crosses owners, and both sides must agree. It only reaches scripts that called open on that exact channel. Opening is the receiver's consent — nobody can push a message into a script that never asked for one.
emit
events:emit(channel, data) → true, or nil, reason
Sends to every script of the same owner that is listening on that channel, including the one that sent it.
luau
events:emit("door:open", { by = player.id })channel is any name from 1 to 64 characters. data is optional.
listen
events:listen(channel, fn) → handle
Hears what emit sent.
luau
events:listen("door:open", function(data)
if type(data) ~= "table" then return end
swing_open()
end)Registering the same channel again replaces the previous handler.
A switch and a door
The switch:
luau
--!strict
local self = require("self")
local events = require("events")
self:on_touch(function(player)
events:emit("gate", { by = player.id })
end)The door, which can be anywhere in the region:
luau
--!strict
local self = require("self")
local events = require("events")
local open = false
events:listen("gate", function(data)
open = not open
self:tween({
rot = { x = 0, y = open and 90 or 0, z = 0 },
seconds = 0.6,
ease = "in_out",
})
end)Two switches and five doors work the same way, with no extra code: everything that emits "gate" opens everything that listens for it.
send
events:send(channel, data) → true, or nil, reason
Sends on a public channel, to every script in the region that has that channel open — whatever owner they belong to. It never comes back to the sender.
luau
events:send("acme.shop", { want = "rope" })Channel names are global to the region. Two creators who both pick "shop" are on the same channel and will hear each other. Prefix yours with something nobody else will use — a brand, your name, the build's name: "acme.shop", "mira.lift".
open
events:open(channel, fn) → handle
Declares that this script is willing to be reached by anyone's send.
luau
events:open("acme.shop", function(data, from)
if from.kind ~= "hud" then return end
give(from.owner, data.want)
end)The handler gets a second argument saying who sent it:
| Field | Meaning |
|---|---|
kind | "object" (a world object), "hud" (somebody's personal HUD) or "attachment" (a script on an item they are wearing) |
owner | the id of the account that owns the sending script |
object | the sending object's id, or nil when it came from a HUD |
Treat from as the only thing you can trust. data is whatever the sender chose to put in it; from is stamped by the region.
Registering the same channel again replaces the previous handler.
What can travel
data may be:
- nothing at all
- a boolean, a number or a string
- a table with string keys
Tables are limited to 3 levels deep, 32 entries, and strings of 512 bytes. Anything else is refused with a reason rather than silently trimmed.
Numbers and text come out the other side as what they were — you do not have to convert the way self.store makes you.
The ceilings
| Limit | Value |
|---|---|
Sends per event (emit and send together) | 16 |
| Deliveries before a cascade is cut | 64 |
| Channel name | 1 – 64 characters |
The cascade ceiling is worth understanding. If A emits to B, and B emits to C, and C emits back to A, you have built a loop. The region cuts it at 64 deliveries rather than letting it run. If a message of yours mysteriously stops arriving, a loop is the first thing to look for.
Choosing between events and a shared object
Events are for telling, not for storing. If two scripts need to agree on a number — a score, an amount of fuel — one of them should own it in self.store and announce changes, rather than both keeping a copy.
See also
- self:on_hud — buttons on a menu reporting back
- objects — acting on another object directly
- Talking between scripts — worked examples
