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.