====== 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.world.level.material.FlowingFluid'', and so shall we. public abstract class TutorialFluid extends FlowingFluid { /** * @return whether the given fluid an instance of this fluid */ @Override public boolean isSame(Fluid fluid) { return fluid == getSource() || fluid == getFlowing(); } /** * @return whether the fluid is infinite (which means can be infinitely created like water). In vanilla, it depends on the game rule. */ @Override protected boolean canConvertToSource() { return false; } /** * Perform actions when the fluid flows into a replaceable block. Water drops * the block's loot table. Lava plays the "block.lava.extinguish" sound. */ @Override protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state) { final BlockEntity entity = state.hasBlockEntity() ? level.getBlockEntity(pos) : null; Block.dropResources(state, level, pos, entity); } /** * Lava returns true if it's FluidState is above a certain height and the * Fluid is Water. * * @return whether the given Fluid can flow into this FluidState */ @Override protected boolean canBeReplacedWith(FluidState state, BlockGetter level, BlockPos pos, Fluid fluid, Direction direction) { return false; } /** * Possibly related to the distance checks for flowing into nearby holes? * Water returns 4. Lava returns 2 in the Overworld and 4 in the Nether. */ @Override protected int getSlopeFindDistance(LevelReader reader) { return 4; } /** * Water returns 1. Lava returns 2 in the overworld and 1 in the nether. * @return How many levels a fluid loses per block. */ @Override protected int getDropOff(LevelReader reader) { return 1; } /** * Water returns 5. Lava returns 30 in the Overworld and 10 in the Nether. * Seems to return the delay before it updates its state. */ @Override public int getTickDelay(LevelReader reader) { return 5; } /** * Water and Lava both return 100.0F. */ @Override protected float getExplosionResistance() { return 100.0F; } } ===== Implementation ===== Now let's make an actual fluid which will have still and flowing variants. For this tutorial, we will call it Acid. The missing references will be filled in shortly. public abstract class AcidFluid extends TutorialFluid { @Override public Fluid getSource() { return YOUR_SOURCE_FLUID_HERE; } @Override public Fluid getFlowing() { return YOUR_FLOWING_FLUID_HERE; } @Override public Item getBucket() { return YOUR_BUCKET_ITEM_HERE; } @Override protected BlockState createLegacyBlock(FluidState state) { return YOUR_FLUID_BLOCK_HERE.defaultBlockState().setValue(BlockStateProperties.LEVEL, getLegacyLevel(state)); } public static class Flowing extends AcidFluid { @Override protected void createFluidStateDefinition(StateDefinition.Builder builder) { super.createFluidStateDefinition(builder); builder.add(LEVEL); } @Override public boolean isSource(FluidState state) { return false; } @Override public int getAmount(FluidState state) { return state.getValue(LEVEL); } } public static class Still extends AcidFluid { @Override public boolean isSource(FluidState state) { return true; } @Override public int getAmount(FluidState state) { return 8; } } } Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your ''ModInitializer'': public static FlowingFluid ACID_SOURCE; public static FlowingFluid FLOWING_ACID; public static Item ACID_BUCKET; @Override public void onInitialize() { ACID_SOURCE = Registry.register(BuiltInRegistries.FLUID, Identifier.fromNamespaceAndPath("tutorial", "acid"), new AcidFluid.Still()) FLOWING_ACID = Registry.register(BuiltInRegistries.FLUID, Identifier.fromNamespaceAndPath("tutorial", "flowing_acid"), new AcidFluid.Flowing()); ACID_BUCKET = Registry.register(BuiltInRegistries.ITEM, Identifier.fromNamespaceAndPath("tutorial", "acid_bucket"), new BucketItem(ACID_SOURCE, properties), new Item.Properties().craftRemainder(Items.BUCKET).stacksTo(1); // ... } // ... To make a custom fluid behave more like water or lava, you must add it to a corresponding fluid tag: For water, make a ''data/minecraft/tags/fluid/water.json'' file and write the identifiers of your fluids in there: { "replace": false, "values": [ "tutorial:acid", "tutorial:flowing_acid" ] } ===== Making a fluid block ===== Next we need to create a block which will represent acid in the world. ''net.minecraft.world.level.block.LiquidBlock'' is the class we need to use. In your ''ModInitializer'': public static Block ACID; @Override public void onInitialize() { ACID = Registry.register(BuiltInRegistries.BLOCK, Identifier.fromNamespaceAndPath("tutorial", "acid"), new LiquidBlock(ACID_SOURCE, BlockBehaviour.Properties.ofFullCopy(Blocks.WATER))); // ... } Now that we have these static objects, we can go back to ''AcidFluid'' and fill in the overridden methods: public abstract class AcidFluid extends TutorialFluid { @Override public Fluid getSource() { return TutorialMod.ACID_SOURCE; } @Override public Fluid getFlowing() { return TutorialMod.FLOWING_ACID; } @Override public Item getBucket() { return TutorialMod.ACID_BUCKET; } @Override protected BlockState createLegacyBlock(FluidState state) { return TutorialMod.ACID.defaultBlockState().setValue(BlockStateProperties.LEVEL, getLegacyLevel(state)); } public static class Flowing extends AcidFluid { @Override protected void createFluidStateDefinition(StateDefinition.Builder builder) { super.createFluidStateDefinition(builder); builder.add(LEVEL); } @Override public boolean isSource(FluidState state) { return false; } @Override public int getAmount(FluidState state) { return state.getValue(LEVEL); } } public static class Still extends AcidFluid { @Override public boolean isSource(FluidState state) { return true; } @Override public int getAmount(FluidState state) { return 8; } } } ===== Rendering setup ===== For your fluids to have textures or be tinted with a color, you will need to register a ''FluidRenderHandler'' for them. Here, we will reuse water's textures and just change the tint color applied to them. To make sure the textures are rendered as translucent, you can use Fabric's ''BlockRenderLayerMap'' (see [[blockappearance]]). @Environment(EnvType.CLIENT) public class TutorialModClient implements ClientModInitializer { @Override public void onInitializeClient() { FluidRenderHandlerRegistry.INSTANCE.register(TutorialMod.ACID_SOURCE, TutorialMod.FLOWING_ACID, SimpleFluidRenderHandler.coloredWater(0x4CC248)); BlockRenderLayerMap.putFluids(ChunkSectionLayer.TRANSLUCENT, TutorialMod.ACID_SOURCE, TutorialMod.FLOWING_ACID); } } ===== Adding custom textures to your fluid ===== To create a fluid with custom textures, you will need at least 2 new textures, with an optional 3rd, overlay texture that is shown when you look at the fluid behind glass or leaves. All textures go in the block textures folder. The first texture should be named YOUR_FLUID_NAME_still.png, and can optionally be animated. The second texture should be named YOUR_FLUID_NAME_flowing.png, and should have double the size in the dimensions. For example, if your still texture has frames of 16x16, the flowing should have frames of 32x32. It can also be animated. The third will be named YOUR_FLUID_NAME_overlay.png, and should not be animated. Using our acid example, the client initializer will look like this: @Environment(EnvType.CLIENT) public class TutorialModClient implements ClientModInitializer { @Override public void onInitializeClient() { FluidRenderHandlerRegistry.INSTANCE.register(TutorialMod.ACID_SOURCE, TutorialMod.FLOWING_ACID, new SimpleFluidRenderHandler( Identifier.fromNamespaceAndPath("tutorial", "block/acid_still"), Identifier.fromNamespaceAndPath("tutorial", "block/acid_flowing"), Identifier.fromNamespaceAndPath("tutorial", "block/acid_overlay") /*optional tint can go after the overlay*/); BlockRenderLayerMap.putFluids(ChunkSectionLayer.TRANSLUCENT, TutorialMod.ACID_SOURCE, TutorialMod.FLOWING_ACID); } } You can refer to vanilla's assets ((''assets/minecraft/textures/block/water_still.png''\\ ''assets/minecraft/textures/block/water_still.png.mcmeta''\\ ''assets/minecraft/textures/block/water_flow.png''\\ ''assets/minecraft/textures/block/water_flow.png.mcmeta'' \\ ''assets/minecraft/textures/block/water_overlay.png'')) as a template for your own textures.