Skip to content

Transfer & Inventory

Archie provides cross-platform item and fluid storage abstractions, built on top of Common Storage Lib's resource/slot model, that work on both Fabric and NeoForge without platform-specific code. They're designed to back a block entity's or an item stack's inventory/tank and expose it through Archie's GUI framework — the slot types below plug directly into ComposeContainerMenuBase (via either ComposeBlockContainerMenu or ComposeItemContainerMenu).


ArchieItemStorage

ArchieItemStorage is Archie's platform-agnostic item container: a fixed-size list of ArchieItemSlots that implements Common Storage Lib's CommonStorage<ItemResource> (so it supports resource-based insert/extract for capability interop) and Archie's NbtTag serialization for save/load. Create one via NBTHolder:

class MyBlockEntity(pos, state) : NBTBlockEntity(TYPE, pos, state) {
    val items by nbt.itemField(9)  // 9-slot ArchieItemStorage
}

Public surface is deliberately small — size(): Int and get(slot: Int): ArchieItemSlot. Read or mutate the ItemStack in a slot through the ArchieItemSlot it returns (see below), not directly on the storage.


ArchieFluidStorage

The fluid equivalent of ArchieItemStorage: a fixed-size list of ArchieFluidSlots (each capped at a shared limit), implementing Common Storage Lib's CommonStorage<FluidResource> and Archie's NBT serialization, following the exact same shape:

class MyBlockEntity(pos, state) : NBTBlockEntity(TYPE, pos, state) {
    val tank by nbt.fluidField(FluidStack.bucketAmount() * 4)  // 1 tank slot, 4 buckets
}

Display it in a screen with the FluidTank composable, which renders the real fluid texture and tint (not a placeholder color) via a per-loader render bridge — Fabric's FluidRenderHandlerRegistry and NeoForge's IClientFluidTypeExtensions expose a fluid's client appearance through unrelated APIs, so this one spot needed platform-specific code rather than a single cross-loader call.


ArchieEnergyStorage

Implements Common Storage Lib's ValueStorage — the energy analogue of the CommonStorage<ItemResource>/CommonStorage<FluidResource> ArchieItemStorage/ ArchieFluidStorage implement — plus Archie's NBT serialization, mirroring their shape: a capacity-capped Long buffer with resource-style insert/extract.

Archie doesn't register this with Common Storage Lib's EnergyApi.BLOCK/ITEM/ENTITY lookups for you, the same way it doesn't for ArchieItemStorage/ArchieFluidStorage today — wire that lookup registration, and any platform-specific capability bridge (NeoForge's IEnergyStorage, Fabric's Team Reborn Energy API) you still want on top of it, in your own mod. Read/write through getStoredAmount()/getCapacity()/insert()/extract().

class MyBlockEntity(pos, state) : NBTBlockEntity(TYPE, pos, state) {
    val energy by nbt.energyField(10_000)
}

val accepted = tile.energy.insert(amount = 500, simulate = false)

Display it with the EnergyBar composable: EnergyBar(storage = tile.energy).


ArchieItemSlot / ArchieFluidSlot

ArchieItemSlot is one resource-backed slot inside an ArchieItemStorage. It tracks an ItemResource + amount internally and exposes plain ItemStack access:

val slot = myStorage[0]
val current: ItemStack = slot.getItem()
slot.set(ItemStack(Items.DIAMOND, 4))

ArchieFluidSlot is the fluid analogue, backed by FluidResource and a per-slot capacity limit.


Wiring storage into a menu

You don't usually build net.minecraft.world.inventory.Slots by hand. ComposeContainerMenuBase (see gui.md) takes a declarative approach instead: implement registerSlotHandlers() and register each storage under a named group with handler(group, storage, filter). The same mechanism works identically whether the menu is backed by a block entity (ComposeBlockContainerMenu) or an item stack (ComposeItemContainerMenu) — only how you get at the storage in the first place differs.

// Block-entity-backed
class MyMenu(
    id: Int, playerInventory: Inventory, blockEntity: MyBlockEntity,
) : ComposeBlockContainerMenu<MyBlockEntity, MyMenu>(MY_MENU_TYPE, id, playerInventory, blockEntity) {
    override fun registerSlotHandlers() {
        handler("inventory", tile.items)   // ties the "inventory" group to the block entity's storage
    }
}

// Item-backed (e.g. a backpack) - see gui.md's ComposeItemContainerMenu section for the full shape
class MyBackpackMenu(
    id: Int, playerInventory: Inventory, access: ItemContainerAccess,
) : ComposeItemContainerMenu<MyBackpackMenu>(MY_BACKPACK_MENU_TYPE, id, playerInventory, access) {
    override fun registerSlotHandlers() {
        handler("inventory", holder.itemField(27))
    }
}

A Slot composable placed in the screen's layout (matched by group name) is what actually determines each slot's on-screen position and how many slots from the group are shown — the menu creates and manages the underlying ArchieItemMenuSlot/VanillaMenuSlot instances for you based on that layout. ArchieItemMenuSlot bridges an ArchieItemStorage slot; VanillaMenuSlot does the same for a Common Storage Lib AbstractVanillaContainer, for adapting an existing vanilla-style container instead of an ArchieItemStorage. Both accept an ItemStack -> Boolean filter for mayPlace.

See gui.md for how ComposeContainerMenuBase/ComposeContainerScreen fit together.


Slot composable

The Slot composable (gui/Slot.kt) renders an item-slot widget that visually tracks the container menu's slot positions, enabling accurate item rendering inside a ComposeContainerScreen.