User Tools

Site Tools


tutorial:fluids

This is an old revision of the document!


Creating a fluid

Overview

Here we'll cover creation of a custom fluid. If you plan to create several fluids, it is recommended to make an abstract basic fluid class where you'll set necessary defaults that will be shared in its subclasses. We'll also make it generate in the world like lakes.

Making an abstract fluid

Vanilla fluids extend net.minecraft.fluid.BaseFluid class, and so will our abstract fluid. It can be like this:

public abstract class BasicFluid extends BaseFluid
{
    /**
     * @return does it produce infinite fluid (like water)?
     */
    @Override
    protected boolean isInfinite()
    {
        return false;
    }
 
    // make it transparent
    @Override
    protected BlockRenderLayer getRenderLayer()
    {
        return BlockRenderLayer.TRANSLUCENT;
    }
 
    /**
     *
     * @return an associated item that "holds" this fluid
     */
    @Override
    public abstract Item getBucketItem();
 
    /**
     *
     * @return a blockstate of the associated {@linkplain net.minecraft.block.FluidBlock} with {@linkplain net.minecraft.block.FluidBlock#LEVEL}
     */
    @Override
    protected abstract BlockState toBlockState(FluidState var1);
 
    /**
     *
     * @return flowing static instance of this fluid
     */
    @Override
    public abstract Fluid getFlowing();
 
    /**
     *
     * @return still static instance of this fluid
     */
    @Override
    public abstract Fluid getStill();
 
    // how much does the height of the fluid block decreases
    @Override
    protected int getLevelDecreasePerBlock(ViewableWorld world)
    {
        return 1;
    }
 
    /**
     * 
     * @return update rate
     */
    @Override
    public int getTickRate(ViewableWorld world)
    {
        return 5;
    }
 
    @Override
    protected float getBlastResistance()
    {
        return 100;
    }
 
    // this seems to determine fluid's spread speed (higher value means faster)
    @Override
    protected int method_15733(ViewableWorld world)
    {
        return 4;
    }
 
    // I don't know what this does, but it's present in the water fluid
    @Override
    protected void beforeBreakingBlock(IWorld world, BlockPos blockPos, BlockState blockState) {
        BlockEntity blockEntity = blockState.getBlock().hasBlockEntity() ? world.getBlockEntity(blockPos) : null;
        Block.dropStacks(blockState, world.getWorld(), blockPos, blockEntity);
    }
 
    // also don't know what it does
    public boolean method_15777(FluidState fluidState, BlockView blockView, BlockPos blockPos, Fluid fluid, Direction direction) {
        return direction == Direction.DOWN;
    }
 
    /**
     *
     * @return is given fluid instance of this fluid?
     */
    @Override
    public abstract boolean matchesType(Fluid fluid);
 
}

Implementation

Now let's make an actual fluid; it will have a still and flowing variants; will name it “Acid”:

public abstract class Acid extends BasicFluid
{
    @Override
    public Item getBucketItem()
    {
        return null;
    }
    @Override
    protected BlockState toBlockState(FluidState var1)
    {
        return null;
    }
 
    @Override
    public Fluid getFlowing()
    {
        return null;
    }
 
    @Override
    public Fluid getStill()
    {
        return null;
    }
 
    @Override
    public boolean matchesType(Fluid fluid)
    {
        return false;
    }
 
    // still acid
    public static class Still extends Acid
    {
 
        @Override
        public boolean isStill(FluidState fluidState)
        {
            return true;
        }
 
        /**
         * @return height of the fluid block
         */
        @Override
        public int getLevel(FluidState fluidState)
        {
            return 8;
        }
    }
 
    // flowing acid
    public static class Flowing extends  Acid
    {
 
        @Override
        public boolean isStill(FluidState fluidState)
        {
            return false;
        }
 
        /**
         * @return height of the fluid block
         */
        @Override
        public int getLevel(FluidState fluidState)
        {
            return fluidState.get(LEVEL);
        }
 
        @Override
        protected void appendProperties(StateFactory.Builder<Fluid, FluidState> stateFactoryBuilder)
        {
            super.appendProperties(stateFactoryBuilder);
            stateFactoryBuilder.add(LEVEL);
        }
    }
}

Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your ModInitializer:

 
    public static Acid stillAcid;
    public static Acid flowingAcid;
 
    public static BucketItem acidBucket;
 
    @Override
    public void onInitialize()
    {
 
        stillAcid = Registry.register(Registry.FLUID, new Identifier(MODID,"acid_still"), new Acid.Still());
        flowingAcid = Registry.register(Registry.FLUID, new Identifier(MODID,"acid_flowing"), new Acid.Flowing());
 
        acidBucket = new BucketItem(stillAcid, new Item.Settings().maxCount(1));
        Registry.register(Registry.ITEM, new Identifier(MODID,"acid_bucket"), acidBucket);
    }    

To make the custom fluid behave like water or lava, you must add it to a corresponding fluid tag: make a file “data/minecraft/tags/fluids/water.json” and write identifiers of your fluids in there:

{
  "replace": false,
  "values": [
    "modid:acid_still",
    "modid:acid_flowing"
  ]
}

Making a fluid block

Next we need to create a block which will represent acid in the world. net.minecraft.block.FluidBlock is the class we need to use, but for “mojang” reasons its constructor is protected. The solution is well-known - make a subclass of it and change the visibility of the constructor:

public class BaseFluidBlock extends FluidBlock
{
    public BaseFluidBlock(BaseFluid fluid, Settings settings)
    {
        super(fluid, settings);
    }
}

Now make a static block instance:

    ...
 
    public static FluidBlock acid;
 
    @Override
    public void onInitialize()
    {
 
        ...
 
        acid = new BaseFluidBlock(stillAcid, FabricBlockSettings.of(Material.WATER).dropsNothing().build());
        Registry.register(Registry.BLOCK, new Identifier(MODID, "acid_block"), acid);
    }    

Now when we have these static objects, we go back to Acid class and complete the overridden methods:

public abstract class Acid extends BasicFluid
{
    @Override
    public Item getBucketItem()
    {
        return Mod.acidBucket;
    }
 
    @Override
    protected BlockState toBlockState(FluidState fluidState)
    {
        //don't ask me what **method_15741** does...
        return Mod.acid.getDefaultState().with(FluidBlock.LEVEL, method_15741(fluidState));
    }
 
    @Override
    public Fluid getFlowing()
    {
        return Mod.flowingAcid;
    }
 
    @Override
    public Fluid getStill()
    {
        return Mod.stillAcid;
    }
 
    @Override
    public boolean matchesType(Fluid fluid_1)
    {
        return fluid_1==Mod.flowingAcid || fluid_1==Mod.stillAcid;
    }
 
    ...
 
}    

Now we can assert that the Acid class is complete.

Rendering setup

Time to do client-side things. In your ClientModInitializer you need to specify locations of sprites for your fluids and define their rendering. I will reuse water textures and just change the color applied to them.

    @Override
    public void onInitializeClient()
    {
 
        // adding the sprites to the block texture atlas
        ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register((spriteAtlasTexture, registry) -> {
 
            Identifier stillSpriteLocation = new Identifier("block/water_still");
            Identifier dynamicSpriteLocation = new Identifier("block/water_flow");
            // here I tell to use only 16x16 area of the water texture
            FabricSprite stillAcidSprite = new FabricSprite(stillSpriteLocation, 16, 16);
            // same, but 32
            FabricSprite dynamicAcidSprite = new FabricSprite(dynamicSpriteLocation, 32, 32);
 
            registry.register(stillAcidSprite);
            registry.register(dynamicAcidSprite);
 
 
            // this renderer is responsible for drawing fluids in a world
            FluidRenderHandler acidRenderHandler = new FluidRenderHandler()
            {
                // return the sprites: still sprite goes first into the array, flowing/dynamic goes last
                @Override
                public Sprite[] getFluidSprites(ExtendedBlockView extendedBlockView, BlockPos blockPos, FluidState fluidState)
                {
                    return new Sprite[] {stillAcidSprite, dynamicAcidSprite};
                }
 
                // apply light green color
                @Override
                public int getFluidColor(ExtendedBlockView view, BlockPos pos, FluidState state)
                {
                    return 0x4cc248;
                }
            };
 
            // registering the same renderer for both fluid variants is intentional
 
            FluidRenderHandlerRegistry.INSTANCE.register(Mod.stillAcid, acidRenderHandler);
            FluidRenderHandlerRegistry.INSTANCE.register(Mod.flowingAcid, acidRenderHandler);
        });

Then what's left to do is to create necessary Json files and textures, but you should know how to do that at this point.

Generation in a world

To make acid lakes generate in the world, you can use net.minecraft.world.gen.feature.LakeFeature, which you create in the ModInitializer:

        LakeFeature acidFeature = Registry.register(Registry.FEATURE, new Identifier(MODID,"acid_lake"), new LakeFeature(dynamic -> new LakeFeatureConfig(acid.getDefaultState())));

Then put it into desired biomes to generate:

        // I tell it to generate like water lakes, with a rarity of 40 (the higher is the number, the lesser is the generation chance):
        Biomes.FOREST.addFeature(GenerationStep.Feature.LOCAL_MODIFICATIONS, Biome.configureFeature(acidFeature, new LakeFeatureConfig(acid.getDefaultState()), Decorator.WATER_LAKE, new LakeDecoratorConfig(40)));

This is the end of the tutorial.

tutorial/fluids.1572887043.txt.gz · Last modified: 2019/11/04 17:04 by alexiy