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, and so shall we.

  1. public abstract class TutorialFluid extends BaseFluid
  2. {
  3. /**
  4. * @return is the given fluid an instance of this fluid?
  5. */
  6. @Override
  7. public boolean matchesType(Fluid fluid)
  8. {
  9. return fluid == getStill() || fluid == getFlowing();
  10. }
  11.  
  12. /**
  13. * @return is the fluid infinite like water?
  14. */
  15. @Override
  16. protected boolean isInfinite()
  17. {
  18. return false;
  19. }
  20.  
  21. /**
  22. * Perform actions when fluid flows into a replaceable block. Water drops
  23. * the block's loot table. Lava plays the "block.lava.extinguish" sound.
  24. */
  25. @Override
  26. protected void beforeBreakingBlock(IWorld world, BlockPos pos, BlockState state)
  27. {
  28. final BlockEntity blockEntity = state.getBlock().hasBlockEntity() ? world.getBlockEntity(pos) : null;
  29. Block.dropStacks(state, world.getWorld(), pos, blockEntity);
  30. }
  31.  
  32. /**
  33. * Lava returns true if its FluidState is above a certain height and the
  34. * Fluid is Water.
  35. *
  36. * @return if the given Fluid can flow into this FluidState?
  37. */
  38. @Override
  39. protected boolean method_15777(FluidState fluidState, BlockView blockView, BlockPos blockPos, Fluid fluid, Direction direction)
  40. {
  41. return false;
  42. }
  43.  
  44. /**
  45. * Possibly related to the distance checks for flowing into nearby holes?
  46. * Water returns 4. Lava returns 2 in the Overworld and 4 in the Nether.
  47. */
  48. @Override
  49. protected int method_15733(WorldView worldView)
  50. {
  51. return 4;
  52. }
  53.  
  54. /**
  55. * Water returns 1. Lava returns 1 in the Overworld and 2 in the Nether.
  56. */
  57. @Override
  58. protected int getLevelDecreasePerBlock(WorldView worldView)
  59. {
  60. return 1;
  61. }
  62.  
  63. /**
  64. * Water returns 5. Lava returns 30 in the Overworld and 10 in the Nether.
  65. */
  66. @Override
  67. public int getTickRate(WorldView worldView)
  68. {
  69. return 5;
  70. }
  71.  
  72. /**
  73. * Water and Lava both return 100.0F.
  74. */
  75. @Override
  76. protected float getBlastResistance()
  77. {
  78. return 100.0F;
  79. }
  80. }

Implementation

Now let's make an actual fluid which'll have still and flowing variants. For this tutorial, we will call it Acid. The missing references will be filled in shortly.

  1. public abstract class AcidFluid extends TutorialFluid
  2. {
  3. @Override
  4. public Fluid getStill()
  5. {
  6. return <YOUR_STILL_FLUID_HERE>;
  7. }
  8.  
  9. @Override
  10. public Fluid getFlowing()
  11. {
  12. return <YOUR_FLOWING_FLUID_HERE>;
  13. }
  14.  
  15. @Override
  16. public Item getBucketItem()
  17. {
  18. return <YOUR_BUCKET_ITEM_HERE>;
  19. }
  20.  
  21. @Override
  22. protected BlockState toBlockState(FluidState fluidState)
  23. {
  24. // method_15741 converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses
  25. return <YOUR_FLUID_BLOCK_HERE>.getDefaultState().with(Properties.LEVEL_15, method_15741(fluidState));
  26. }
  27.  
  28. public static class Flowing extends AcidFluid
  29. {
  30. @Override
  31. protected void appendProperties(StateManager.Builder<Fluid, FluidState> builder)
  32. {
  33. super.appendProperties(builder);
  34. builder.add(LEVEL);
  35. }
  36.  
  37. @Override
  38. public int getLevel(FluidState fluidState)
  39. {
  40. return fluidState.get(LEVEL);
  41. }
  42.  
  43. @Override
  44. public boolean isStill(FluidState fluidState)
  45. {
  46. return false;
  47. }
  48. }
  49.  
  50. public static class Still extends AcidFluid
  51. {
  52. @Override
  53. public int getLevel(FluidState fluidState)
  54. {
  55. return 8;
  56. }
  57.  
  58. @Override
  59. public boolean isStill(FluidState fluidState)
  60. {
  61. return true;
  62. }
  63. }
  64. }

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

  1. // ...
  2.  
  3. public static BaseFluid STILL_ACID;
  4. public static BaseFluid FLOWING_ACID;
  5.  
  6. public static Item ACID_BUCKET;
  7.  
  8. // ...
  9.  
  10. @Override
  11. public void onInitialize()
  12. {
  13. // ...
  14.  
  15. STILL_ACID = Registry.register(Registry.FLUID, new Identifier(MOD_ID, "acid"), new AcidFluid.Still());
  16.  
  17. FLOWING_ACID = Registry.register(Registry.FLUID, new Identifier(MOD_ID, "flowing_acid"), new AcidFluid.Flowing());
  18.  
  19. ACID_BUCKET = Registry.register(Registry.ITEM, new Identifier(MOD_ID, "acid_bucket"), new BucketItem(STILL_ACID, new Item.Settings().recipeRemainder(Items.BUCKET).maxCount(1)));
  20.  
  21. // ...
  22. }
  23.  
  24. // ...

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:

  1. {
  2. "replace": false,
  3. "values":
  4. [
  5. "your_mod_id:acid",
  6. "your_mod_id:flowing_acid"
  7. ]
  8. }

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:

  1. // ...
  2.  
  3. public static Block ACID;
  4.  
  5. // ...
  6.  
  7. @Override
  8. public void onInitialize()
  9. {
  10. // ...
  11.  
  12. ACID = Registry.register(Registry.BLOCK, new Identifier(MOD_ID, "acid"), new FluidBlock(STILL_ACID, FabricBlockSettings.copy(Blocks.WATER).build()){});
  13.  
  14. // ...
  15. }
  16.  
  17. // ...

Now that we have these static objects, we can go back to AcidFluid and fill in the overridden methods:

  1. public abstract class AcidFluid extends TutorialFluid
  2. {
  3. @Override
  4. public Fluid getStill()
  5. {
  6. return TutorialMod.STILL_ACID;
  7. }
  8.  
  9. @Override
  10. public Fluid getFlowing()
  11. {
  12. return TutorialMod.FLOWING_ACID;
  13. }
  14.  
  15. @Override
  16. public Item getBucketItem()
  17. {
  18. return TutorialMod.ACID_BUCKET;
  19. }
  20.  
  21. @Override
  22. protected BlockState toBlockState(FluidState fluidState)
  23. {
  24. // method_15741 converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses
  25. return TutorialMod.ACID.getDefaultState().with(Properties.LEVEL_15, method_15741(fluidState));
  26. }
  27.  
  28. // ...
  29. }

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.

  1. public class TutorialModClient implements ClientModInitializer
  2. {
  3. // ...
  4.  
  5. @Override
  6. public void onInitializeClient()
  7. {
  8. // ...
  9.  
  10. setupFluidRendering(TutorialMod.STILL_ACID, TutorialMod.FLOWING_ACID, new Identifier("minecraft", "water"), 0x4CC248);
  11. BlockRenderLayerMap.INSTANCE.putFluids(RenderLayer.getTranslucent(), TutorialMod.STILL_ACID, TutorialMod.FLOWING_ACID);
  12.  
  13. // ...
  14. }
  15.  
  16. // ...
  17.  
  18. public static void setupFluidRendering(final Fluid still, final Fluid flowing, final Identifier textureFluidId, final int color)
  19. {
  20. final Identifier stillSpriteId = new Identifier(textureFluidId.getNamespace(), "block/" + textureFluidId.getPath() + "_still");
  21. final Identifier flowingSpriteId = new Identifier(textureFluidId.getNamespace(), "block/" + textureFluidId.getPath() + "_flow");
  22.  
  23. // If they're not already present, add the sprites to the block atlas
  24. ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register((atlasTexture, registry) ->
  25. {
  26. registry.register(stillSpriteId);
  27. registry.register(flowingSpriteId);
  28. });
  29.  
  30. final Identifier fluidId = Registry.FLUID.getId(still);
  31. final Identifier listenerId = new Identifier(fluidId.getNamespace(), fluidId.getPath() + "_reload_listener");
  32.  
  33. final Sprite[] fluidSprites = new Sprite[] { null, null };
  34.  
  35. ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener()
  36. {
  37. @Override
  38. public Identifier getFabricId()
  39. {
  40. return listenerId;
  41. }
  42.  
  43. /**
  44. * Get the sprites from the block atlas when resources are reloaded
  45. */
  46. @Override
  47. public void apply(ResourceManager resourceManager)
  48. {
  49. final Function<Identifier, Sprite> atlas = MinecraftClient.getInstance().getSpriteAtlas(SpriteAtlasTexture.BLOCK_ATLAS_TEX);
  50. fluidSprites[0] = atlas.apply(stillSpriteId);
  51. fluidSprites[1] = atlas.apply(flowingSpriteId);
  52. }
  53. });
  54.  
  55. // The FluidRenderer gets the sprites and color from a FluidRenderHandler during rendering
  56. final FluidRenderHandler renderHandler = new FluidRenderHandler()
  57. {
  58. @Override
  59. public Sprite[] getFluidSprites(BlockRenderView view, BlockPos pos, FluidState state)
  60. {
  61. return fluidSprites;
  62. }
  63.  
  64. @Override
  65. public int getFluidColor(BlockRenderView view, BlockPos pos, FluidState state)
  66. {
  67. return color;
  68. }
  69. };
  70.  
  71. FluidRenderHandlerRegistry.INSTANCE.register(still, renderHandler);
  72. FluidRenderHandlerRegistry.INSTANCE.register(flowing, renderHandler);
  73. }
  74.  
  75. // ...
  76. }

If you want to use your own fluid textures, you can refer to vanilla's assets 1) as a template.

Generation in the world

To make lakes of acid generate in the world, you can create a net.minecraft.world.gen.feature.LakeFeature in your ModInitializer and then add it to the biomes you want it to generate in:

  1. // ...
  2.  
  3. public static LakeFeature ACID_LAKE;
  4.  
  5. // ...
  6.  
  7. @Override
  8. public void onInitialize()
  9. {
  10. // ...
  11.  
  12. ACID_LAKE = Registry.register(Registry.FEATURE, new Identifier(MOD_ID, "acid_lake"), new LakeFeature(SingleStateFeatureConfig::deserialize));
  13.  
  14. // generate in swamps, similar to water lakes, but with a chance of 40 (the higher the number, the lower the generation chance)
  15. Biomes.SWAMP.addFeature(
  16. GenerationStep.Feature.LOCAL_MODIFICATIONS,
  17. ACID_LAKE.configure(new SingleStateFeatureConfig(ACID.getDefaultState()))
  18. .createDecoratedFeature(Decorator.WATER_LAKE.configure(new ChanceDecoratorConfig(40)))
  19. );
  20.  
  21. // ...
  22. }
  23.  
  24. // ...
1)
assets/minecraft/blockstates/water.json
assets/minecraft/models/block/water.json
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
tutorial/fluids.1581422853.txt.gz · Last modified: 2020/02/11 12:07 by upcraftlp