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.FlowableFluid
, and so shall we.
public abstract class TutorialFluid extends FlowableFluid {
/**
* @return whether the given fluid an instance of this fluid
*/
@Override
public boolean matchesType(Fluid fluid) {
return fluid == getStill() || 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 isInfinite() {
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 beforeBreakingBlock(WorldAccess world, BlockPos pos, BlockState state) {
final BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
Block.dropStacks(state, world, pos, blockEntity);
}
/**
* 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 fluidState,
BlockView blockView, BlockPos blockPos, 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 getFlowSpeed(WorldView worldView) {
return 4;
}
/**
* Water returns 1. Lava returns 2 in the Overworld and 1 in the Nether.
*/
@Override
protected int getLevelDecreasePerBlock(WorldView worldView) {
return 1;
}
/**
* Water returns 5. Lava returns 30 in the Overworld and 10 in the Nether.
*/
@Override
public int getTickRate(WorldView worldView) {
return 5;
}
/**
* Water and Lava both return 100.0F.
*/
@Override
protected float getBlastResistance() {
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 getStill() {
return YOUR_STILL_FLUID_HERE;
}
@Override
public Fluid getFlowing() {
return YOUR_FLOWING_FLUID_HERE;
}
@Override
public Item getBucketItem() {
return YOUR_BUCKET_ITEM_HERE;
}
@Override
protected BlockState toBlockState(FluidState fluidState) {
return YOUR_FLUID_BLOCK_HERE.
getDefaultState().
with(Properties.
LEVEL_15, getBlockStateLevel
(fluidState
)); }
public static class Flowing extends AcidFluid {
@Override
protected void appendProperties(StateManager.Builder<Fluid, FluidState> builder) {
super.appendProperties(builder);
builder.add(LEVEL);
}
@Override
public int getLevel(FluidState fluidState) {
return fluidState.get(LEVEL);
}
@Override
public boolean isStill(FluidState fluidState) {
return false;
}
}
public static class Still extends AcidFluid {
@Override
public int getLevel(FluidState fluidState) {
return 8;
}
@Override
public boolean isStill(FluidState fluidState) {
return true;
}
}
}
Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your ModInitializer
:
public static FlowableFluid STILL_ACID;
public static FlowableFluid FLOWING_ACID;
public static Item ACID_BUCKET;
@Override
public void onInitialize() {
STILL_ACID
= Registry.
register(Registries.
FLUID,
new Identifier
("tutorial",
"acid"),
new AcidFluid.
Still()); FLOWING_ACID
= Registry.
register(Registries.
FLUID,
new Identifier
("tutorial",
"flowing_acid"),
new AcidFluid.
Flowing()); ACID_BUCKET
= Registry.
register(Registries.
ITEM,
new Identifier
("tutorial",
"acid_bucket"),
new BucketItem(STILL_ACID, new Item.Settings().recipeRemainder(Items.BUCKET).maxCount(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/fluids/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.block.FluidBlock
is the class we need to use, but since its constructor is protected, we can't construct it directly. Some ways to use it are to make a subclass or an anonymous subclass. Here we will be showing the latter. In your ModInitializer
:
public static Block ACID;
@Override
public void onInitialize() {
ACID
= Registry.
register(Registries.
BLOCK,
new Identifier
(MOD_ID,
"acid"),
new FluidBlock
(STILL_ACID, FabricBlockSettings.
copy(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 getStill() {
return TutorialMod.STILL_ACID;
}
@Override
public Fluid getFlowing() {
return TutorialMod.FLOWING_ACID;
}
@Override
public Item getBucketItem() {
return TutorialMod.ACID_BUCKET;
}
@Override
protected BlockState toBlockState(FluidState fluidState) {
// getBlockStateLevel converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses
return TutorialMod.
ACID.
getDefaultState().
with(Properties.
LEVEL_15, getBlockStateLevel
(fluidState
)); }
public static class Flowing extends AcidFluid {
@Override
protected void appendProperties(StateManager.Builder<Fluid, FluidState> builder) {
super.appendProperties(builder);
builder.add(LEVEL);
}
@Override
public int getLevel(FluidState fluidState) {
return fluidState.get(LEVEL);
}
@Override
public boolean isStill(FluidState fluidState) {
return false;
}
}
public static class Still extends AcidFluid {
@Override
public int getLevel(FluidState fluidState) {
return 8;
}
@Override
public boolean isStill(FluidState fluidState) {
return true;
}
}
}
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).
public class TutorialModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
FluidRenderHandlerRegistry.INSTANCE.register(TutorialMod.STILL_ACID, TutorialMod.FLOWING_ACID, new SimpleFluidRenderHandler(
new Identifier("minecraft:block/water_still"),
new Identifier("minecraft:block/water_flow"),
0x4CC248
));
BlockRenderLayerMap.INSTANCE.putFluids(RenderLayer.getTranslucent(), TutorialMod.STILL_ACID, TutorialMod.FLOWING_ACID);
//if you want to use custom textures they needs to be registered.
//In this example this is unnecessary because the vanilla water textures are already registered.
//To register your custom textures use this method.
//ClientSpriteRegistryCallback.event(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE).register((atlasTexture, registry) -> {
// registry.register(new Identifier("tutorial:block/custom_fluid_still"));
// registry.register(new Identifier("tutorial:block/custom_fluid_flowing"));
//});
// ...
}
}
If you want to use your own fluid textures, you can refer to vanilla's assets 1) as a template.
Generation in the world
TODO Update to 1.19.4
</code>