Table of Contents

Fabric Transfer API: Fluid-containing items

This article is part of a series on the Fabric Transfer API. Link to the home page of the series.

This tutorial focuses on “fluid-containing items”, i.e. items such as buckets or tanks. This is a complex topic, so make sure you read the previous fluid and item tutorials first.

The problem

When we are dealing with fluid-containing items, we are interacting with fluid containers stored inside of an inventory. For example, this is the sequence of operations that must be executed to empty a water bucket:

You do not need to understand this in detail, but this should give an idea of where we are headed. In code, this is what this looks like: (taken from FullItemFluidStorage with comments adjusted)

// This is the important field: the "context" represents the underlying inventory - more on that in a bit.
private final ContainerItemContext context;
// A few constants, ignore these for now
private final Item fullItem = Items.WATER_BUCKET;
private final Function<ItemVariant, ItemVariant> fullToEmptyMapping = fullBucket -> ItemVariant.of(Items.BUCKET, fullBucket.getNbt()); // This preserves NBT, such as the custom name of a bucket.
private final FluidVariant containedFluid = FluidVariant.of(Fluids.WATER);
private final long containedAmount = FluidConstants.BUCKET;
 
@Override
public long extract(FluidVariant resource, long maxAmount, TransactionContext transaction) {
	StoragePreconditions.notBlankNotNegative(resource, maxAmount); // Defensive check, this is good practice.
 
	// If the context's item is not a bucket anymore, can't extract!
	if (!context.getItemVariant().isOf(fullItem)) return 0;
 
	// Make sure that the fluid and the amount match.
	if (resource.equals(containedFluid) && maxAmount >= containedAmount) {
		// If that's ok, just convert one of the full item into the empty item, copying the nbt.
		ItemVariant newVariant = fullToEmptyMapping.apply(context.getItemVariant());
 
		// Exchange removes 1 full bucket, and adds 1 empty bucket.
		if (context.exchange(newVariant, 1, transaction) == 1) {
			// Conversion ok!
			return containedAmount;
		}
	}
	return 0;
}

ContainerItemContext

ContainerItemContext represents the inventory containing the fluid container, and it is made of the following parts:

You usually don't interact with these methods directly, since ContainerItemContext has many useful default-implemented methods. Make sure to read the javadoc if you ever need to implement or use it.

Obtaining instances

Fabric provides various static methods to create a ContainerItemContext, depending on your use case. The most important ones are the following:

A word of caution: don't use ContainerItemContext.withInitial(stack) unless you know what you're doing. It does not mutate the stack.

The API in action

An example to understand what is going on: how to query a storage for the main hand of a player, and insert 1 bucket of water into it:

PlayerEntity player;
 
// Build the ContainerItemContext.
ContainerItemContext handContext = ContainerItemContext.ofPlayerHand(player, Hand.MAIN_HAND);
// Use it to query a fluid storage.
Storage<FluidVariant> handStorage = handContext.find(FluidStorage.ITEM);
if (handStorage != null) {
	// Use the storage: any usual Storage<FluidVariant> can be attempted.
	try (Transaction transaction = Transaction.openOuter()) {
		handStorage.insert(FluidVariant.of(Fluids.WATER), FluidConstants.BUCKET, transaction);
		transaction.commit();
	}
}

TODO:

  1. filling an item, example from TR
  2. using the existing base implementations for items