Minecraft Modding: Custom Fluid Blocks

Background


This tutorial is about creating a custom fluid block. If you're interested in a fluid handling items or tile entities, please check out Jabelar's Custom Fluids, Tanks and Buckets Overview.

Fluids can be contained in "tanks" (a.k.a. fluid handlers) which can be either an Item or a TileEntity and can also be placed in the world as a fluid block. A fluid block is placed as a "source" block and then over time it flows outwards depending on the space around it.

The Forge fluid system is nice, but it doesn't fully re-create the effects of the built-in liquids: water and lava. In particular, the Forge system will not:
  • Push entities or players that are in the flowing fluid
  • Create the colored "fog" effect when the player is submerged in the fluid
  • Affect air or drown entities or players that are submerged in the fluid.

Bonus: I have figured out ways to make custom fluids act like water to push, color the view and potentially drown entities and players. The techniques are linked at the bottom of this tutorial.

This tutorial assumes you've already made a custom Fluid. See Jabelar's Custom Fluid Tutorial.

The Custom Fluid Block Approach


Generally, the steps are:
  1. Create a custom Fluid class (with instance, registration and assets) as described in Jabelar's Custom Fluid Tutorial.
  2. Creating an instance of  BlockFluidClassic or BlockFluidFinite to reference. In the constructor you pass the instance of your fluid from the previous step.
  3. Registering the instance of the fluid block from Step 2.
  4. Put your blockstate files in proper asset location.
  5. If you want your fluid block to push entities like water and lava, handle the WorldTickEvent and execute code similar to the modifyAcceration() method if any entity is in your fluid block.
  6. If you want a colored fog to happen when the player is within the fluid block (like how going under water turns the screen a bit blue) handle the FogDensity and FogColor events.
  7. If you want your fluid block to be able to drown entities like water along with the air supply HUD, you need to handle the LivingUpdateEvent and replace the vanilla code to check for your fluid.
Step #1. Creating A Custom Class That Extends Fluid

Follow the steps for creating the custom Fluid class, along with the registered instance and texture and sound assets as described in Jabelar's Custom Fluid Tutorial.

Step #2. Create An Instance of BlockFluidClassic or BlockFluidFinite

Tip: Note it possible to create custom fluids that don't have an associated block in which case you would set the block to null by calling the setBlock() method for the fluid instance you created in the previous step. 

As mentioned above, the classic block flows similar to vanilla water or lava with a source block that keeps flowing forever, whereas a finite block flows a certain amount then stops. So you can choose to instantiate either BlockFluidClassic or BlockFluidFinite as suits your needs.

Key Point: You do not need to implement IFluidBlock or extend FluidBlockBase yourself, rather extend the classic or finite class.

Key Point: You may extend  BlockFluidClassic or BlockFluidFinite if you feel the need to @Override their behavior, but for most cases you can simply instantiate them as is.

Warning: If you want your fluid to push the player around like water does, then you could give your block the Material.WATER. This is because the check for Material.WATER is hard-coded into the vanilla entity code. However, the problem with Material.WATER is that it also shows up on maps with the water color (MapColor.WATER). So if you want your fluid to push the player around like water but also have a different map color then you have to make a custom material instance of MaterialLiquid with your desired color and create the pushing effect by copying the code from the entity to the world tick handling event where you would check for entities in your fluid. Since player motion is handled on client side you'll have to do a similar technique for the player in the player tick event. See Step #5 below for more information on how to do this.

So with all the above in mind you should somewhere (I suggest either in your mod's main class or in a ModBlocks class to collect the instances if you have multiple) create an instance of the desired fluid block. For example:

public static final MY_FLUID_BLOCK = new BlockFluidClassic(ModFluids.MY_FLUID, ModMatierals.MY_MATERIAL);

Key Point: You should pass your fluid instance from Step #1 into the constructor of the fluid block along with an appropriate material (possibly custom).

Tip: You may want to call setCreativeTab() method on your fluid block instance if you want it to appear in a creative tab.

Warning: Do not put the instance of the custom fluid block in the same class as the instance of your custom fluid because the constructor of BlockFluidClassic and BlockFluidFinite require that the fluid already be registered. Since the instance is created statically, fluid registration will not yet have happened. But if you put the instance in your Blocks class and call it during block registry event then fluid registration will already have happened (in the pre-init handling method).

Step #3. Register The Instance Of Your Fluid Block Class

In the regular block registration (which in 1.12+ should be done from the block registry event) you should register your block normally using the event register() method.

Also, register a new ItemBlock for your custom block using the item registry event register() method.

Lastly, register the model for your custom block using the model registry event and the ModelLoader.setCustomModelResourceLocation() method.

Step #4. Place Blockstate Files in Proper Asset Location

Your fluid block is much like other blocks and therefore needs a blockstate JSON file in the assets to help map the model and textures.

Warning: the file name must exactly match the registry name of the custom block, as entered in the block registry method.

In the blockstate JSON you need to use the forge:fluid model. Use Forge's blockstates format to set the "fluid" custom property to the name of your custom fluid.

Warning: The fluid property must exactly match the registry name of your fluid (i.e. whatever you used as the first parameter in the Fluid() constructor and what is returned by the getName() method).

Tip: Some modders make all their fluids variants of a single custom fluid block class, but I personally think there is no harm (and probably easier) to implement each distinctly.
For example, you can use a blockstate file such as the following:
   {
"forge_marker": 1,
"defaults": {
"model": "forge:fluid"
},
"variants": {
"normal": [
{
"custom": {
"fluid": "my_fluid"
}
}
]
        }
   }

Step #5. Make Fluid Push Entities Like Water, If Desired

This is fairly tricky because the code for this is in the Entity class, not in your block class, and much of it is private or protected scope so you can't easily copy it. However, by using copying and Java reflection you can recreate the water effect for your fluids.

Because the explanation and code samples are pretty long, I've created a separate page to discuss this. Check out Jabelar's How To Enable Custom Fluids To Push Entities Tutorial.

Step #6. Render Colored "Fog" When Player Is Inside Your Fluid, If Desired

This is an optional step, but I really like to do this. Basically when the player is submersed you probably want the screen to get colored a bit, like how it goes blue when you're under water.  Check out Jabelar's Rendering Colored Fog When Inside Fluid Tutorial.

Step #7. Enable Your Custom Fluid To Drown Entities

This is an optional step, but for some fluids may be an important part of your mod. Basically when an entity is submerged in your fluid you probably want it to run out of air and "drown" just like in water. Unfortunately the drowning in vanilla Minecraft is hard-coded to look for Material.WATER and worse it is deep inside the EntityLivingBase onUpdate() logic so there is no simple event to fix it. Instead you need to cancel the vanilla living update code and replace it with copied code that changes the part where it checks for Material.WATER.

If you're interested in doing this for your mod, check out Jabelar's Enabling Custom Fluids To Cause Drowning Tutorial.

Conclusion

You should be able to test your fluid by ensuring that it is on the creative tab using the setCreativeTab() method in the block constructor, and then playing the game in creative mod and placing the fluid block. It should look good in terms of the textures, it should flow out over time, it should be able to push you, drown you, etc. according to what you've implemented.

Hope you liked this tutorial. Happy modding!

2 comments:

  1. Do you not need a custom state mapper? I started off following this tutorial but was unable to get the fluid to render in the world. The difference between this and another tutorial I followed was registering a custom state mapper.

    ReplyDelete
  2. In 1.18 it seems you can add your fluid to the tags datafile in resource/data/minecraft/tags/fluids/water.json . That allows pushing of items and entities.

    ReplyDelete