Containers and Menus¶
Inventories that work the same on every loader: slot containers with rules, moving items like automation does, finding a block's or entity's inventory, block entities that tick and hold items, and menus that need no screen code. Packages: net.ixdarklord.coolcatcore.api.container, api.handler, api.block, api.menu, api.client.gui.screens.
Slot containers¶
A ContainerLayout describes the slots; a SlotContainer holds the items:
public static final ContainerLayout CRATE_LAYOUT = ContainerLayout.builder(9)
.filter(0, 8, stack -> !stack.is(Items.BEDROCK)) // slots 0-7 refuse bedrock
.role(8, SlotRole.OUTPUT) // slot 8: automation can only take out
.face(Direction.DOWN, 8) // hoppers below only see slot 8
.build();
SlotContainer container = CRATE_LAYOUT.create();
ItemStack rest = container.insert(new ItemStack(Items.IRON_INGOT, 5), false); // returns what didn't fit
ItemStack taken = container.extract(stack -> stack.is(Items.IRON_INGOT), 3, false);
Store one on anything with an attachment: ATTACHMENTS.container("crate_inventory", CRATE_LAYOUT) (see Attachments). For items, implement ContainerItem on the item, and its contents live in the stack's minecraft:container component: ItemContainers.of(stack).
Finding and moving items¶
// Any inventory at a position, as seen from a face (vanilla containers, double chests, composters, your own...):
Container top = HandlerTypes.CONTAINER.find(level, pos, Direction.UP);
ItemStack rest = ItemTransfer.insert(top, new ItemStack(Items.IRON_INGOT, 5), false);
ItemTransfer.move(from, to, 16);
// For repeated lookups, cache it; it's looked up again when the block or block entity there changes:
BlockHandlerCache<Container> cache = HandlerTypes.CONTAINER.createCache(level, pos, Direction.UP);
Container current = cache.get();
You can define your own handler types with HandlerType.create(id, MyHandler.class), and provide them for blocks, block entities, entities and items.
Block entities¶
public static class CrateBlockEntity extends ExtendedContainerBlockEntity {
public CrateBlockEntity(BlockPos pos, BlockState state) {
super(MyRegistries.CRATE_BE.get(), pos, state, MyAttachments.CRATE_INVENTORY);
}
@Override
protected void serverTick(ServerLevel level) { /* runs every tick on the server */ }
}
// In the block:
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
return ExtendedBlockEntity.ticker(type, MyRegistries.CRATE_BE.get());
}
ExtendedContainerBlockEntity is a WorldlyContainer backed by the attachment. Hoppers and other mods see it through the layout's roles and faces. Its contents spill when it's broken, unless the layout says keepContentsOnBreak().
Storage menus¶
StorageMenuDefinition describes a menu's slots and synced values. StorageScreen draws it without any texture:
public static final StorageMenuDefinition CRATE_MENU_DEF = StorageMenuDefinition.grid(9, 1)
.syncValue("free_slots", ByteBufCodecs.VAR_INT, 0, menu -> countFreeSlots(menu.getContainer()))
.button(0, (menu, player) -> menu.getContainer().clearContent())
.build();
public static final RegistryEntry<MenuType<StorageMenu>> CRATE_MENU = MENUS.register("crate", CRATE_MENU_DEF::createMenuType);
// Open it (server side), e.g. from Block#useWithoutItem:
CRATE_MENU_DEF.open(serverPlayer, crateBlockEntity, Component.literal("Crate"));
// Client constructor:
MenuScreenRegistry.register(CRATE_MENU, StorageScreen::new);
For menus of your own, ExtendedMenus.create(factory) makes a MenuType whose factory reads extra data, and ExtendedMenus.open(player, provider, buf -> ...) writes it.
Reference¶
| Type | Description |
|---|---|
ContainerLayout |
builder(size) / of(size): maxStackSize (default 99), filter, limit, role, face, keepContentsOnBreak; create(), codec(), streamCodec(). |
SlotRole |
STORAGE, INPUT, OUTPUT, INTERNAL: what players and automation may do with a slot. |
SlotContainer |
A Container with rules: insert, extract, count, canPlayerPlace, canInsertFrom, canExtractFrom, copyItems, setItems, toContents, dropContents, comparatorSignal, createSlot(slot, x, y), addListener, setValidator. |
ContainerSlot |
A menu Slot that enforces the layout. |
ContainerItem, ItemContainers |
Items with an inventory: containerLayout(stack), ItemContainers.of(stack[, layout]). |
ItemTransfer |
insert, extract, move, slotLimit on any Container, optionally through a face. |
SidedContainerView |
A WorldlyContainer seen from one side. |
api.handler.HandlerType<T> |
create(id, class); registerBlock, registerBlockEntity, registerEntity, registerItem (plus fallbacks); find(level, pos, side), find(entity), find(stack); createCache(...), invalidate(level, pos). |
api.handler.HandlerTypes |
CONTAINER: the built-in inventory lookup. |
api.handler.HandlerProvider, ItemHandlerProvider, HandlerMap, BlockHandlerCache |
Expose handlers from your own block entities, entities and items. |
api.block.ExtendedBlockEntity |
A block entity with attachment shortcuts (get, set, update, modify), handlers, serverTick/clientTick, and ticker(type, expected). |
api.block.ExtendedContainerBlockEntity |
The same, holding a SlotContainer attachment as a WorldlyContainer. |
api.menu.ExtendedMenus |
create(factory), open(player, provider[, data]). |
api.menu.StorageMenuDefinition |
grid(columns, rows) / builder(): slot, grid, playerInventory, size, sync(attachment), syncValue(...), button(id, action); createMenuType(), open(player, blockEntity / hand / inventorySlot, title). |
api.menu.StorageMenu |
The menu: getContainer(), getSource(), get(attachment) (synced on clients), getValue(name, default). |
api.client.gui.screens.StorageScreen |
A texture-free screen for storage menus; extend it to add panels (extractPanel) and buttons (pressButton). |