Fabric Transfer API: Creating a simple tank

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

But wait, what is a FluidVariant ?

A FluidVariant is what the Transfer API uses to represent the “type” of a fluid. It contains the Fluid in the minecraft sense, and also an optional NBT compound tag:

// Creating a fluid variant from a fluid, without an NBT tag.
FluidVariant waterVariant = FluidVariant.of(Fluids.WATER);
waterVariant.getFluid() // returns Fluids.WATER
waterVariant.copyNbt() // returns a copy of the optional nbt tag, in this case null
// Creating a fluid variant from a fluid, with an NBT tag.
NbtCompound customTag = new NbtCompound();
customTag.putBoolean("magic", true);
FluidVariant magicWater = FluidVariant.of(Fluids.WATER, customTag);

Variants are always compared with .equals, NEVER WITH == !

waterVariant.equals(waterVariant); // returns true
waterVariant.equals(magicWater); // returns false
// You can easily test if a variant has some fluid:
waterVariant.isOf(Fluids.WATER); // returns true
magicWater.isOf(Fluids.WATER); // returns true

They can easily be serialized to and from NBT or network packets:

// NBT
NbtCompound compound = variant.toNbt();
FluidVariant variant = FluidVariant.fromNbt(compound);
// Network packets
variant.toPacket(buf);
FluidVariant variant = FluidVariant.fromPacket(buf);

CAUTION: make sure that you know the base understanding of making a block entity and creating block entity inventories before continuing to read.

Let's see how we can make a block entity contain some fluid:

// Make sure you implement ExtendedScreenHandlerFactory because we need to write the pos of this entity...
public class MyTankBlockEntity extends BlockEntity implements ExtendedScreenHandlerFactory, ImplementedInventory {
        private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(3, ItemStack.EMPTY);
	// This field is going to contain the amount, and the fluid variant (more on that in a bit).
	public final SingleVariantStorage<FluidVariant> fluidStorage = new SingleVariantStorage<>() {
		@Override
		protected FluidVariant getBlankVariant() {
			return FluidVariant.blank();
		}
 
		@Override
		protected long getCapacity(FluidVariant variant) {
			// Here, you can pick your capacity depending on the fluid variant.
			// For example, if we want to store 8 buckets of any fluid:
			return (8 * FluidConstants.BUCKET) / 81 // This will convert it to mB amount to be consistent;
		}
 
		@Override
		protected void onFinalCommit() {
			// Called after a successful insertion or extraction, markDirty to ensure the new amount and variant will be saved properly.
			markDirty();
                        if (!world.isClient) {
                           var buf = PacketByteBufs.create();
                           // Write your data here.
                           PlayerLookup.tracking(MyTankBlockEntity.this).forEach(player -> {
                               ServerPlayNetworking.send(player, YOUR_IDENTIFIER, buf);
                           });
                        }
		}
	};
 
	@Override
	public NbtCompound writeNbt(NbtCompound tag) {
		tag.put("fluidVariant", fluidStorage.variant.toNbt());
		tag.putLong("amount", fluidStorage.amount);
		return super.writeNbt(tag);
	}
	@Override
	public void readNbt(NbtCompound tag) {
		super.readNbt(tag);
		fluidStorage.variant = FluidVariant.fromNbt(tag.getCompound("fluidVariant"));
		fluidStorage.amount = tag.getLong("amount");
	}
}

Alright, now we can contain some fluid, and we are properly saving it if it changes. Now, we must register the block entity in order to ensure that other mods can properly interact with our tank:

BlockEntityType<MyTankBlockEntity> MY_TANK = // see block entity tutorial
 
// Put this in your mod initializer, after you have created the block entity type:
FluidStorage.SIDED.registerForBlockEntity((myTank, direction) -> myTank.fluidStorage, MY_TANK);

More on Fabric API Lookup

To understand what the call to FluidStorage.SIDED does, please have a look at the Usage Example in the documentation of ''BlockApiLookup''. The Fabric API Lookup system allows blocks to expose interfaces such as Storage<FluidVariant>, so that pipes and other devices can use them.