Skip to content

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.

PairReachesUse it for
emit / listenscripts of the same owneryour own build talking to itself
send / openany owner who opted inyour 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:

FieldMeaning
kind"object" (a world object), "hud" (somebody's personal HUD) or "attachment" (a script on an item they are wearing)
ownerthe id of the account that owns the sending script
objectthe 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

LimitValue
Sends per event (emit and send together)16
Deliveries before a cascade is cut64
Channel name1 – 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

Hungrit scripting documentation.