Skip to content

Talking between scripts

Three ways for scripts to reach each other, and when each is right.

WayReachesGood for
events:emit / listenyour own scriptsa switch and its door
events:send / openanyone who opted inyour HUD and somebody else's shop
objectsone object you can addresspainting every lamp

Your own build talking to itself

The sender:

luau
--!strict
local self = require("self")
local events = require("events")

self:on_touch(function(player)
    events:emit("alarm", { by = player.id })
end)

Every listener:

luau
--!strict
local self = require("self")
local events = require("events")

events:listen("alarm", function(data)
    self:set({ color = "#e35d6a", emissive = 3 })
    self:after(5, function()
        self:set({ color = "#3a3a3a", emissive = 0 })
    end)
end)

One button, twenty lamps, no list of ids anywhere. Adding a lamp is copying the listener onto it.

emit is private to you. Another creator's scripts never hear it, even if they guess the channel name.

Two scripts on the same object

They do not share variables, but they hear the same events:

luau
-- main.luau
events:emit("state", { open = true })

-- lock.luau
events:listen("state", function(data)
    -- ...
end)

emit reaches every script of the same owner including the one that sent it, so a script can listen to its own channel. That is occasionally what you want and occasionally a loop — see below.

Crossing owners

Your personal HUD is yours; the shop is somebody else's. emit cannot reach across, and that is deliberate.

The receiver opens a door:

luau
events:open("acme.shop", function(data, from)
    if from.kind ~= "hud" then return end
    serve(from.owner, data)
end)

The sender knocks:

luau
events:send("acme.shop", { want = "rope" })

open is the receiver's consent. Nothing can push a message into a script that never asked for one.

The from table is stamped by the region and is the only part you can trust:

Field
kind"object", "hud" or "attachment"
ownerthe account that owns the sending script
objectthe sending object's id, or nil from a HUD

Channel names are global to the region. Two creators who both pick "shop" are on the same channel. Prefix yours: "acme.shop", "mira.lift".

Reaching a specific object

When you know which object, objects is more direct than a broadcast:

luau
local objects = require("objects")

local lamps = objects:tagged("streetlight")
if lamps then
    for _, lamp in ipairs(lamps) do
        lamp:set({ light = true })
    end
end

Tags beat names: a name is what a person reads and may rename; a tag is what a script means.

You cannot read another object's state, only act on it. Tags are the way round it: an object's own script controls its tags, so a tag like "door.open" is the public half of an object that anyone may check with has_tag.

Choosing: event or tag?

Eventsomething happened. A moment
Tagsomething is true. A state

A door that just opened emits; a door that is open carries a tag. Somebody arriving later missed the event but can still read the tag.

What can travel

Nothing, a boolean, a number, a string, or a table with string keys — 3 levels deep, 32 entries, 512-byte strings. Anything else is refused with a reason rather than trimmed.

Values arrive as what they were; unlike self.store, you do not convert.

Always check the shape you were given:

luau
events:listen("alarm", function(data)
    if type(data) ~= "table" then return end
    local who = tostring(data.by or "")
    -- ...
end)

Loops

If A emits to B, 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 mysteriously stops arriving, a loop is the first thing to check. The usual fix is a guard:

luau
local settling = false

events:listen("state", function(data)
    if settling then return end
    settling = true
    -- ... change things, which may emit ...
    settling = false
end)

Ceilings

Sends per event (emit + send)16
Cascade cut at64 deliveries
Channel name1 – 64 characters

Registering the same channel twice replaces the handler — a script has one handler per channel, not a list.

Built-in channels

The world reports on its own channels; listen with events:listen:

Channel
world.destroyedsomething with health was destroyed
world.collapsea building piece lost its support and came down
world.blocka block was placed or broken
world.builda building piece was raised

See also

Hungrit scripting documentation.