Skip to content

Container Screens

ssbaxys edited this page Oct 26, 2026 · 1 revision

Your own chests and machines with a Lumina screen, synced by the server.

A chest, furnace or custom machine window owned by the server, just like a vanilla chest. MenuSpec is a description shared by server and client (usually a constant): slot sections and synced numbers. On the loader, LuminaMenu is a regular AbstractContainerMenu with vanilla slots (broadcastChanges sends the contents and the cursor, Shift+click defaults to chest behavior), values go through DataSlot (an int as two 16-bit halves), and the screen is a LuminaMenuScreen with the UiRoot from your factory.

// shared code
static final MenuSpec SPEC = MenuSpec.builder()
        .container("chest", 27).playerInventory()        // menu slots: 0..26, then backpack and hotbar
        .intValue("progress").boolValue("burning").build();
static final LuminaMenuType CHEST = new LuminaMenuType(SPEC);
MENUS.register("chest", CHEST::create);                   // DeferredRegister<MenuType<?>>

// server
CHEST.open(player, Component.literal("Chest"), container, index -> progress, (p, action, value) -> sort());

// client: Forge in FMLClientSetupEvent.enqueueWork, NeoForge in RegisterMenuScreensEvent
LuminaMenuScreen.register(CHEST, menu -> new UiRoot(InventoryPanel.chest(menu.title(),
        menu.inventory("chest"), menu.playerInventory(), Slot.Look.MODERN), ModernTheme.dark()));
new ProgressBar().bind(menu.intValue("progress").map(p -> p / 100f));
new Button("Sort", () -> menu.sendAction(SORT, 0));

RemoteMenu gives each section a regular Inventory, but a remote one (Inventory.remote): slots and grids look and drag as usual, but a drop sends vanilla clicks through handleInventoryMouseClick: the server validates and the game predicts the result immediately. A transfer is "pick up" (all or half), "place" and, if something is left on the cursor, "put back"; Shift+click is a quick move (the server decides the route, Slot.quickMoveTo is not needed); outside the slots it's a throw if onDropOutside returned true; keys 1–9 and F over a slot swap with the hotbar and offhand, Q throws (with Ctrl the whole stack). Without remote the inventory is local as before; you can't mix remote and local slots in one transfer.

The screen checks slot contents every frame (reference and count, fully once per tick) and builds a Stack only for the changed ones: name, the game's tooltip lines (with colors), durability, stack limit and the game stack itself (Stack.platformStack()) so potions and heads render exactly. Values are intValue/boolValue signals, updated together once per frame. Your own buttons use sendAction(id, number): the pair is encoded as menu button clicks (clickMenuButton, 7 bits per click, usually two), so no custom network channel is needed. Demo: /luminatest chest.


← Inventory and Slots · English home · Mobs, Items and 3D →

Clone this wiki locally