1. 方块形式的流体容器

很明显,普通的方块不可能有能力持有任意流体数据——所以你需要一个 TileEntity。关于 TileEntity 的内容可参考第九章的相关内容

// WIP
import net.minecraftforge.fluids.FluidTank;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;

public class MyFluidTank extends TileEntity {

    // 我们直接复用 Forge 自带的 FluidTank 实现,它可以满足 90% 的需求。
    private IFluidHandler tank = new FluidTank(8000);

    public boolean hasCapability(Capability<?> capability, EnumFacing direction) {
        return capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY
            || super.hasCapability(capability, direction);
    }

    public <T> T getCapability(Capability<T> capability, EnumFacing direction) {
        if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
            return CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY.cast(this.tank);
        } else {
            return super.getCapability(capability, direction);
        }
    }
}

1.1. 重新实现 IFluidHandler

如上文所述,FluidTank 已经正确实现这个接口了,还附赠读写 NBT 的方法,拿来直接用就好。大多数时候我们不需要手动实现 IFluidHandler
但实际上我们难免会遇到“需要有特殊行为的 IFluidHandler”的情况,比方说只能输入水的 IFluidHandler。遇到这样的情况时我们也只能选择重新实现 IFluidHandler。 这里只对实现 IFluidHandler 的要求做简要说明。

public class MyFluidHandler implements IFluidHandler {
    /*
     * 返回这个 IFluidHandler 内部各个“储罐”(Tank)的信息。
     * 是的,对于一个 IFluidHandler 来说,它的内部可能有不止一个储罐,
     * 可以是两个、三个甚至十几个。这些储罐也可能只是模拟的,实际上根本
     * 没有对应的对象,或者它自己不持有对应的对象。但无论如何,这个方法
     * 可以让外界对这个 IFluidHandler 的内部信息有一个认识。
     * 这些信息是只读的。
     * 注意:千万不要直接往返回的数组里赋值!这个方法返回的值可以是缓存
     * 的结果,直接赋值会破坏缓存!
     */
    @Override
    public IFluidTankProperties[] getTankProperties() {
        return new IFluidTankProperties[0];
    }

    /*
     * 向该 IFluidHandler 填充流体。
     * 填充的具体逻辑全部在这个方法中发生,对外界完全不公开。
     * 返回的 int 代表“这个 IFluidHandler 最终接受的流体的数量”,
     * 也就是说调用这个方法的用户理当从 resource.amount 中减去
     * 你返回的值。你不应直接操作 resource。
     * 当 doFill 为 false 时,整个过程为“模拟”的结果,也就是说你
     * 不应对你的 IFluidHandler 信息做出任何修改。参考很多程序都
     * 提供的“dry-run”的选项。
     */
    @Override
    public int fill(FluidStack resource, boolean doFill) {
        return resource.amount;
    }

    /*
     * 从该 IFluidHandler 中提取指定类型的指定数量的流体。
     * FluidStack 的 getFluid 决定了要提取的流体的类型,它的 amount
     * 则决定了最大允许的提取数量。
     * 返回值代表实际提取出的 FluidStack 对象,若为 null 表示“因为某种
     * 原因,这个 IFluidHandler 拿不出要求的 FluidStack”。反之,则
     * 返回的 FluidStack 代表的流体类型和请求的流体类型一致,但 amount
     * 只保证小于等于请求的量。
     * 当 doDrain 为 false 时,整个过程为“模拟”的结果,也就是说你
     * 不应对你的 IFluidHandler 信息做出任何修改。参考很多程序都
     * 提供的“dry-run”的选项。
     * TODO:查证一下是否允许返回比请求的量更多的 FluidStack?
     */
    @Nullable
    @Override
    public FluidStack drain(FluidStack toDrain, boolean doDrain) {
        return null;
    }

    /*
     * 从该 IFluidHandler 中提取指定数量的流体。
     * maxDrainAmount 决定了最大允许的提取数量。
     * 返回值代表实际提取出的 FluidStack 对象,若为 null 表示“因为某种
     * 原因,这个 IFluidHandler 拿不出要求的 FluidStack”。反之,则
     * 返回的 FluidStack 的 amount 只保证小于等于请求的量,对于返回的
     * 流体类型则没有任何保证。
     * 当 doDrain 为 false 时,整个过程为“模拟”的结果,也就是说你
     * 不应对你的 IFluidHandler 信息做出任何修改。参考很多程序都
     * 提供的“dry-run”的选项。
     */
    @Nullable
    @Override
    public FluidStack drain(int maxDrainAmount, boolean doDrain) {
        return null;
    }
}