Minecraft Modding: Custom Fluids

Background


Forge has provided a custom fluid API which can be used to make blocks that behave like water and lava from vanilla Minecraft.

As a fundamental part of this approach, you need to create the custom fluid instance and register it.

While you could just create an instance of the Fluid class, unfortunately for some reason Forge hasn't properly provided all the setter and getter functions you might want to fully customize your fluid. For example, if you want to change the color you will need to @Override the method in an extended class, and similarly if you want to change the sounds, control what happens when it vaporizes, etc. So I recommend that you extend Fluid as explained in this tutorial.

Once you've finished creating the fluid, you should go back to Jabelar's Custom Fluids, Tanks, and Buckets Tutorial to do further things with the fluid, such as having an item that dispenses it, having it work in a UniversalBucket and so forth.

Creating A Custom Fluid


Generally, the steps are:
  1. Create a custom class that extends Fluid and @Overrides the getColor() method and optionally provides a setColor() method.
  2. Creating instance of Fluid and set all the properties according to your need such as how much can fit in a bucket, resources for the textures and sounds, density, viscosity, luminosity, etc. 
  3. Create an instance of MaterialLiquid with the map color you want for your custom fluid.
  4. Registering your custom fluid instance from Step 2.
  5. Add entries to your .lang file for localization.
  6. If you have custom fill and empty sounds put sound files in proper asset location.
Step #1. Creating A Custom Class That Extends Fluid

For some reason the Fluid class provides some fields with setter and getter methods but not for other things like color (which is hard-coded to return white) or sounds. I feel most modders will want to change the color, as this affects how it is displayed in buckets. Note that the color of the block is not set by this method, but rather set by the texture asset. Also you may want to add sounds and maybe @Override the doesVaporize method which is hard-coded to look for Material.WATER.

So you should extend Fluid, make color a field and provide a setter and getter for it. For example:

public class ModFluid extends Fluid
{
    protected static int mapColor = 0xFFFFFFFF;
    protected static float overlayAlpha = 0.2F;
    protected static SoundEvent emptySound = SoundEvents.ITEM_BUCKET_EMPTY;
    protected static SoundEvent fillSound = SoundEvents.ITEM_BUCKET_FILL;
    protected static Material material = Material.WATER;
    public ModFluid(String fluidName, ResourceLocation still, ResourceLocation flowing) 
    {
        super(fluidName, still, flowing);
    }
    public ModFluid(String fluidName, ResourceLocation still, ResourceLocation flowing, int mapColor) 
    {
        this(fluidName, still, flowing);
        setColor(mapColor);
    }
    public ModFluid(String fluidName, ResourceLocation still, ResourceLocation flowing, int mapColor, float overlayAlpha) 
    {
        this(fluidName, still, flowing, mapColor);
        setAlpha(overlayAlpha);
    }
    @Override
    public int getColor()
    {
        return mapColor;
    }
    public ModFluid setColor(int parColor)
    {
        mapColor = parColor;
        return this;
    }
    public float getAlpha()
    {
        return overlayAlpha;
    }
    public ModFluid setAlpha(float parOverlayAlpha)
    {
        overlayAlpha = parOverlayAlpha;
        return this;
    }
    @Override
    public ModFluid setEmptySound(SoundEvent parSound)
    {
        emptySound = parSound;
        return this;
    }
    @Override
    public SoundEvent getEmptySound()
    {
        return emptySound;
    }
    @Override
    public ModFluid setFillSound(SoundEvent parSound)
    {
        fillSound = parSound;
        return this;
    }
    @Override
    public SoundEvent getFillSound()
    {
        return fillSound;
    }
    public ModFluid setMaterial(Material parMaterial)
    {
        material = parMaterial;
        return this;
    }
    public Material getMaterial()
    {
        return material;
    }
    @Override
    public boolean doesVaporize(FluidStack fluidStack)
    {
        if (block == null)
            return false;
        return block.getDefaultState().getMaterial() == getMaterial();
    }
}

Important: You can use this same custom fluid for all your custom fluids since you will now have available methods to set every aspect of it as needed when you instantiate it.

Step #2. Creating An Instance Of Your Fluid Class And Setting Various Properties

Somewhere (I suggest either in your mod's main class or in a ModFluids class to collect the instances if you have multiple) create an instance of the custom fluid you created in Step #1 where you pass a string with the fluid's registry name and resource locations for the textures for the still and flowing versions of the block.

Further you should call the various setter methods to set the viscosity, luminosity, density,color and such. Look through the Fluid class to see all the available parameters you should consider for your fluid.

Warning: You should not call the setUnlocalizedName() method because it is called automatically by constructor when you instantiate your fluid and sets it to match the registry name.

Warning: You should not call the setBlock() method because it is called automatically by the BlockFluidBase constructor when you instantiate your fluid block (see below).

For example:

public static final ModFluid SLIME = (ModFluid) new ModFluid(
"slime", 
new ResourceLocation(MainMod.MODID,"slime_still"), 
new ResourceLocation(MainMod.MODID, "slime_flow")
)
.setMaterial(ModMaterials.SLIME)
.setDensity(1100)
.setGaseous(false)
.setLuminosity(9)
.setViscosity(25000)
.setTemperature(300);

Warning: The strings and resource locations in the fluid constructor need to exactly match the asset file names and should be all lowercase.

Step #3. Create An Instance Of MaterialLiquid With Desired MapColor

Somewhere (I suggest either in your main mod file or in a ModMaterials class) you should create an instance of MaterialLiquid where you pass in the MapColor that you want for showing your fluid on maps. MapColor comes with a lot of built-in color constants and you can also create some if you know the RGB value of the color you want. For example:

public class ModMaterials 
{
  public static final Material SLIME = new MaterialLiquid(
        MapColor.GREEN_STAINED_HARDENED_CLAY);
}

Step #4. Registering The Instance Of Your Fluid In the FluidRegistry

Either in the pre-init handling method of your proxy system, or along with the block registration in the block registry event handler, you should register your fluid to the FluidRegistry using the registerFluid() method.

Step #5. Put Texture Assets With Proper Names In Proper Asset Location

You should make sure that there are appropriate texture files in the asset locations and with exactly matching names as you passed into your fluid constructor, for example my_fluid_still.png and my_fluid_flow.png.

Tip: It is suggested that you create your texture by taking the existing water or lava texture and modifying it in a graphics editing program (I recommend GIMP which is like an open-source Photoshop).

Key Point: The fluid texture itself is often partially transparent as for example is the vanilla water_still.png and water_flow.png textures. Transparency in your texture can be accomplished by setting the opacity of the layer or the brush in your graphics editing program.

Key Point: The fluid textures are usually animated. You must create the proper texture files as well as .mcmeta files to control the animation as described below.

To create an animated texture for a block (generally) you simply need to create a series of textures that are all connected vertically and further you must put an .mcmeta file along with the texture assets that has the same name (e.g. my_fluid_flow.png.mcmeta).

For the .mcmeta file you can either just let it animate in order by setting an animation speed like vanilla lava_flow does:

{
  "animation": {
    "frametime": 3
  }
}

Or you can create more complicated animations like waves and such by controlling the order of the frames as well like vanilla lava_still does:

{
  "animation": {
    "frametime": 2,
    "frames": [
      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
      18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
    ]
  }
}

Step #6. Update Your .lang File With Entries For Your Fluid Blocks and Fluids

In order to have a display name (for example in the creative inventory) correctly display in different languages, you should put entries into your .lang file that map to the exact names you used for the unlocalized names of your fluid block and fluid. Note that "fluid." will be automatically added to the beginning of the unlocalized name of the fluid. For example, in your us_en.lang file you might have:

   fluid.slime=Slime Juice

Learn more about localization in my .lang file tutorial.

Step #7. Put Any Sound Files In  Proper Asset Location

t is possible to change the fill and empty sounds for your fluid with the setFillSound() and setEmptySound() methods on your fluid instance. These methods require registered SoundEvent instances to be passed as parameters, so see my Jabelar's Sounds Tutorial for more information on how to set those up.

Warning: You need to make sure your sound assets match the name and location passed to these methods.


Conclusion


On its own, the Fluid above cannot be used yet. You now need to create a block and/or tank item or block, or enable the UniversalBucket. For information on how to do that, see Jabelar's Custom Fluids, Tanks, and Buckets Tutorial.

1 comment:

  1. Hey! Your tutorial is very good but can you give an exemple for the #4 ? i have try a lot of method but this is doesn't working :c

    ReplyDelete