User Tools

Site Tools


tutorial:fluids

This is an old revision of the document!


map2fabricyarn - Page contains unknown Intermediary names:
field_11154 field_11142 field_11146

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.

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

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.

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

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

  1. public static FlowableFluid STILL_ACID;
  2. public static FlowableFluid FLOWING_ACID;
  3. public static Item ACID_BUCKET;
  4.  
  5. @Override
  6. public void onInitialize() {
  7. STILL_ACID = Registry.register(Registry.field_11154, new Identifier(MOD_ID, "acid"), new AcidFluid.Still());
  8. FLOWING_ACID = Registry.register(Registry.field_11154, new Identifier(MOD_ID, "flowing_acid"), new AcidFluid.Flowing());
  9. ACID_BUCKET = Registry.register(Registry.field_11142, new Identifier(MOD_ID, "acid_bucket"),
  10. new BucketItem(STILL_ACID, new Item.Settings().recipeRemainder(Items.BUCKET).maxCount(1)));
  11.  
  12. // ...
  13. }
  14.  
  15. // ...

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. public static Block ACID;
  2.  
  3. @Override
  4. public void onInitialize() {
  5. ACID = Registry.register(Registry.field_11146, new Identifier(MOD_ID, "acid"), new FluidBlock(STILL_ACID, FabricBlockSettings.copy(Blocks.WATER)){});
  6.  
  7. // ...
  8. }

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. @Override
  3. public Fluid getStill() {
  4. return TutorialMod.STILL_ACID;
  5. }
  6.  
  7. @Override
  8. public Fluid getFlowing() {
  9. return TutorialMod.FLOWING_ACID;
  10. }
  11.  
  12. @Override
  13. public Item getBucketItem() {
  14. return TutorialMod.ACID_BUCKET;
  15. }
  16.  
  17. @Override
  18. protected BlockState toBlockState(FluidState fluidState) {
  19. // getBlockStateLevel converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses
  20. return TutorialMod.ACID.getDefaultState().with(Properties.LEVEL_15, getBlockStateLevel(fluidState));
  21. }
  22.  
  23. public static class Flowing extends AcidFluid {
  24. @Override
  25. protected void appendProperties(StateManager.Builder<Fluid, FluidState> builder) {
  26. super.appendProperties(builder);
  27. builder.add(LEVEL);
  28. }
  29.  
  30. @Override
  31. public int getLevel(FluidState fluidState) {
  32. return fluidState.get(LEVEL);
  33. }
  34.  
  35. @Override
  36. public boolean isStill(FluidState fluidState) {
  37. return false;
  38. }
  39. }
  40.  
  41. public static class Still extends AcidFluid {
  42. @Override
  43. public int getLevel(FluidState fluidState) {
  44. return 8;
  45. }
  46.  
  47. @Override
  48. public boolean isStill(FluidState fluidState) {
  49. return true;
  50. }
  51. }
  52. }

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

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. public static LakeFeature ACID_LAKE;
  2.  
  3. @Override
  4. public void onInitialize() {
  5. ACID_LAKE = Registry.register(Registry.FEATURE, new Identifier(MOD_ID, "acid_lake"), new LakeFeature(SingleStateFeatureConfig::deserialize));
  6.  
  7. // generate in swamps, similar to water lakes, but with a chance of 40 (the higher the number, the lower the generation chance)
  8. Biomes.SWAMP.addFeature(
  9. GenerationStep.Feature.LOCAL_MODIFICATIONS,
  10. ACID_LAKE.configure(new SingleStateFeatureConfig(ACID.getDefaultState()))
  11. .createDecoratedFeature(Decorator.WATER_LAKE.configure(new ChanceDecoratorConfig(40)))
  12. );
  13. }

Advanced customization

You may be noticed that your custom fluid is too much similar to water. In this section we'll cover some customizations, like adding custom fog and fog color, “strenght” (pushing back the player), and adding custom sounds and particles.

Unfortunately, for now, there isn't a way to do this normally, and even with Fabric Api, so we need to use mixins to change the behaviour of Entity class and and BackgroundRenderer class.

To start, remove the fluid from the water tag we added previously, and add a custom tag for custom behaviours:

  1. //Add in the main mod file
  2. public static final Tag<Fluid> FABRIC_FLUIDS = TagFactory.FLUID.create(new Identifier(MOD_ID, "fabric_fluid"));

Then set all fluids that you want to customize to your custom tag:

/data/tutorial/tags/fabric_fluid.json

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

Adding fog customizations

To make things simpler and more customizable, let's add an interface to get the fog values:

  1. public interface FabricFlowableFluid {
  2. /**
  3.   * Get the fog color.
  4.   */
  5. int getFogColor(Entity focusedEntity);
  6.  
  7. /**
  8.   * Get the fog start value.
  9.   */
  10. float getFogStart(Entity focusedEntity);
  11.  
  12. /**
  13.   * Get the fog end value.
  14.   */
  15. float getFogEnd(Entity focusedEntity);
  16. }

Now you must implement this interface to the previous example fluid class:

  1. public abstract class AcidFluid extends TutorialFluid implements FabricFlowableFluid {
  2.  
  3. [...]
  4.  
  5. /**
  6.   * Get the fog color.
  7.   */
  8. @Override
  9. public int getFogColor(Entity focusedEntity) {
  10. //Set the fog color to #99ff33 for a light green acid.
  11. return 0x99ff33;
  12. }
  13.  
  14. /**
  15.   * Get the fog start value. (Lava uses 0.25f, and 0.0f if the player has fire resistance)
  16.   */
  17. @Override
  18. public float getFogStart(Entity focusedEntity) {
  19. //You can use focusedEntity to get the effects, just comment for now.
  20. //if (entity instanceof LivingEntity && ((LivingEntity)entity).hasStatusEffect(StatusEffects.FIRE_RESISTANCE)) return 0.0f;
  21. return 0.25f;
  22. }
  23.  
  24. /**
  25.   * Get the fog end value. (Lava uses 1.0f, and 3.0f if the player has fire resistance)
  26.   */
  27. @Override
  28. public float getFogEnd(Entity focusedEntity) {
  29. //You can use focusedEntity to get the effects, just comment for now.
  30. //if (entity instanceof LivingEntity && ((LivingEntity)entity).hasStatusEffect(StatusEffects.FIRE_RESISTANCE)) return 3.0f
  31. return 1.0f;
  32. }
  33.  
  34. [...]
  35. }

To make the game use these values we must use an inject mixin to two methods inside the BackgroundRenderer class:

  • render : This will render the fog color
  • applyFog : This will aplly the fog with the start and end parameters.
  1. @Mixin(BackgroundRenderer.class)
  2. public class BackgroundRendererMixin {
  3. @Shadow private static float red;
  4. @Shadow private static float green;
  5. @Shadow private static float blue;
  6. @Shadow private static long lastWaterFogColorUpdateTime = -1L;
  7.  
  8. @Inject(method = "render(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/world/ClientWorld;IF)V",
  9. at = @At("HEAD"),
  10. cancellable = true)
  11. private static void render(Camera camera, float tickDelta, ClientWorld world, int i, float f, CallbackInfo ci) {
  12.  
  13. //Get the fluid that submerged the camera
  14. FluidState fluidState = ((FabricCamera)camera).getSubmergedFluidState();
  15.  
  16. //If this is an instance of FabricFlowableFluid interface...
  17. if (fluidState.getFluid() instanceof FabricFlowableFluid fluid) {
  18.  
  19. //Get the color of the fog...
  20. int fogColor = fluid.getFogColor(camera.getFocusedEntity());
  21.  
  22. //This is an hexadecimal color, so we need to get the three "red", "green", and "blue" values.
  23. red = (fogColor >> 16 & 255) / 255f;
  24. green = (fogColor >> 8 & 255) / 255f;
  25. blue = (fogColor & 255) / 255f;
  26.  
  27. //This is for compatibility, just add!
  28. lastWaterFogColorUpdateTime = -1L;
  29.  
  30. //Apply the color, then return.
  31. RenderSystem.clearColor(red, green, blue, 0.0f);
  32.  
  33. ci.cancel();
  34. }
  35. }
  36.  
  37. @Inject(method = "applyFog(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/BackgroundRenderer$FogType;FZ)V",
  38. at = @At("HEAD"),
  39. cancellable = true)
  40. private static void applyFog(Camera camera, BackgroundRenderer.FogType fogType, float viewDistance, boolean thickFog, CallbackInfo ci) {
  41.  
  42. //Get the fluid that submerged the camera
  43. FluidState fluidState = ((FabricCamera)camera).getSubmergedFluidState();
  44.  
  45. //If this is an instance of FabricFlowableFluid interface...
  46. if (fluidState.getFluid() instanceof FabricFlowableFluid fluid) {
  47.  
  48. //Get the start and end parameters and apply them, then return.
  49. RenderSystem.setShaderFogStart(fluid.getFogStart(camera.getFocusedEntity()));
  50. RenderSystem.setShaderFogEnd(fluid.getFogEnd(camera.getFocusedEntity()));
  51.  
  52. ci.cancel();
  53. }
  54. }
  55. }

From 1.17 camera has not the getSubmergedFluidState method so, as you can see, we must “re-add” it, this is pretty simple.

Add a FabricCamera interface:

  1. public interface FabricCamera {
  2. /**
  3.   * Returns the fluid in which the camera is submerged.
  4.   */
  5. FluidState getSubmergedFluidState();
  6. }

Add a mixin to get the value (Accessor) from the camera:

  1. @Mixin(Camera.class)
  2. public class CameraMixin implements FabricCamera {
  3. @Shadow private BlockView area;
  4. @Shadow @Final private BlockPos.Mutable blockPos;
  5.  
  6. /**
  7.   * Returns the fluid in which the camera is submerged.
  8.   */
  9. @Override
  10. public FluidState getSubmergedFluidState() {
  11. return this.area.getFluidState(this.blockPos);
  12. }
  13. }

Adding push back, custom sounds and custom particles

For first, extend the previous FabricFlowableFluid interface to get the values and make custom behaviours.

Now it will be like this:

  1. public interface FabricFlowableFluid {
  2. /**
  3.   * Get the fog color.
  4.   */
  5. int getFogColor(Entity focusedEntity);
  6.  
  7. /**
  8.   * Get the fog start value.
  9.   */
  10. float getFogStart(Entity focusedEntity);
  11.  
  12. /**
  13.   * Get the fog end value.
  14.   */
  15. float getFogEnd(Entity focusedEntity);
  16.  
  17. /**
  18.   * Get the fluid pushing strength.
  19.   */
  20. double getStrength();
  21.  
  22. /**
  23.   * Get the fluid splash sound.
  24.   */
  25. Optional<SoundEvent> getSplashSound();
  26.  
  27. /**
  28.   * Make things when the player splashes on the fluid (like jumping).
  29.   */
  30. void onSplash(World world, Vec3d pos, Entity entity);
  31. }

Add the new methods implementations to the fluid class:

  1. public abstract class AcidFluid extends TutorialFluid implements FabricFlowableFluid {
  2.  
  3. [...]
  4.  
  5. /**
  6.   * Get the fluid pushing strength. (Water uses 0.014d, Lava uses 0.0023333333333333335d, and 0.007d in the Nether)
  7.   */
  8. @Override
  9. double getStrength() {
  10. return 0.014d;
  11. }
  12.  
  13. /**
  14.   * Get the fluid splash sound.
  15.   */
  16. @Override
  17. Optional<SoundEvent> getSplashSound() {
  18. //For this example we will use the strider step sound in lava.
  19. return Optional.of(SoundEvents.ENTITY_STRIDER_STEP_LAVA);
  20. }
  21.  
  22. /**
  23.   * Make things when the player splashes on the fluid (like jumping).
  24.   */
  25. @Override
  26. void onSplash(World world, Vec3d pos, Entity entity) {
  27. //You can use the parameters in this method to add the particles you want.
  28. //This is an example that will show a smoke particle when hitting the fluid (or jumping on it).
  29. //pos is the position of each block of still state, or flowing state, of the fluid, it represents the corner of the block.
  30. //entity is the entity that caused the splash event.
  31. world.addParticle(ParticleTypes.SMOKE, pos.getX(), h + 1.0F, pos.getZ(), 0.02d, 0.02d, 0.02d);
  32. }
  33.  
  34. [...]
  35. }

Now we must add a mixin to checkWaterState method of Entity class to make the fluid use our custom behaviours.

For now we will disable the swimming on the fluid, this will be covered in the future.

  1. @Mixin(Entity.class)
  2. public abstract class EntityMixin {
  3. @Shadow public World world;
  4. @Shadow private BlockPos blockPos;
  5. @Shadow protected boolean firstUpdate;
  6. @Shadow public float fallDistance;
  7. @Shadow protected boolean touchingWater;
  8. protected boolean touchingFabricFlowableFluid;
  9.  
  10. @Shadow @Nullable public abstract Entity getVehicle();
  11. @Shadow public abstract boolean updateMovementInFluid(Tag<Fluid> tag, double d);
  12. @Shadow public abstract void extinguish();
  13. //@Shadow protected abstract void onSwimmingStart();
  14. @Shadow public abstract void setSwimming(boolean swimming);
  15. @Shadow public abstract boolean isRegionUnloaded();
  16. @Shadow public abstract void playSound(SoundEvent sound, float volume, float pitch);
  17. @Shadow public abstract double getX();
  18. @Shadow public abstract double getY();
  19. @Shadow public abstract double getZ();
  20.  
  21.  
  22. @Inject(method = "checkWaterState()V",
  23. at = @At("HEAD"),
  24. cancellable = true)
  25. void checkWaterState(CallbackInfo ci) {
  26. if (this.getVehicle() instanceof BoatEntity) {
  27.  
  28. //If the player is on a boat it doesn't touch the fluid
  29. this.touchingWater = false; //This is added for compatibility with water.
  30. this.touchingFabricFlowableFluid = false;
  31.  
  32. ci.cancel();
  33. } else {
  34.  
  35. //Gets the fluid strength
  36. double fluidStrength = getFluidStrength();
  37.  
  38. //Triggers the movement in the fluid
  39. if (fluidStrength != -1 && this.updateMovementInFluid(FabricFluidTags.FABRIC_FLUIDS, fluidStrength)) {
  40. if (!this.touchingFabricFlowableFluid && !this.firstUpdate) {
  41. //If the player has jumped on the fluid, or touched the fluid for the first fime, executes the touched method below
  42. this.onFabricFlowableFluidTouched();
  43. //this.onSwimmingStart();
  44. }
  45.  
  46. //This prevents fall damage when hitting the fluid (like water).
  47. this.fallDistance = 0.0F;
  48.  
  49. //When the player is on the fluid set touching to true.
  50. this.touchingFabricFlowableFluid = true;
  51. this.touchingWater = true; //This is added for compatibility with water.
  52.  
  53. //This extinguish the fire from the player (like water).
  54. this.extinguish();
  55.  
  56. ci.cancel();
  57. }
  58. else {
  59. //When the player leaved the fluid (or jumped from it) set touching to false.
  60. this.touchingFabricFlowableFluid = false;
  61. }
  62. }
  63. }
  64.  
  65. /**
  66.   * This is not very important, will simply disable swimming on the fluid for now.
  67.   */
  68. @Inject(method = "updateSwimming()V",
  69. at = @At("HEAD"),
  70. cancellable = true)
  71. public void updateSwimming(CallbackInfo ci) {
  72. if (this.touchingFabricFlowableFluid) {
  73. //Disable swimming
  74. setSwimming(false);
  75.  
  76. ci.cancel();
  77. }
  78. }
  79.  
  80. /**
  81.   * Get the fluid pushing strength from the fluid.
  82.   */
  83. private double getFluidStrength() {
  84. if (this.isRegionUnloaded()) {
  85. return -1;
  86. } else {
  87. FluidState fluidState = this.world.getFluidState(this.blockPos);
  88. if (fluidState.getFluid() instanceof FabricFlowableFluid fluid) {
  89. return fluid.getStrength();
  90. }
  91. else return -1;
  92. }
  93. }
  94.  
  95. /**
  96.   * Executed when the player touches the fluid for the first time, or jumps on it.
  97.   */
  98. private void onFabricFlowableFluidTouched() {
  99. FluidState fluidState = this.world.getFluidState(this.blockPos);
  100. if (fluidState.getFluid() instanceof FabricFlowableFluid fluid) {
  101.  
  102. //Gets and play the splash sound
  103. fluid.getSplashSound().ifPresent(soundEvent -> this.playSound(soundEvent, 1f, 1f));
  104.  
  105. //Execute the onSplash event
  106. fluid.onSplash(this.world, new Vec3d(this.getX(), this.getY(), this.getZ()), (Entity)(Object)this);
  107. }
  108. }
  109. }
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.1634687662.txt.gz · Last modified: 2021/10/19 23:54 by salvopelux