-
Notifications
You must be signed in to change notification settings - Fork 1
FURNITURE INTERACTIONS
Second page of the Furniture section. InteractionType (see FURNITURE-MODEL) is a string, not an enum with generated dispatch — every value is a case in a small number of switches, and this page walks through what each one does. There are two dispatch layers: a generic one for behavior every furniture item might need, and a specialized one for behavior only certain items need.
internal/realm/furniture/interactions resolves the two interaction types that don't need any specialized logic — they just cycle or flip a state string:
// Resolve returns behavior for one definition interaction type.
func (registry *Registry) Resolve(interactionType string) (Behavior, bool) {
switch interactionType {
case "default", "toggle":
return registry.toggle, true
case "gate":
return registry.gate, true
default:
return nil, false
}
}toggle cycles ordinary multi-state furniture (a lamp with several color states) through its InteractionModesCount states in order, wrapping back to zero. gate is a binary behavior: it flips between two states and changes whether the tile is walkable, rejecting the flip if a unit currently occupies the footprint — that occupancy check is what stops a gate from trapping someone under it.
Anything not default, toggle, or gate falls through to layer two.
The essential package owns everything else, dispatched from one switch in Service.Use:
switch request.Item.Definition.InteractionType {
case "dice", "colorwheel", "random_state":
return true, service.useRandom(ctx, request)
case "pressureplate", "colorplate", "handitem_tile":
return true, nil // handled by occupancy events, not clicks — see below
case "onewaygate", "switch", "switch_remote_control", "multiheight":
return true, service.useTraversal(ctx, request)
case "vendingmachine", "vendingmachine_no_sides", "handitem":
return true, service.useHandItem(ctx, request)
case "cannon":
return true, service.useCannon(ctx, request)
case "effect_giver":
return true, service.useEffectGiver(ctx, request)
default:
for _, handler := range service.external {
if handled, err := handler.UseFurniture(ctx, request); handled || err != nil {
return handled, err
}
}
return false, nil
}That default branch is how FURNITURE-ADVANCED's standalone packages (roller, teleport, rentable, lovelock, mystery box, firework, the room-games bridge) plug in without essential knowing they exist — each registers itself as an External handler at startup via AddExternal, and unclaimed clicks fall through the chain until one claims them.
All three resolve to the same delayed-roll mechanic in useRandom, differing only in how many states they roll across and how long the roll takes:
-
dicerequires the clicking player be standing on an adjacent tile (dice are meant to be rolled by someone actually at the table), rolls acrossInteractionModesCountfaces, and settles after 1.5 seconds. -
colorwheelrequires room-furniture management rights rather than adjacency — only someone who can edit the room can spin it — and takes 3 seconds. -
random_stateis the generic case: state count and delay come fromCustomParams(states=6,delay=1000), so a catalog item can define its own random behavior without a bespoke interaction type.
While rolling, the item's ExtraData is set to Nitro's rolling sentinel value; a second click while already rolling is a no-op. The actual roll is scheduled on the room's own task queue rather than blocking the click:
key := scheduledKey(request.Item.ID, 1)
request.Room.ScheduleReplacing(key, delay, func(time.Time) {
result := service.random.IntN(modes) + 1
...
})ScheduleReplacing is the room runtime's delayed-work primitive, covered in ROOMS-RUNTIME — nothing in the interaction packages spawns its own goroutine or timer; every delay is a task scheduled against the owning room. A dice value can also be reset early through Nitro's dedicated close-dice request (CloseDice), which the client sends when a player picks the dice back up.
These four don't respond to clicks at all — useRandom's cousins here are nil-handled in the click switch because the real trigger is a unit stepping on or off the tile, delivered as furniture.walkedon / furniture.walkedoff events:
switch item.Definition.InteractionType {
case "pressureplate":
service.schedulePressure(ctx, active, item)
case "colorplate":
return service.changeColorPlate(ctx, active, item, 1) // -1 on walk-off
case "handitem_tile":
return service.giveHandItem(ctx, active, payload.PlayerID, item, false)
case "effect_tile":
return service.giveTileEffect(ctx, payload.PlayerID, item)
}-
pressureplatedebounces occupancy through a short replacing task (100ms) so a player briefly crossing the tile doesn't cause a visible flicker, then sets state to"1"or"0"based on whether anyone is still standing in the footprint. -
colorplateincrements or decrements a bounded counter (clamped to[0, InteractionModesCount-1]) each time someone steps on or off — a crowd of people on a colorplate visibly deepens its color. -
handitem_tilehands the walking player a hand item on entry (see the vending family below for what "give a hand item" means) without requiring a click. -
effect_tilegrants and immediately enables a gender-specific avatar effect fromEffectMale/EffectFemale, routed through the player realm's effect grant described in USERS-PROFILE, and expires after a fixed duration (24 hours) if not consumed sooner.
Grouped because all four change how units move through the room, and three of the four involve the room's controlled-walk mechanism — pathing a player to a specific tile before the interaction actually fires:
-
switchtoggles immediately if the clicking player is already adjacent; otherwise it walks them to the nearest activator tile first and toggles on arrival, polled once per tick until the walk settles:
func (service *Service) useSwitch(ctx context.Context, request Request, remote bool) error {
...
if adjacentToItem(unit.Position.Point, request.Item) {
return service.toggleFinal(ctx, request)
}
// otherwise: sort activator tiles by distance, controlled-walk to the nearest, toggle on arrival-
switch_remote_controlis the same toggle without the walk — it requires room-management rights instead of proximity, for a switch meant to be operated from anywhere in the room. -
onewaygateonly opens from its front tile and only when the back tile is clear, then briefly sets state to"1"to let exactly one crossing through — the directional gate you can walk through one way but not back. -
multiheightisuseTraversal's default case: it doesn't gate or switch anything by itself, it's the interaction type used together with a definition'sMultiheightconfiguration (see FURNITURE-MODEL) to let a footprint expose more than one walkable height, which the surface resolver described in ROOMS-HEIGHTMAP turns into real sections.
-
handitemgives the clicking player a hand item directly if they're already standing on or adjacent to it — no walk needed. -
vendingmachineandvendingmachine_no_sidesrequire the player to be standing on one of the item's designated activator tiles; if they're not, the same controlled-walk-then-activate pattern used byswitchkicks in. The_no_sidesvariant simply narrows which tiles count as activators for furniture shaped so its side tiles shouldn't trigger it.
An environmental "kick" interaction: clicking near a cannon walks the player to an adjacent activator tile if needed, then after a short lit-fuse delay (750ms, with a 2-second cooldown between shots) fires and forcibly moves whoever is in front of it — the mechanic behind furniture that launches other units.
Picks one effect at random from EffectPool and grants it to the clicking player through the same effect-grant path as effect_tile, the click-triggered counterpart to the walk-triggered version.
If a new definition needs behavior beyond cycling a state, the decision tree following from this page is: does an existing family already cover the shape of the behavior (occupancy-driven vs click-driven vs walk-then-activate)? If yes, extend that family's switch case. If the behavior is genuinely standalone — it needs its own persistence, its own config, its own background work — it belongs as an External handler following the pattern in FURNITURE-ADVANCED, not as a new case bolted onto essential.
Pixels
Getting Started
Architecture
Architecture Internals
Authentication
Users
Navigator
Inventory
Furniture
Rooms
Decoration
Games
Plugins
- PLUGINS-OVERVIEW
- PLUGINS-CREATING
- PLUGINS-LISTENERS
- PLUGINS-EVENTS-REALMS
- PLUGINS-EVENTS-ECONOMY-ROOMS
- PLUGINS-EVENTS-MODERATION-TRADES
- PLUGINS-EVENTS-COMMERCE-WORLD
- PLUGINS-EVENT-FURNITURE-MOVE
- PLUGINS-EVENT-FURNITURE-PICKUP
- PLUGINS-EVENT-ROOM-CREATE
- PLUGINS-EVENT-MARKETPLACE-LIST
- PLUGINS-EVENT-MARKETPLACE-BUY
- PLUGINS-EVENT-PLAYER-PROFILE-UPDATE
- PLUGINS-EVENT-BOT-SPEECH
- PLUGINS-EVENT-GROUP-MEMBERSHIP-CHANGE
- PLUGINS-EVENT-MESSENGER-FRIEND-REQUEST
- PLUGINS-EVENT-MESSENGER-FRIEND-ACCEPT
- PLUGINS-EVENT-CRAFTING-CRAFT
- PLUGINS-WIRED
- WIRED
- PLUGINS-COMMANDS
- PLUGINS-SDK
- PLUGINS-DEPLOYMENT