Shared Blocks

The nested blocks that appear identically across many configs — hitbox, light, drop-table, scripts and particles — documented once, with the per-config differences called out.

Most of a config file’s fields belong to that file. A handful don’t: the same nested block, with the same keys and the same meaning, appears inside blocks, items, mobs, mounts, trees, interactables and biomes. This page is the reference for those, so a light: block means one thing everywhere and you only have to learn it once.

Each section below says which configs accept the block and flags any per-config difference. Where a config genuinely bends the shared shape, its own page says so — but the shape starts here.

BlockAccepted by
hitboxitems (inside wielded-item-data), mobs, mounts, trees, interactables, projectiles, player
lightblocks, items, interactables, particles
drop-tableblocks, mobs, mounts, trees, interactables
scriptsblocks, items, mobs, mounts, interactables, weather, player
particlesblocks, trees, interactables, biomes

hitbox

A rectangle, in blocks, as floats. Both keys are required when the block is present.

hitbox:
  width: 2.0
  height: 3.0
FieldRequiredDescription
widthYesWidth in blocks.
heightYesHeight in blocks.

Two notes on shape and units:

  • It is width/height, not x/y. Sizes use width/height throughout the schema; x/y is for positions, offsets and velocities. See Schema Conventions → 2D vectors.
  • Blocks, not pixels. A 2.0 × 3.0 hitbox is two blocks wide and three tall (32 × 48 px at the default BLOCK_SIZE_PX).

The rectangle is anchored at the entity’s position and is the box used for collision and for hit detection. A mob may additionally declare damage-hitbox, an identically-shaped block that replaces the collision hitbox for taking hits — use it when a mob’s art is much larger or smaller than the volume it should physically occupy. When absent, the mob is hit on its hitbox.

Whether hitbox is optional is decided by the config, not by this block. Both keys are required within the block, so a half-written hitbox: { width: 2.0 } always fails — but what an entirely absent hitbox does varies: a mob or interactable is skipped with an error (it’s a required field there), a projectile falls back to 1.0 × 1.0, a tree variation inherits the tree-level default, and the player falls back to the engine baseline. Each config’s own page gives its rule.


light

Makes the thing that carries it emit light. Accepted on a block (the placed block glows), an item (dropped copies lying on the ground glow), an interactable, and a particle.

light:
  magnitude: 8.0              # optional, default 4.0. Radius of the full-brightness core, in blocks.
  intensity: 1.0              # optional, default 1.0. Peak brightness multiplier.
  color:                       # all four channels required if `light` is present
    r: 1.0
    g: 0.945
    b: 0.4627
    a: 0.5
  drop-off-distance: 3.0      # optional, default 4.0. Length of the fade band beyond the core, in blocks.
  is-static: false             # optional, default false. See below.
FieldDefaultDescription
magnitude4.0Radius of the full-brightness core, in blocks.
intensity1.0Peak brightness multiplier, 0.01.0.
color(required)RGBA, float channels 0.01.0. All four required.
drop-off-distance4.0Length of the fade band beyond the core, in blocks.
is-staticfalseWhether the light is baked into its world sector. See below.
frames(none)Blocks only. Per-texture-frame magnitude/intensity overrides — see Animated lights. Accepted but unused on items, interactables and particles.

How the three numbers combine

magnitude, intensity and drop-off-distance are often mistaken for three ways of saying “brighter”. They are not — they describe three different parts of the same falloff curve:

FieldUnitWhat it controls
magnitudeblocksThe radius of the full-brightness core. Everything within this distance is lit at full intensity. It is a distance, not a brightness.
intensitymultiplier (0.01.0)The peak brightness the colour is drawn at, inside the core. This is the “brighter/dimmer” knob.
drop-off-distanceblocksThe length of the fade band outside the core, across which brightness falls linearly from intensity to zero.

The light’s total reach is magnitude + drop-off-distance — so the example above reaches 11 blocks: 8 at full strength, then a 3-block fade to nothing.

That gives you two independent shapes to tune. A torch that lights a small area harshly is a small magnitude with a large drop-off-distance (a tight core, a long soft glow). A lantern that fills a room evenly is a large magnitude with a small drop-off-distance (a wide even pool, a sharp edge). Turning intensity down dims the whole curve without changing how far it reaches.

Setting drop-off-distance: 0 is treated as a very small positive number rather than dividing by zero, which gives you a hard-edged circle of light.

is-static

is-static controls how the light is tracked. A static light (true) is baked once into its world sector — cheaper, but it must not move; use it for lights fixed in the world. A dynamic light (false, the default) is recomputed every frame and may move with whatever carries it, at slightly higher cost. Anything that moves — a dropped item that can be kicked around, a drifting particle — must be dynamic, or its light will be left behind at the baked position.

Animated lights

A block light may add a frames: list, one entry per frame of the block’s texture animation, to pulse the light in step with the art:

light:
  magnitude: 3.0
  color: { r: 1.0, g: 0.9, b: 0.4, a: 1.0 }
  frames:
    - magnitude: 4.0
      intensity: 0.8
    - magnitude: 3.0

Each entry takes magnitude and intensity, both optional, each falling back to the light’s own base value when omitted. Declaring any frames forces the light dynamic regardless of is-static — reserve it for a handful of accent lights rather than bulk content, since dynamic lights cost considerably more to render.


drop-table

What something leaves behind when it breaks or dies. Accepted on blocks, mobs, mounts, trees (and each tree variations entry) and interactables.

A drop-table is written one of two ways, and they are mutually exclusive:

  • Inline — declare drops: and/or groups: here.
  • By referenceloot-table: names a standalone loot table to roll instead.
drop-table:
  drops:                       # independent rolls — each entry rolls on its own
    - item: MyMod:my_item
      min-amount: 1
      max-amount: 1
      chance: 1.0
  groups:                      # weighted pick-one — each group yields at most one entry
    - entries:
        - item: MyMod:common_drop
          weight: 0.9
        - item: MyMod:rare_drop
          weight: 0.1

drops — independent rolls. Each entry rolls independently: chance is the probability (0.01.0) that entry drops at all, and if it does, the amount is randomized between min-amount and max-amount. Any number of entries can drop on a single break — zero, some, or all of them.

groups — weighted pick-one. Each group is a list of entries and rolls once, selecting exactly one entry, weighted by each entry’s weight relative to the group’s total. Multiple groups are independent of each other (e.g. one group “always drop the object itself”, plus a separate “rare bonus” group). weight is a relative weight, not a probability — a lone entry in a group always wins it regardless of its number, so weight: 0.5 on a single-entry group drops every time, not half the time. (Use a drops: entry with chance: 0.5 if you want a 50% roll.)

The two selection keys are deliberately different words because they behave differently. Writing the wrong one for the context is a load-time error, not a silent mis-roll: weight inside drops:, or chance inside a groups: entry, fails the table with a message saying which key that context takes.

Entry fields

FieldDefaultDescription
item(required)What to drop — a bare item name, or a full nested itemstack reference. See below.
chance1.0drops: entries only. Independent probability, 0.01.0.
weight1.0groups: entries only. Relative weight within the group’s pick-one.
min-amount / max-amount1 / 1Quantity range, inclusive. min-amount must be ≥ 0 and max-amountmin-amount, or the whole table fails.
amount(none)Shorthand fixing the quantity: amount: N equals min-amount: N + max-amount: N. Mutually exclusive with them — setting both is a load-time error.

item — the thing to drop. Either a bare item name (item: MyMod:my_item, the shorthand used in every example above) or a full nested itemstack reference (item: with a name: and optional overrides — the same block an item entry uses). Every other key in a drop entry is validated: an unrecognized key — including an itemstack field written at the entry’s top level instead of nested under item: — is a load-time error naming the bad key.

loot-table: — defer to a named table

Instead of listing entries, point at a standalone loot table:

drop-table:
  loot-table: boss_hoard

The table is rolled exactly as it would be when filling a container — every guaranteed entry, then min-rolls..max-rolls weighted picks — and the results drop as loose items. loot-table is mutually exclusive with drops/groups; declaring both is a load-time error rather than a silent precedence rule. The name is mod-prefixed like any other reference, and resolved when the drop happens rather than at load, so mod load order never matters. See Loot Table Configuration.

drops-self — the one-line shorthand

Blocks and interactables only. Mobs, trees and mounts share drop-table but have no drops-self.

drops-self: true

Exactly equivalent to a drop-table with a single group holding this object’s own item at chance: 1.0 and min/max of 1, and it requires an item registered under the object’s own name. If both drops-self and drop-table are given, drop-table wins and a warning is logged.


scripts

A map of Lua hook name → path to a .lua file that returns the handler function.

scripts:
  server-update: /mobs/my_mob/scripts/server_update.lua
  client-on-interact: /mobs/my_mob/scripts/client_interact.lua

Paths are relative to your mod’s folder, with or without a leading slash/mobs/my_mob/x.lua and mobs/my_mob/x.lua resolve identically. It is never a filesystem-absolute path. (This is a different rule from require, which resolves module names under your mod’s scripts/ and lib/ folders — see Lua Modding Overview.)

A hook name the config doesn’t recognize is a load-time error that fails the entry. Hook sets differ per config — a mob has no server-on-primary-use, an item has no client-update — so a name that is valid on one config is a typo on another, and is reported as one. This is deliberate: a silently-unrecognized hook name is a script that never runs, with nothing in the log to say why.

Which hooks each config takes

ConfigHooks
Mobserver-update, server-on-create, server-on-death, server-on-mounted, server-on-dismounted, server-on-dialogue-open, server-on-dialogue-choice, client-update, client-on-create, client-on-death, client-on-mounted, client-on-dismounted, client-on-interact
Mountserver-update, server-on-create, server-on-death, server-on-mounted, server-on-dismounted, client-update, client-on-create, client-on-death, client-on-mounted, client-on-dismounted, client-on-interact
Interactableserver-on-create, server-on-interact, server-on-inventory-close, server-update, client-on-create, client-on-interact, client-update
Blockserver-on-interact, server-update
Itemserver-on-primary-use
Weatherserver-update, server-on-start, server-on-end
Playerserver-update

Hook names follow one rule, so you can predict them. <side>-update is the per-tick hook — it isn’t an event, so it takes no on-. Everything else is an event, and every event reads <side>-on-<event>: server-on-create, client-on-death, server-on-interact, server-on-inventory-close. The only thing left to check per config is which events it offers, which is what the table above is for.

The API each hook is called with lives in the Lua reference — Server Entity API, Client Entity API, Server Weather API and NPC Dialogue.


particles

Ambient particle emission — a list of emitter groups, accepted on blocks, trees (and tree variations), interactables and biomes. The shape is shared, but the filters a group may carry depend on the owner, because each owner answers a different question about where particles appear.

This block is documented in full on the particle page, alongside the particle definitions it references: Emitting particles — the particles: block, with the per-emitter fields at Emit Random.


Changing how something looks: three mechanisms

Not a shared block, but the question most often asked of one — the schema has three ways to change an object’s appearance at runtime, they are not interchangeable, and only one of them is named:

MechanismApplies toWritten asPersisted as
Block stateblocksblock-state: 2 in the block texture config; block.state = 2 in Luaone byte, per block
Block variantblocksvariant-of: {base-block-name, variant-name}; get_block_variation(id, "snow") in Luaa different block id
Object stateinteractables (and other static objects)states[].name; set_state("door_open") in Luaa name string, per object

Which to reach for:

  • Block state — a block that cycles or toggles appearance in place, cheaply, driven by a script (a lamp, a machine). It’s a bare number 0255 with no name anywhere, and the texture config’s block-state: is the only place the number appears in config. Keep a comment next to it.
  • Block variant — a lasting swap to a different block, where the two are separate blocks that happen to be related (grasssnowy_grass). The variant is fully registered in its own right, with its own drops and hardness.
  • Object state — a named pose for a placed object. This is the readable one, because static objects are few enough to afford a string each.

Why blocks don’t get names. A chunk is 32 × 32 blocks across three layers — about 3000 blocks, each carrying one state byte, in every saved chunk and every chunk packet. Names would cost a length-prefixed string per block, tens of kilobytes per chunk against a 64 KB packet limit. The numeric form isn’t an oversight; it’s what makes per-block state affordable at all. Interactables are orders of magnitude rarer, so they can spend the bytes.


Where to go next