User Tools

Site Tools


tutorial:fluids

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:fluids [2021/10/19 23:44] – Completed Advanced customization section. salvopeluxtutorial:fluids [2026/02/23 14:26] (current) – fixed reference infinitychances
Line 5: Line 5:
  
 ===== Making an abstract fluid ===== ===== Making an abstract fluid =====
-Vanilla fluids extend ''<yarn net.minecraft.class_3609>'', and so shall we. +Vanilla fluids extend ''net.minecraft.world.level.material.FlowingFluid'', and so shall we. 
-<yarncode java [enable_line_numbers="true"]> +<code java [enable_line_numbers="true"]> 
-public abstract class TutorialFluid extends class_3609 {+public abstract class TutorialFluid extends FlowingFluid {
  /**  /**
  * @return whether the given fluid an instance of this fluid  * @return whether the given fluid an instance of this fluid
  */  */
  @Override  @Override
- public boolean method_15780(class_3611 fluid) { + public boolean isSame(Fluid fluid) { 
- return fluid == method_15751() || fluid == method_15750();+ return fluid == getSource() || fluid == getFlowing();
  }  }
  
  /**  /**
- * @return whether the fluid infinite like water+ * @return whether the fluid is infinite (which means can be infinitely created like water). In vanilla, it depends on the game rule.
  */  */
  @Override  @Override
- protected boolean method_15737() {+ protected boolean canConvertToSource() {
  return false;  return false;
  }  }
  
  /**  /**
- * Perform actions when fluid flows into a replaceable block. Water drops+ * Perform actions when the fluid flows into a replaceable block. Water drops
  * the block's loot table. Lava plays the "block.lava.extinguish" sound.  * the block's loot table. Lava plays the "block.lava.extinguish" sound.
  */  */
  @Override  @Override
- protected void method_15730(class_1936 worldclass_2338 pos, class_2680 state) { + protected void beforeDestroyingBlock(LevelAccessor levelBlockPos pos, BlockState state) { 
- final class_2586 blockEntity = state.method_31709() ? world.method_8321(pos) : null; + final BlockEntity entity = state.hasBlockEntity() ? level.getBlockEntity(pos) : null; 
- class_2248.method_9610(state, world, pos, blockEntity);+ Block.dropResources(state, level, pos, entity);
  }  }
  
  /**  /**
- * Lava returns true if its FluidState is above a certain height and the+ * Lava returns true if it'FluidState is above a certain height and the
  * Fluid is Water.  * Fluid is Water.
  
Line 41: Line 41:
  */  */
  @Override  @Override
- protected boolean method_15777(class_3610 fluidStateclass_1922 blockViewclass_2338 blockPosclass_3611 fluid, class_2350 direction) {+ protected boolean canBeReplacedWith(FluidState stateBlockGetter levelBlockPos posFluid fluid, Direction direction) {
  return false;  return false;
  }  }
Line 50: Line 50:
  */  */
  @Override  @Override
- protected int method_15733(class_4538 worldView) {+ protected int getSlopeFindDistance(LevelReader reader) {
  return 4;  return 4;
  }  }
  
  /**  /**
- * Water returns 1. Lava returns 2 in the Overworld and 1 in the Nether.+ * Water returns 1. Lava returns 2 in the overworld and 1 in the nether. 
 + * @return How many levels a fluid loses per block.
  */  */
  @Override  @Override
- protected int method_15739(class_4538 worldView) {+ protected int getDropOff(LevelReader reader) {
  return 1;  return 1;
  }  }
Line 64: Line 65:
  /**  /**
  * Water returns 5. Lava returns 30 in the Overworld and 10 in the Nether.  * Water returns 5. Lava returns 30 in the Overworld and 10 in the Nether.
 + * Seems to return the delay before it updates its state.
  */  */
  @Override  @Override
- public int method_15789(class_4538 worldView) {+ public int getTickDelay(LevelReader reader) {
  return 5;  return 5;
  }  }
Line 74: Line 76:
  */  */
  @Override  @Override
- protected float method_15784() {+ protected float getExplosionResistance() {
  return 100.0F;  return 100.0F;
  }  }
 } }
-</yarncode>+</code>
  
 ===== Implementation ===== ===== 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. 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.
  
-<yarncode java [enable_line_numbers="true"]>+<code java [enable_line_numbers="true"]>
 public abstract class AcidFluid extends TutorialFluid { public abstract class AcidFluid extends TutorialFluid {
  @Override  @Override
- public class_3611 method_15751() { + public Fluid getSource() { 
- return YOUR_STILL_FLUID_HERE;+ return YOUR_SOURCE_FLUID_HERE;
  }  }
  
  @Override  @Override
- public class_3611 method_15750() {+ public Fluid getFlowing() {
  return YOUR_FLOWING_FLUID_HERE;  return YOUR_FLOWING_FLUID_HERE;
  }  }
  
  @Override  @Override
- public class_1792 method_15774() {+ public Item getBucket() {
  return YOUR_BUCKET_ITEM_HERE;  return YOUR_BUCKET_ITEM_HERE;
  }  }
  
  @Override  @Override
- protected class_2680 method_15790(class_3610 fluidState) { + protected BlockState createLegacyBlock(FluidState state) { 
- return YOUR_FLUID_BLOCK_HERE.method_9564().method_11657(class_2741.field_12538method_15741(fluidState));+ return YOUR_FLUID_BLOCK_HERE.defaultBlockState().setValue(BlockStateProperties.LEVELgetLegacyLevel(state));
  }  }
  
  public static class Flowing extends AcidFluid {  public static class Flowing extends AcidFluid {
  @Override  @Override
- protected void method_15775(class_2689.class_2690<class_3611class_3610> builder) { + protected void createFluidStateDefinition(StateDefinition.Builder<FluidFluidState> builder) { 
- super.method_15775(builder); + super.createFluidStateDefinition(builder); 
- builder.method_11667(field_15900);+ builder.add(LEVEL);
  }  }
  
  @Override  @Override
- public int method_15779(class_3610 fluidState) { + public boolean isSource(FluidState state) { 
- return fluidState.method_11654(field_15900);+ return false;
  }  }
  
  @Override  @Override
- public boolean method_15793(class_3610 fluidState) { + public int getAmount(FluidState state) { 
- return false;+ return state.getValue(LEVEL);
  }  }
  }  }
Line 125: Line 127:
  public static class Still extends AcidFluid {  public static class Still extends AcidFluid {
  @Override  @Override
- public int method_15779(class_3610 fluidState) { + public boolean isSource(FluidState state) { 
- return 8;+ return true;
  }  }
  
  @Override  @Override
- public boolean method_15793(class_3610 fluidState) { + public int getAmount(FluidState state) { 
- return true;+ return 8;
  }  }
  }  }
 } }
-</yarncode>+</code>
  
 Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your ''ModInitializer'': Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your ''ModInitializer'':
  
-<yarncode java [enable_line_numbers="true"]> +<code java [enable_line_numbers="true"]> 
-public static class_3609 STILL_ACID+public static FlowingFluid ACID_SOURCE
-public static class_3609 FLOWING_ACID; +public static FlowingFluid FLOWING_ACID; 
-public static class_1792 ACID_BUCKET;+public static Item ACID_BUCKET;
    
 @Override @Override
 public void onInitialize() { public void onInitialize() {
- STILL_ACID class_2378.method_10230(class_2378.field_11154new class_2960(MOD_ID, "acid"), new AcidFluid.Still()); + ACID_SOURCE Registry.register(BuiltInRegistries.FLUIDIdentifier.fromNamespaceAndPath("tutorial", "acid"), new AcidFluid.Still()) 
- FLOWING_ACID = class_2378.method_10230(class_2378.field_11154new class_2960(MOD_ID, "flowing_acid"), new AcidFluid.Flowing()); + FLOWING_ACID =  Registry.register(BuiltInRegistries.FLUIDIdentifier.fromNamespaceAndPath("tutorial", "flowing_acid"), new AcidFluid.Flowing()); 
- ACID_BUCKET = class_2378.method_10230(class_2378.field_11142new class_2960(MOD_ID, "acid_bucket"),  + ACID_BUCKET = Registry.register(BuiltInRegistries.ITEMIdentifier.fromNamespaceAndPath("tutorial", "acid_bucket"),  
-        new class_1755(STILL_ACID, new class_1792.class_1793().method_7896(class_1802.field_8550).method_7889(1)));+        new BucketItem(ACID_SOURCE, properties), new Item.Properties().craftRemainder(Items.BUCKET).stacksTo(1);
    
  // ...  // ...
Line 155: Line 157:
    
 // ... // ...
-</yarncode>+</code>
  
-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:+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/fluid/water.json'' file and write the identifiers of your fluids in there:
 <code json [enable_line_numbers="true"]> <code json [enable_line_numbers="true"]>
 { {
Line 163: Line 165:
  "values":  "values":
  [  [
- "your_mod_id:acid", + "tutorial:acid", 
- "your_mod_id:flowing_acid"+ "tutorial:flowing_acid"
  ]  ]
 } }
Line 170: Line 172:
  
 ===== Making a fluid block ===== ===== Making a fluid block =====
-Next we need to create a block which will represent acid in the world. ''<yarn net.minecraft.class_2404>'' 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'':+Next we need to create a block which will represent acid in the world. ''net.minecraft.world.level.block.LiquidBlock'' is the class we need to use. In your ''ModInitializer'':
  
-<yarncode java [enable_line_numbers="true"]> +<code java [enable_line_numbers="true"]> 
-public static class_2248 ACID;+public static Block ACID;
  
 @Override @Override
 public void onInitialize() { public void onInitialize() {
- ACID = class_2378.method_10230(class_2378.field_11146new class_2960(MOD_ID, "acid"), new class_2404(STILL_ACIDFabricBlockSettings.method_9630(class_2246.field_10382)){});+ ACID = Registry.register(BuiltInRegistries.BLOCKIdentifier.fromNamespaceAndPath("tutorial", "acid"), new LiquidBlock(ACID_SOURCEBlockBehaviour.Properties.ofFullCopy(Blocks.WATER)));
   
  // ...  // ...
  
-</yarncode>+</code>
  
 Now that we have these static objects, we can go back to ''AcidFluid'' and fill in the overridden methods: Now that we have these static objects, we can go back to ''AcidFluid'' and fill in the overridden methods:
  
-<yarncode java [enable_line_numbers="true"]>+<code java [enable_line_numbers="true"]>
 public abstract class AcidFluid extends TutorialFluid { public abstract class AcidFluid extends TutorialFluid {
  @Override  @Override
- public class_3611 method_15751() { + public Fluid getSource() { 
- return TutorialMod.STILL_ACID;+ return TutorialMod.ACID_SOURCE;
  }  }
- +
  @Override  @Override
- public class_3611 method_15750() {+ public Fluid getFlowing() {
  return TutorialMod.FLOWING_ACID;  return TutorialMod.FLOWING_ACID;
  }  }
- +
  @Override  @Override
- public class_1792 method_15774() {+ public Item getBucket() {
  return TutorialMod.ACID_BUCKET;  return TutorialMod.ACID_BUCKET;
  }  }
- +
  @Override  @Override
- protected class_2680 method_15790(class_3610 fluidState) { + protected BlockState createLegacyBlock(FluidState state) { 
- // method_15741 converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses + return TutorialMod.ACID.defaultBlockState().setValue(BlockStateProperties.LEVELgetLegacyLevel(state));
- return TutorialMod.ACID.method_9564().method_11657(class_2741.field_12538method_15741(fluidState));+
  }  }
  
  public static class Flowing extends AcidFluid {  public static class Flowing extends AcidFluid {
  @Override  @Override
- protected void method_15775(class_2689.class_2690<class_3611class_3610> builder) { + protected void createFluidStateDefinition(StateDefinition.Builder<FluidFluidState> builder) { 
- super.method_15775(builder); + super.createFluidStateDefinition(builder); 
- builder.method_11667(field_15900);+ builder.add(LEVEL);
  }  }
  
  @Override  @Override
- public int method_15779(class_3610 fluidState) { + public boolean isSource(FluidState state) { 
- return fluidState.method_11654(field_15900);+ return false;
  }  }
  
  @Override  @Override
- public boolean method_15793(class_3610 fluidState) { + public int getAmount(FluidState state) { 
- return false;+ return state.getValue(LEVEL);
  }  }
  }  }
Line 228: Line 229:
  public static class Still extends AcidFluid {  public static class Still extends AcidFluid {
  @Override  @Override
- public int method_15779(class_3610 fluidState) { + public boolean isSource(FluidState state) { 
- return 8;+ return true;
  }  }
  
  @Override  @Override
- public boolean method_15793(class_3610 fluidState) { + public int getAmount(FluidState state) { 
- return true;+ return 8;
  }  }
  }  }
-  +
-</yarncode>+</code>
  
 ===== Rendering setup ===== ===== 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''.+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]]).
  
-<yarncode java [enable_line_numbers="true"]>+<code java [enable_line_numbers="true"]> 
 +@Environment(EnvType.CLIENT)
 public class TutorialModClient implements ClientModInitializer { public class TutorialModClient implements ClientModInitializer {
  
  @Override  @Override
  public void onInitializeClient() {  public void onInitializeClient() {
- setupFluidRendering(TutorialMod.STILL_ACID, TutorialMod.FLOWING_ACID, new class_2960("minecraft", "water"), 0x4CC248)+ FluidRenderHandlerRegistry.INSTANCE.register(TutorialMod.ACID_SOURCE, TutorialMod.FLOWING_ACID, SimpleFluidRenderHandler.coloredWater(0x4CC248));
- BlockRenderLayerMap.INSTANCE.putFluids(class_1921.method_23583(), TutorialMod.STILL_ACID, TutorialMod.FLOWING_ACID);+
  
- // ... + BlockRenderLayerMap.putFluids(ChunkSectionLayer.TRANSLUCENTTutorialMod.ACID_SOURCETutorialMod.FLOWING_ACID);
-+
- +
- public static void setupFluidRendering(final class_3611 still, final class_3611 flowing, final class_2960 textureFluidId, final int color) { +
- final class_2960 stillSpriteId = new class_2960(textureFluidId.method_12836()"block/" + textureFluidId.method_12832() + "_still"); +
- final class_2960 flowingSpriteId = new class_2960(textureFluidId.method_12836()"block/" + textureFluidId.method_12832() + "_flow"); +
- +
- // If they're not already present, add the sprites to the block atlas +
- ClientSpriteRegistryCallback.event(class_1059.field_5275).register((atlasTexture, registry) -> { +
- registry.register(stillSpriteId); +
- registry.register(flowingSpriteId); +
- }); +
- +
- final class_2960 fluidId = class_2378.field_11154.method_10221(still); +
- final class_2960 listenerId = new class_2960(fluidId.method_12836(), fluidId.method_12832() + "_reload_listener"); +
- +
- final class_1058[] fluidSprites = { null, null }; +
- +
- ResourceManagerHelper.get(class_3264.field_14188).registerReloadListener(new SimpleSynchronousResourceReloadListener() { +
- @Override +
- public class_2960 getFabricId() { +
- return listenerId; +
-+
- +
- /** +
- * Get the sprites from the block atlas when resources are reloaded +
- */ +
- @Override +
- public void method_14491(class_3300 resourceManager) { +
- final Function<class_2960, class_1058> atlas = class_310.method_1551().method_1549(class_1059.field_5275); +
- fluidSprites[0] = atlas.apply(stillSpriteId); +
- fluidSprites[1] = atlas.apply(flowingSpriteId); +
-+
- }); +
- +
- // The FluidRenderer gets the sprites and color from a FluidRenderHandler during rendering +
- final FluidRenderHandler renderHandler = new FluidRenderHandler() +
-+
- @Override +
- public class_1058[] getFluidSprites(class_1920 view, class_2338 pos, class_3610 state) { +
- return fluidSprites; +
-+
- +
- @Override +
- public int getFluidColor(class_1920 view, class_2338 pos, class_3610 state) { +
- return color; +
-+
- }; +
- +
- FluidRenderHandlerRegistry.INSTANCE.register(still, renderHandler); +
- FluidRenderHandlerRegistry.INSTANCE.register(flowing, renderHandler);+
  }  }
-} 
-</yarncode> 
- 
-If you want to use your own fluid textures, you can refer to vanilla's assets ((''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'')) as a template. 
- 
-===== Generation in the world ===== 
-To make lakes of acid generate in the world, you can create a ''<yarn net.minecraft.class_3085>'' in your ''ModInitializer'' and then add it to the biomes you want it to generate in: 
- 
-<code java [enable_line_numbers="true"]> 
-public static LakeFeature ACID_LAKE; 
- 
-@Override 
-public void onInitialize() { 
- ACID_LAKE = Registry.register(Registry.FEATURE, new Identifier(MOD_ID, "acid_lake"), new LakeFeature(SingleStateFeatureConfig::deserialize)); 
-  
- // generate in swamps, similar to water lakes, but with a chance of 40 (the higher the number, the lower the generation chance) 
- Biomes.SWAMP.addFeature( 
- GenerationStep.Feature.LOCAL_MODIFICATIONS, 
- ACID_LAKE.configure(new SingleStateFeatureConfig(ACID.getDefaultState())) 
- .createDecoratedFeature(Decorator.WATER_LAKE.configure(new ChanceDecoratorConfig(40))) 
- ); 
 } }
 </code> </code>
 +===== Adding custom textures to your fluid =====
 +To create a fluid with custom textures, you will need at least 2 new textures, with an optional 3rd, overlay texture that is shown when you look at the fluid behind glass or leaves. All textures go in the block textures folder.
  
-===== Advanced customization ===== +The first texture should be named YOUR_FLUID_NAME_still.png, and can optionally be animated. 
-You may be noticed that your custom fluid is too much similar to water. +The second texture should be named YOUR_FLUID_NAME_flowing.png, and should have double the size in the dimensionsFor exampleif your still texture has frames of 16x16, the flowing should have frames of 32x32. It can also be animated
-In this section we'll cover some customizationslike adding custom fog and fog color, "strenght" (pushing back the player), and adding custom sounds and particles. +The third will be named YOUR_FLUID_NAME_overlay.png, and should not be animated.
- +
-Unfortunatelyfor now, there isn't a way to do this normally, and even with Fabric Apiso 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:+
  
 +Using our acid example, the client initializer will look like this:
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-//Add in the main mod file +@Environment(EnvType.CLIENT
-public static final Tag<Fluid> FABRIC_FLUIDS = TagFactory.FLUID.create(new Identifier(MOD_ID, "fabric_fluid")); +public class TutorialModClient implements ClientModInitializer {
-</code>+
  
-Then set all fluids that you want to customize to your custom tag:+ @Override 
 + public void onInitializeClient() { 
 + FluidRenderHandlerRegistry.INSTANCE.register(TutorialMod.ACID_SOURCE, TutorialMod.FLOWING_ACID, new SimpleFluidRenderHandler( 
 + Identifier.fromNamespaceAndPath("tutorial", "block/acid_still"), 
 + Identifier.fromNamespaceAndPath("tutorial", "block/acid_flowing"), 
 + Identifier.fromNamespaceAndPath("tutorial", "block/acid_overlay") /*optional tint can go after the overlay*/);
  
-''/data/tutorial/tags/fabric_fluid.json'' + BlockRenderLayerMap.putFluids(ChunkSectionLayer.TRANSLUCENTTutorialMod.ACID_SOURCETutorialMod.FLOWING_ACID); 
- + }
-<code json [enable_line_numbers="true"]> +
-+
-  "replace": false, +
-  "values": +
-  [ +
-    "tutorial:acid"+
-    "tutorial:flowing_acid" +
-  ]+
 } }
 </code> </code>
  
-==== Adding fog customizations ==== +You can refer to vanilla's assets ((''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'' \\ ''assets/minecraft/textures/block/water_overlay.png'')) as template for your own textures.
-To make things simpler and more customizable, let's add an interface to get the fog values: +
- +
-<code java [enable_line_numbers="true"]> +
-public interface FabricFlowableFluid { +
-    /** +
-     * Get the fog color. +
-     */ +
-    int getFogColor(Entity focusedEntity); +
-     +
-    /** +
-     * Get the fog start value. +
-     */ +
-    float getFogStart(Entity focusedEntity); +
-     +
-    /** +
-     * Get the fog end value. +
-     */ +
-    float getFogEnd(Entity focusedEntity); +
-+
-</code> +
- +
-Now you must implement this interface to the previous example fluid class: +
- +
-<code java [enable_line_numbers="true"]> +
-public abstract class AcidFluid extends TutorialFluid implements FabricFlowableFluid { +
- +
-    [...] +
-     +
-    /** +
-     * Get the fog color. +
-     */ +
-    @Override +
-    public int getFogColor(Entity focusedEntity) { +
-        //Set the fog color to #99ff33 for a light green acid. +
-        return 0x99ff33; +
-    } +
- +
-    /** +
-     * Get the fog start value. (Lava uses 0.25f, and 0.0f if the player has fire resistance) +
-     */ +
-    @Override +
-    public float getFogStart(Entity focusedEntity) { +
-        //You can use focusedEntity to get the effects, just comment for now. +
-        //if (entity instanceof LivingEntity && ((LivingEntity)entity).hasStatusEffect(StatusEffects.FIRE_RESISTANCE)) return 0.0f; +
-        return 0.25f; +
-    } +
- +
-    /** +
-     * Get the fog end value. (Lava uses 1.0f, and 3.0f if the player has fire resistance) +
-     */ +
-    @Override +
-    public float getFogEnd(Entity focusedEntity) { +
-        //You can use focusedEntity to get the effects, just comment for now. +
-        //if (entity instanceof LivingEntity && ((LivingEntity)entity).hasStatusEffect(StatusEffects.FIRE_RESISTANCE)) return 3.0f +
-        return 1.0f; +
-    } +
-     +
-    [...] +
-+
-</code> +
- +
-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. +
- +
-<code java [enable_line_numbers="true"]> +
-@Mixin(BackgroundRenderer.class) +
-public class BackgroundRendererMixin { +
-    @Shadow private static float red; +
-    @Shadow private static float green; +
-    @Shadow private static float blue; +
-    @Shadow private static long lastWaterFogColorUpdateTime = -1L; +
- +
-    @Inject(method = "render(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/world/ClientWorld;IF)V", +
-            at = @At("HEAD"), +
-            cancellable = true) +
-    private static void render(Camera camera, float tickDelta, ClientWorld world, int i, float f, CallbackInfo ci) { +
-     +
-        //Get the fluid that submerged the camera +
-        FluidState fluidState = ((FabricCamera)camera).getSubmergedFluidState(); +
- +
-        //If this is an instance of FabricFlowableFluid interface... +
-        if (fluidState.getFluid() instanceof FabricFlowableFluid fluid) { +
-         +
-            //Get the color of the fog... +
-            int fogColor = fluid.getFogColor(camera.getFocusedEntity()); +
-             +
-            //This is an hexadecimal color, so we need to get the three "red", "green", and "blue" values. +
-            red = (fogColor >> 16 & 255) / 255f; +
-            green = (fogColor >> 8 & 255) / 255f; +
-            blue = (fogColor & 255) / 255f; +
-             +
-            //This is for compatibility, just add! +
-            lastWaterFogColorUpdateTime = -1L; +
-             +
-            //Apply the color, then return. +
-            RenderSystem.clearColor(red, green, blue, 0.0f); +
-             +
-            ci.cancel(); +
-        } +
-    } +
- +
-    @Inject(method = "applyFog(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/BackgroundRenderer$FogType;FZ)V", +
-            at = @At("HEAD"), +
-            cancellable = true) +
-    private static void applyFog(Camera camera, BackgroundRenderer.FogType fogType, float viewDistance, boolean thickFog, CallbackInfo ci) { +
-     +
-        //Get the fluid that submerged the camera +
-        FluidState fluidState = ((FabricCamera)camera).getSubmergedFluidState(); +
- +
-        //If this is an instance of FabricFlowableFluid interface... +
-        if (fluidState.getFluid() instanceof FabricFlowableFluid fluid) { +
-         +
-            //Get the start and end parameters and apply them, then return. +
-            RenderSystem.setShaderFogStart(fluid.getFogStart(camera.getFocusedEntity())); +
-            RenderSystem.setShaderFogEnd(fluid.getFogEnd(camera.getFocusedEntity())); +
-             +
-            ci.cancel(); +
-        } +
-    } +
-+
-</code> +
- +
-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: +
- +
-<code java [enable_line_numbers="true"]> +
-public interface FabricCamera { +
-    /** +
-     * Returns the fluid in which the camera is submerged. +
-     */ +
-    FluidState getSubmergedFluidState(); +
-+
-</code> +
- +
-Add a mixin to get the value (Accessor) from the camera: +
- +
-<code java [enable_line_numbers="true"]> +
-@Mixin(Camera.class) +
-public class CameraMixin implements FabricCamera { +
-    @Shadow private BlockView area; +
-    @Shadow @Final private BlockPos.Mutable blockPos; +
- +
-    /** +
-     * Returns the fluid in which the camera is submerged. +
-     */ +
-    @Override +
-    public FluidState getSubmergedFluidState() { +
-        return this.area.getFluidState(this.blockPos); +
-    } +
-+
-</code> +
- +
-==== 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: +
- +
-<code java [enable_line_numbers="true"]> +
-public interface FabricFlowableFluid { +
-    /** +
-     * Get the fog color. +
-     */ +
-    int getFogColor(Entity focusedEntity); +
-     +
-    /** +
-     * Get the fog start value. +
-     */ +
-    float getFogStart(Entity focusedEntity); +
-     +
-    /** +
-     * Get the fog end value. +
-     */ +
-    float getFogEnd(Entity focusedEntity); +
- +
-    /** +
-     * Get the fluid pushing strength. +
-     */ +
-    double getStrength(); +
- +
-    /** +
-     * Get the fluid splash sound. +
-     */ +
-    Optional<SoundEvent> getSplashSound(); +
- +
-    /** +
-     * Make things when the player splashes on the fluid (like jumping). +
-     */ +
-    void onSplash(World world, Vec3d pos, Entity entity); +
-+
-</code> +
- +
-Add the new methods implementations to the fluid class +
- +
-<code java [enable_line_numbers="true"]> +
-public abstract class AcidFluid extends TutorialFluid implements FabricFlowableFluid { +
- +
-    [...] +
-     +
-    /** +
-     * Get the fluid pushing strength. (Water uses 0.014d, Lava uses 0.0023333333333333335d, and 0.007d in the Nether) +
-     */ +
-    @Override +
-    double getStrength() { +
-        return 0.014d; +
-    } +
- +
-    /** +
-     * Get the fluid splash sound. +
-     */ +
-    @Override +
-    Optional<SoundEvent> getSplashSound() { +
-        //For this example we will use the strider step sound in lava. +
-        return Optional.of(SoundEvents.ENTITY_STRIDER_STEP_LAVA); +
-    } +
- +
-    /** +
-     * Make things when the player splashes on the fluid (like jumping). +
-     */ +
-    @Override +
-    void onSplash(World world, Vec3d pos, Entity entity) { +
-        //You can use the parameters in this method to add the particles you want. +
-        //This is an example that will show a smoke particle when hitting the fluid (or jumping on it). +
-        //pos is the position of each block of still state, or flowing state, of the fluid, it represents the corner of the block. +
-        //entity is the entity that caused the splash event. +
-        world.addParticle(ParticleTypes.SMOKE, pos.getX(), h + 1.0F, pos.getZ(), 0.02d, 0.02d, 0.02d); +
-    } +
-     +
-    [...] +
-+
-</code> +
- +
-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. +
- +
-<code java [enable_line_numbers="true"]> +
-@Mixin(Entity.class) +
-public abstract class EntityMixin { +
-    @Shadow public World world; +
-    @Shadow private BlockPos blockPos; +
-    @Shadow protected boolean firstUpdate; +
-    @Shadow public float fallDistance; +
-    @Shadow protected boolean touchingWater; +
-    protected boolean touchingFabricFlowableFluid; +
- +
-    @Shadow @Nullable public abstract Entity getVehicle()+
-    @Shadow public abstract boolean updateMovementInFluid(Tag<Fluid> tag, double d); +
-    @Shadow public abstract void extinguish(); +
-    //@Shadow protected abstract void onSwimmingStart(); +
-    @Shadow public abstract void setSwimming(boolean swimming); +
-    @Shadow public abstract boolean isRegionUnloaded(); +
-    @Shadow public abstract void playSound(SoundEvent sound, float volume, float pitch); +
-    @Shadow public abstract double getX(); +
-    @Shadow public abstract double getY(); +
-    @Shadow public abstract double getZ(); +
- +
- +
-    @Inject(method = "checkWaterState()V", +
-            at = @At("HEAD"), +
-            cancellable = true) +
-    void checkWaterState(CallbackInfo ci) { +
-        if (this.getVehicle() instanceof BoatEntity) { +
- +
-            //If the player is on boat it doesn't touch the fluid +
-            this.touchingWater = false; //This is added for compatibility with water. +
-            this.touchingFabricFlowableFluid = false; +
- +
-            ci.cancel(); +
-        } else { +
- +
-            //Gets the fluid strength +
-            double fluidStrength = getFluidStrength(); +
- +
-            //Triggers the movement in the fluid +
-            if (fluidStrength != -1 && this.updateMovementInFluid(FabricFluidTags.FABRIC_FLUIDS, fluidStrength)) { +
-                if (!this.touchingFabricFlowableFluid && !this.firstUpdate) { +
-                    //If the player has jumped on the fluid, or touched the fluid for the first fime, executes the touched method below +
-                    this.onFabricFlowableFluidTouched(); +
-                    //this.onSwimmingStart(); +
-                } +
- +
-                //This prevents fall damage when hitting the fluid (like water). +
-                this.fallDistance = 0.0F; +
- +
-                //When the player is on the fluid set touching to true. +
-                this.touchingFabricFlowableFluid = true; +
-                this.touchingWater = true; //This is added for compatibility with water. +
- +
-                //This extinguish the fire from the player (like water). +
-                this.extinguish(); +
- +
-                ci.cancel(); +
-            } +
-            else { +
-                //When the player leaved the fluid (or jumped from it) set touching to false. +
-                this.touchingFabricFlowableFluid = false; +
-            } +
-        } +
-    } +
- +
-    /** +
-     * This is not very important, will simply disable swimming on the fluid for now. +
-     */ +
-    @Inject(method = "updateSwimming()V", +
-            at = @At("HEAD"), +
-            cancellable = true) +
-    public void updateSwimming(CallbackInfo ci) { +
-        if (this.touchingFabricFlowableFluid) { +
-            //Disable swimming +
-            setSwimming(false); +
- +
-            ci.cancel(); +
-        } +
-    } +
- +
-    /** +
-     * Get the fluid pushing strength from the fluid. +
-     */ +
-    private double getFluidStrength() { +
-        if (this.isRegionUnloaded()) { +
-            return -1; +
-        } else { +
-            FluidState fluidState = this.world.getFluidState(this.blockPos); +
-            if (fluidState.getFluid() instanceof FabricFlowableFluid fluid) { +
-                return fluid.getStrength(); +
-            } +
-            else return -1; +
-        } +
-    } +
- +
-    /** +
-     * Executed when the player touches the fluid for the first time, or jumps on it. +
-     */ +
-    private void onFabricFlowableFluidTouched() { +
-        FluidState fluidState = this.world.getFluidState(this.blockPos); +
-        if (fluidState.getFluid() instanceof FabricFlowableFluid fluid) { +
- +
-            //Gets and play the splash sound +
-            fluid.getSplashSound().ifPresent(soundEvent -> this.playSound(soundEvent, 1f, 1f)); +
- +
-            //Execute the onSplash event +
-            fluid.onSplash(this.world, new Vec3d(this.getX(), this.getY(), this.getZ()), (Entity)(Object)this); +
-        } +
-    } +
-+
-</code>+
  
tutorial/fluids.1634687052.txt.gz · Last modified: 2021/10/19 23:44 by salvopelux