====== Fabric Transfer API: Item transfer with Storage ====== //This article is part of a series on the Fabric Transfer API. [[tutorial:transfer-api|Link to the home page of the series]].// This tutorial aims to give an overview of the Fabric Item Transfer API. Please make sure that you read the various fluid tutorials before reading this one, since most concepts used in the Fluid Storage API apply to the Item Transfer API. ===== Brief overview ===== The core message is: **The Item Transfer API is used exactly the same as the Fluid Transfer API, but with ''ItemVariant'' instead of ''FluidVariant''**. ==== ItemVariant ==== Item variants represent the "type" of an item, i.e. the Minecraft `Item`, and also an optional NBT compound tag. **They are immutable.** The following examples are very similar to the ''FluidVariant'' ones, since they are the same concept. Reading them is recommended though since ''ItemVariant'' has a few nice functions to work with ''ItemStack''s. // Creating an item variant from an item, without an NBT tag. ItemVariant ironVariant = ItemVariant.of(Items.IRON_INGOT); ironVariant.getItem() // returns Items.IRON_INGOT ironVariant.copyNbt() // returns a copy of the optional nbt tag, in this case null // Creating an item variant from an item, with an NBT tag. NbtCompound customTag = new NbtCompound(); customTag.putBoolean("magic", true); ItemVariant magicIron = ItemVariant.of(Items.IRON_INGOT, customTag); // Creating an item variant from an ItemStack ItemStack specialStack = new ItemStack(Items.IRON_INGOT); specialStack.setCustomName(new LiteralText("My Special Ingot")); ItemVariant specialIngot = ItemVariant.of(specialStack); // Further modifications to the ItemStack will not affect the ItemVariant since all the data is copied by of(...). // Creating an item stack from an item variant ItemStack size1 = specialIngot.toStack(); // no parameter -> count of the stack is 1 ItemStack anySize = specialIngot.toStack(32); // int parameter -> sets the count of the stack Variants are always compared with ''.equals'', NEVER WITH ''=='' ! ironVariant.equals(ironVariant); // returns true ironVariant.equals(magicIron); // returns false // You can easily test if a variant has some item: ironVariant.isOf(Items.IRON_INGOT); // returns true magicIron.isOf(Items.IRON_INGOT); // returns true // The matches function can be used to compare an item variant with an item stack: ironVariant.matches(specialStack); // returns false specialIngot.matches(specialStack); // returns true They can easily be serialized to and from NBT or network packets: // NBT NbtCompound compound = variant.toNbt(); ItemVariant variant = ItemVariant.fromNbt(compound); // Network packets variant.toPacket(buf); ItemVariant variant = ItemVariant.fromPacket(buf); ==== Finding Storage instances in the world ==== ''Storage'' is the type used to represent item containers in the Transfer API. It is queried and exposed through the ''ItemStorage.SIDED'' lookup for access by other mods: // Finding an instance @Nullable Storage maybeStorage = ItemStorage.SIDED.find(world, pos, side); // To expose an instance, use one of the ItemStorage.SIDED.registerFor* functions To learn how to use ''Storage'', please refer to the [[tutorial:transfer-api/storage|Storage tutorial]] since the methods are exactly the same. ==== Dealing with Inventory/SidedInventory and PlayerInventory ==== Sometimes, it is necessary to convert some ''Inventory'' to a ''Storage''. For that purpose, [[https://github.com/FabricMC/fabric/blob/f66f2be7b6a29a764f78cf9762171bf0c0ef097e/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/item/InventoryStorage.java#L63|InventoryStorage.of(inventory, side)]] can be used. ''InventoryStorage'' also allows recovering each slot separately through the ''getSlot'' and ''getSlots'' functions. The side ''@Nullable Direction'' parameter exists so that if a non-null side is specified and the inventory is a ''SidedInventory'', ''canInsert'' and ''canExtract'' are checked before insertion and extraction respectively. [[https://github.com/FabricMC/fabric/blob/1.18/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/item/PlayerInventoryStorage.java|PlayerInventoryStorage]] serves a similar purpose, but for player inventories. Make sure to read its documentation, it contains very useful functions such as ''offerOrDrop''. **A word of caution**: To be able to support transactions, ''ItemStorage.of'' assumes that each slot in the inventory is independent of other slots, and of other inventories. Do NOT use it if that is not the case for your inventory. ===== How to implement Storage ===== This section describes various ways to create and customize ''Storage'' instances. ==== SingleVariantStorage, FilteringStorage, CombinedStorage ==== The helpers described on the [[transfer-api/fluid_implementation|fluid implementation page]] can be used just fine for item transfer. However, screen handler interactions make heavy use of mutable ''ItemStack''s. So **if you are implementing an item-containing block with GUI slots, you should avoid ''SingleVariantStorage''** and look at the other solutions below instead. ''FilteringStorage'' and ''CombinedStorage'' might still be useful to you. ==== Use SimpleInventory ==== A nice way to obtain a ''Storage'' instance is to create a ''SimpleInventory'', and use ''InventoryStorage.of'' to create a wrapper around it. Example: public class MyBlockEntity extends BlockEntity { // ... [other code] // This is the internal inventory, it can be used directly, or to create Slots, etc... private final SimpleInventory inventory = new SimpleInventory(3) { // 3 here is the slot count @Override public int getMaxCountPerStack() { return 4; // Optional: customize the maximum count in each slot. } @Override public boolean isValid(int slot, ItemStack stack) { // Optional: restrict which stacks are allowed in which slot. if (slot == 1) { return stack.isOf(Items.COAL); } return true; } @Override public void markDirty() { // Don't forget to schedule the block entity for saving after a modification! MyBlockEntity.this.markDirty(); } }; // It's better for performance to store the result of InventoryStorage.of(...), since it can be a bit expensive. // It may also be nicer to use than the SimpleInventory in some cases. public final InventoryStorage inventoryWrapper = InventoryStorage.of(inventory, null); // Don't forget to implement fromNbt/toNbt. // Don't forget to expose inventoryWrapper through ItemStorage.SIDED. // [...] Other code omitted } This is a powerful implementation technique due to the low amount of code that is required, and it can be taken even further: * Multiple ''SimpleInventory'''s can be created, for example if the block has a "main inventory" with a stack limit of 64, and an "upgrade inventory" with a stack limit of 1. They can be combined into one ''Storage'' with ''CombinedStorage''. * (Advanced) You can have your ''SimpleInventory'' subclass implement ''SidedInventory'', and wrappers queried with a side via ''InventoryStorage.of(sidedSimpleInv, side)'' will check ''canInsert'' and ''canExtract''. ==== Use SingleStackStorage ==== Another (more complicated) option is to store a stack or a list of stacks somewhere, and also a [[https://github.com/FabricMC/fabric/blob/1.18/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/item/base/SingleStackStorage.java|SingleStackStorage]]. Please have a look at the javadoc if you wish to go down this path. ==== (Discouraged) Directly implement Inventory or SidedInventory ==== For compatibility with existing vanilla inventories, the Transfer API will automatically wrap any block entity implementing ''Inventory'' or its subinterface ''SidedInventory'' as a ''Storage''. It will also wrap ''SidedInventory'''s returned by blocks implementing ''InventoryProvider'' if they return the same inventory multiple times. This means that implementing ''Inventory''/''SidedInventory'' on your block entity class will correctly allow other mods to use it. However, this approach is **discouraged** for the following reasons: * Performance: ''InventoryStorage.of'' is called every time, and it can be an expensive function to call. * Code size: this will require more code than using ''SimpleInventory'' as described above. * Flexibility: this is less flexible than ''SimpleInventory''. * Correctness: correctly implementing ''Inventory'' is harder than reusing the implementation that already exists in Minecraft itself.