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

FieldDefaultDescription
name(required)The interactable’s identifier.
hitbox(required){ width, height } in blocks.
requires-supporting-blocksfalseWhether this object needs solid ground beneath it to be placed.
time-to-break-ms1000How long it takes to break with a valid tool.
power-required1Minimum 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).
breakabletrueWhether 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.
collidabletrueWhether entities/players collide with it physically.
interact-cooldown-ms0Minimum 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-range10.0How 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-selffalseShorthand for “drops exactly one of itself” — see Drops Self.
draw-priority0Client-only. Draw order for this placed object.
shader(none)Client-only. Overrides the render shader for this object.
itemtrueWhether 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-types is set, the tool must have at least one matching entry (via the tool’s own tool-types); if it’s left empty, any tool — or a bare hand — qualifies, and
  • The tool’s power-level must 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:

  • type is required; tier is optional (omitting it means the type’s base tier). type present with a bad value is still a load-time error; a half-configured station never silently loads.
  • type is an open, case-insensitive tag matched against a recipe’s crafting-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’s Crafting/Smelting you can invent your own categories (Anvil, Loom, …) — the station and its recipes just have to use the same tag. See crafting-config.md.
  • tier names one of the ordered tiers declared for this type in a crafting-types block. A station satisfies a recipe when its tier is at least as high, by list position — so a bench station can make both hand- and bench-tier crafting recipes.
  • Your station’s client-on-interact hook opens the crafting menu client-side by calling display_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 this crafting-station config — so the function’s old type, tier arguments are no longer needed (they are still accepted, and ignored, so existing scripts keep working). The crafting-station config 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 Crafting type with no station nearby (this matches the crafting menu the inventory opens by default). Any higher tier, or any recipe of another type (including Smelting), 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 no key-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 matching key-item as their selected item unlocks it permanently for everyone — the contents are then revealed to all clients. Requires key-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:

  • key mode requires key-item; owner mode 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 lock config 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? }. item is the item name (mod-prefixed like any reference), price is the coin cost of one purchase, and quantity (default 1) 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’s sells-for per unit; an item with no sells-for is 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 authored reforge envelope) and pick one behavioral affix from the affix pool its tags allow.
  • socket — insert infusion items into the weapon’s sockets, or remove them (returned intact). An infusion item is one whose name matches an infusion entry.
  • salvage — consume an item for its authored salvage byproducts, 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: keySideCalled
server-on-createServerWhen placed
server-on-interactServerWhen a player interacts with it
server-on-inventory-closeServerWhen a player closes its inventory UI
server-updateServerEvery tick
client-on-createClientWhen received/loaded
client-on-interactClientWhen a player interacts with it
client-updateClientEvery 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