Skip to main content
Version: Minecraft 26.2

Block Entities and Menus

Add blockEntity(...) to a block definition, and each such block can have saved data, code that runs every tick, and an inventory.

Data and ticks​

BlockDefinition.builder().blockEntity(BlockEntityDefinition.builder()
.inventory(3, "container.my_mod.crate") // 27 slots; using the block opens a chest menu
.ticker(entity -> entity.data().putInt("ticks", entity.data().getInt("ticks", 0) + 1))
.syncToClients()
.build());

world.blockEntityAt(pos).flatMap(BlockEntityView::inventory).ifPresent(inv -> inv.add(item, 10));
  • Data: entity.data() is a DataTree. It stores integers, longs, floating-point numbers, strings, booleans, byte arrays, and subtrees (child), and it is saved with the world. Call entity.markChanged() after you change it.
  • Ticks: ticker runs every tick on the server.
  • Client sync: With syncToClients() on, the data goes to the client on markChanged(). Turn it on if block entity drawing needs the data.
  • BlockEntityView: world(), pos(), data(), inventory(), markChanged()

Chest-style inventory​

inventory(rows, title) uses the game's chest menu (1 to 6 rows).

  • It works as a game container. Hoppers and comparators work with it, and its contents drop when the block is broken.
  • InventoryView: size, get(slot), set(slot, item, count), add(item, count) (returns the count that did not fit), count(item), stackAt(slot), clear

Slot-layout menu and progress bar​

For a menu that is not chest-shaped, place the slots yourself with MenuLayout.

BlockDefinition.builder()
.blockEntity(BlockEntityDefinition.builder()
.menu(MenuLayout.builder(176, 166)
.slot(56, 35).outputSlot(116, 35) // an input slot, and a take-only slot
.progressBar(79, 34, MenuLayout.ProgressStyle.ARROW, "progress", "total")
.build(), "container.my_mod.grinder")
.ticker(entity -> { /* every tick: entity.data(), entity.inventory() */ })
.syncToClients()
.build())
.build();
MenuLayout.BuilderMeaning
slot(x, y) / outputSlot(x, y)An input slot / a take-only slot
grid(x, y, columns, rows)A grid of slots
playerInventory(x, y)Position of the player inventory
background(texture)Background texture
progressBar(x, y, style, valueKey, maxKey)Progress bar (ARROW, FLAME)
  • The inventory size equals the number of slots in the layout. A menu type with the same name as the block ID is registered.
  • If you give no background texture, Poly Mod Loader draws a gray panel with slot borders, similar to the game's.
  • The progress bar is filled from two integer keys in the block entity data (value and maximum). They are sent to the client while the menu is open, and the range is -32768..32767.
  • You cannot combine it with chest rows (inventory(rows, …)).

Opening a menu​

PlayerView.openMenu(pos) opens both mod menus and game menus (such as a furnace). Call it on the server thread.

player.openMenu(pos);

Example: grinder tick​

The grinder in examples/native-showcase-mod turns one cobblestone into gravel every 40 ticks.

private static void grind(BlockEntityView entity) {
InventoryView inventory = entity.inventory().orElseThrow();
DataTree data = entity.data();
ItemStackView input = inventory.get(0);
ItemStackView output = inventory.get(1);
ResourceId gravel = ResourceId.minecraft("gravel");
boolean canGrind = input.item().equals(ResourceId.minecraft("cobblestone"))
&& (output.isEmpty() || (output.item().equals(gravel) && output.count() < 64));
if (!canGrind) {
return;
}
int progress = data.getInt("progress", 0) + 1;
if (progress >= 40) {
inventory.set(0, input.item(), input.count() - 1);
inventory.set(1, gravel, output.count() + 1);
progress = 0;
}
data.putInt("progress", progress).putInt("total", 40);
entity.markChanged();
}

Data versions​

If you change the format of block entity data, convert the old data with dataVersion(version, migration). See Saved data and versions.

Buy me a coffee