Interactable Configuration
Register placeable static objects with an inventory and Lua behavior, such as chests, furnaces, and crafting benches.
Interactables are placeable static objects with an inventory and Lua behavior — chests, furnaces, crafting benches, doors, and similar. This is the catalog file that registers them.
File Location
Register the file in your mod’s manifest (mods/<YourMod>/<YourMod>.yaml):
interactable-config: interactable-config.yaml
Then create the file it points to, relative to your mod’s folder:
mods/<YourMod>/interactable-config.yaml
The file is a single list of interactable entries:
interactables:
- name: my_chest
hitbox:
width: 2.0
height: 1.5
requires-supporting-blocks: true
time-to-break-ms: 1000
power-required: 1
tool-break-types: axe
Names are automatically prefixed with your mod name unless you write one already containing a colon.
Minimal Example
interactables:
- name: my_sign
hitbox:
width: 1.0
height: 1.0
Fields
| Field | Default | Description |
|---|---|---|
name | (required) | The interactable’s identifier. |
hitbox | (required) | { width, height } in blocks. |
requires-supporting-blocks | false | Whether this object needs solid ground beneath it to be placed. |
time-to-break-ms | 1000 | How long it takes to break with a valid tool. |
power-required | 1 | Minimum tool power level needed to break it. Note this is a tool tier and is unrelated to crafting-station.tier below, which is the crafting tier this interactable provides — see Schema Conventions → Tool power vs. crafting tier. |
tool-break-types | (none) | Which tool categories can break it — a single string or a list. Leaving this unset means any tool or a bare hand can break it (see Breaking below). |
breakable | true | Whether it can be broken at all. false makes it permanently unbreakable — immune both to being mined directly and to having the block beneath it broken — regardless of tool-break-types. Use it for signs, monuments, or permanent structure pieces. |
collidable | true | Whether entities/players collide with it physically. |
interact-cooldown-ms | 0 | Minimum time between interactions (cooldown). |
default-state | "default" | Initial state name. Must match a states: entry in this object’s texture config, or the object resolves no state — see State names are matched exactly. |
interact-range | 10.0 | How many blocks away this object can be interacted with from. |
crafting-station | (none) | Marks this interactable as a crafting station — { type, tier }. See Crafting Stations. |
light | (none) | Makes the object emit light — same shape as Shared Blocks → light. |
drop-table | (none) | Items dropped when broken — see Drop Table. |
drops-self | false | Shorthand for “drops exactly one of itself” — see Drops Self. |
draw-priority | 0 | Client-only. Draw order for this placed object. |
shader | (none) | Client-only. Overrides the render shader for this object. |
item | true | Whether registering this interactable also registers its type: interactable item, making it obtainable and placeable. Set false for one that must never exist in an inventory — same rule as a block’s item. |
particles | (none) | Client-only. Ambient particle emission — a list of emitter groups, each with its own emit-random. See the particles: block. |
lock | (none) | Server-enforced access lock for an interactable that owns an inventory (a chest) — { mode, key-item? }. See Access Locks. |
vendor | (none) | Makes this interactable a shop the player buys from and sells to — { stock, buys? }. See Vendors. |
enhancement-station | (none) | Makes this interactable a reforge / socket / salvage bench — { kind }. See Gear-Enhancement Stations. |
Unknown keys at the top level of an entry are rejected: a misspelled field is a load-time error naming the bad key, not a silently-ignored one. Its nested blocks are validated the same way — an unrecognized key inside the light block, the scripts block, or a drop-table entry is also a load-time error naming the bad key. See Unknown Fields.
Breaking
tool-break-types and power-required gate which tools can break an interactable:
- If
tool-break-typesis set, the tool must have at least one matching entry (via the tool’s owntool-types); if it’s left empty, any tool — or a bare hand — qualifies, and - The tool’s
power-levelmust be>= power-required.
Leaving tool-break-types empty means “no tool requirement” — any tool or a bare hand can break it (subject to power-required). Set one or more categories to restrict which tools work.
To make an interactable permanent, set breakable: false. This overrides everything above: it can’t be mined directly, and breaking the block beneath it won’t destroy it either (it survives losing its support). Use it for signs, monuments, or fixed structure pieces.
tool-break-types: axe # single tool category
tool-break-types: [axe, pickaxe] # multiple categories, any one qualifies
Crafting Stations
Add a crafting-station block to turn an interactable into a crafting station. While a player stands within the station’s interact-range, the server lets them craft recipes of that type whose required tier (see crafting-config.md) is at most the tier this station provides.
crafting-station:
type: crafting # Crafting, Smelting, or any custom tag your recipes use
tier: bench # a tier name declared for this type; omit for the base tier
Notes:
typeis required;tieris optional (omitting it means the type’s base tier).typepresent with a bad value is still a load-time error; a half-configured station never silently loads.typeis an open, case-insensitive tag matched against a recipe’scrafting-station.type. A recipe declares the station it requires with the same block name and keys an interactable uses to declare the station it provides, so the two sides read identically. Beyond the base game’sCrafting/Smeltingyou can invent your own categories (Anvil,Loom, …) — the station and its recipes just have to use the same tag. See crafting-config.md.tiernames one of the ordered tiers declared for thistypein acrafting-typesblock. A station satisfies a recipe when its tier is at least as high, by list position — so abenchstation can make bothhand- andbench-tiercraftingrecipes.- Your station’s
client-on-interacthook opens the crafting menu client-side by callingdisplay_crafting_menu(). The menu then lists the recipes of every crafting station within range of the player at once (see The craft menu aggregates every station in range), reading each station’s type/tier from thiscrafting-stationconfig — so the function’s oldtype, tierarguments are no longer needed (they are still accepted, and ignored, so existing scripts keep working). Thecrafting-stationconfig is also the server’s independent authority: crafting is validated server-side against nearby stations, so a modified client can’t craft station-gated recipes out of range. - Hand-crafting baseline: every player can always craft the base tier of the
Craftingtype with no station nearby (this matches the crafting menu the inventory opens by default). Any higher tier, or any recipe of anothertype(includingSmelting), requires a station in range.
Access Locks
A chest (any interactable that owns an inventory) can opt into a server-enforced access lock with a lock block. Absent, a chest is never locked. Locks are enforced entirely server-side — a modified client cannot bypass them.
# Player-protected storage: binds to the first player who opens it.
lock:
mode: owner
# Dungeon / progression chest: needs a key item to open.
lock:
mode: key
key-item: brass_key
Two modes:
owner— the first player to open the chest becomes its owner (recorded by persistent player id and saved with the world). Only the owner may open or modify it thereafter; everyone else is refused. Good for player-built storage. Takes nokey-item.key— the chest starts locked, and its contents are hidden on the wire (clients receive an empty grid, so nobody can inspect the loot early). A player who interacts while holding the matchingkey-itemas their selected item unlocks it permanently for everyone — the contents are then revealed to all clients. Requireskey-item(the item name that unlocks it, mod-prefixed like any item reference). The key is not consumed. Good for dungeon reward chests and progression gates.
Notes:
keymode requireskey-item;ownermode forbids it. Either mismatch is a load-time error.- Enforcement covers every inventory action (open, grab, place, take-half, shift-click, sort). An unauthorized attempt is silently refused server-side.
- A key chest hides its contents until unlocked; an owner chest still replicates its contents (the owner check blocks access, not visibility).
- Lock state is per-instance and persists with the world save. If a chest type’s
lockconfig changes, the newest config wins on load (pre-release: no back-compat) — dropping the lock discards any saved owner.
Vendors
A vendor block turns an interactable into a shop the player can buy goods from and sell goods to. Interacting with it opens a trade panel. Prices and stock are authored here; the server re-reads and enforces every transaction, so a modified client can neither set its own price nor buy something the vendor doesn’t sell.
vendor:
stock:
- { item: torch, price: 3 }
- { item: healing_draught, price: 25, quantity: 2 }
buys: [reef_sardine, tangled_kelp]
stock— the goods the vendor sells to the player, each{ item, price, quantity? }.itemis the item name (mod-prefixed like any reference),priceis the coin cost of one purchase, andquantity(default1) is how many the item one purchase yields — so a stack can be sold as a unit. A purchase is all-or-nothing: if the whole bundle won’t fit in the player’s inventory, or they can’t afford it, nothing is bought and no coins are spent.buys— the item names the vendor buys from the player (a single string or a list). The vendor pays each item’ssells-forper unit; an item with nosells-foris refused even if listed here. To sell, the player shift-clicks one of their own inventory slots while the panel is open.
A vendor may have only stock (buy-only), only buys (sell-only), or both. Buying and selling are deliberately separate lists: a buy price is set here per stock entry, while a sell price comes from the item’s own sells-for, so an item’s buy and sell values can never be conflated.
The trade panel opens client-side from the vendor’s client-on-interact hook, which calls the open_trade_panel global. Coins are a per-player, per-save balance (no shared co-op wallet).
Design guidance: the base-game vendor sells only non-power goods — building blocks, decoration, common ingredients — and never gear, tiers, or enhancement, so currency stays a convenience rather than a power shortcut. This is a content convention, not an engine rule: the stock is whatever you list.
Gear-Enhancement Stations
An enhancement-station block turns an interactable into one of the three gear-enhancement benches. Interacting with it opens a panel and the station’s own single work slot — the player drops the gear to enhance into that slot, and the panel’s controls act on it. Every enhancement is validated and executed server-side against the shipped envelope / affix / infusion pools; the client only sends the selection.
enhancement-station:
kind: reforge # reforge | socket | salvage
reforge— set the weapon’s reforge slider offsets (within its authoredreforgeenvelope) and pick one behavioral affix from the affix pool itstagsallow.socket— insert infusion items into the weapon’ssockets, or remove them (returned intact). An infusion item is one whose name matches an infusion entry.salvage— consume an item for its authoredsalvagebyproducts, plus any socketed infusions (returned intact).
The station holds the gear in its own inventory, so its server-on-create hook must size that inventory to the single work slot with set_inventory_size(1, 1), and its client-on-interact hook opens the panel by calling the open_enhancement_panel global. One station provides one kind; a bench offering more than one operation is authored as more than one interactable.
Drops Self
Most interactables just drop one copy of themselves when broken. That’s what drops-self is for:
drops-self: true
It’s exactly equivalent to a drop-table with a single group holding this interactable’s own item at chance: 1.0 and min/max of 1, and requires an item registered under the interactable’s own name. If both drops-self and drop-table are given, drop-table wins and a warning is logged.
This shorthand exists for interactables and blocks only — mobs, trees, and mounts share the drop-table block below but have no drops-self field. For anything more involved than one guaranteed self-drop, use a drop-table.
Drop Table
What the interactable leaves behind when it’s broken — independent drops: rolls, weighted pick-one groups:, or a named loot-table: to defer to:
drop-table:
drops:
- item: MyMod:my_item
chance: 1.0
groups:
- entries:
- item: MyMod:common_drop
weight: 0.9
- item: MyMod:rare_drop
weight: 0.1
This is the shared drop-table block, identical here to the one on blocks, mobs, mounts and trees — see Shared Blocks → drop-table for every field, the chance-vs-weight distinction, the amount shorthand, and deferring to a named loot table.
For how this differs from a standalone loot table, see Drop Table vs Loot Table.
Lua Hooks
Interactables support several Lua callback hooks, declared under a single scripts: map keyed by event name, each value the bare path to a .lua file that returns its handler function (the same shape as items’ server-on-primary-use):
scripts: key | Side | Called |
|---|---|---|
server-on-create | Server | When placed |
server-on-interact | Server | When a player interacts with it |
server-on-inventory-close | Server | When a player closes its inventory UI |
server-update | Server | Every tick |
client-on-create | Client | When received/loaded |
client-on-interact | Client | When a player interacts with it |
client-update | Client | Every frame |
The server-on-interact handler receives (interactable, player_entity_id, player), where player is the interacting player’s entity wrapper — it can reply privately with player:send_message(text) or set the player’s respawn point with player:set_custom_spawn(x, y) / player:clear_custom_spawn().
Complete Example
interactables:
- name: crafting_bench
hitbox:
width: 3.0
height: 1.5
requires-supporting-blocks: true
time-to-break-ms: 500
power-required: 1
tool-break-types: axe
crafting-station:
type: crafting
tier: bench
scripts:
client-on-interact: /interactables/crafting-bench/scripts/client_interact.lua
drops-self: true
- name: door
hitbox:
width: 1.0
height: 3.0
requires-supporting-blocks: true
time-to-break-ms: 1000
power-required: 1
tool-break-types: axe
scripts:
server-on-interact: /interactables/door/scripts/server_interact.lua
interact-cooldown-ms: 400
default-state: door_closed
drops-self: true
Last updated