tutorial:fluids
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| tutorial:fluids [2021/10/20 20:49] – fixed grammar errors, added expert warning to advanced customization. salvopelux | tutorial: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 '' | + | Vanilla fluids extend '' |
| - | <yarncode | + | <code java [enable_line_numbers=" |
| - | public abstract class TutorialFluid extends | + | public abstract class TutorialFluid extends |
| /** | /** | ||
| * @return whether the given fluid an instance of this fluid | * @return whether the given fluid an instance of this fluid | ||
| */ | */ | ||
| @Override | @Override | ||
| - | public boolean | + | public boolean |
| - | 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 |
| */ | */ | ||
| @Override | @Override | ||
| - | protected boolean | + | protected boolean |
| 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' | * the block' | ||
| */ | */ | ||
| @Override | @Override | ||
| - | protected void method_15730(class_1936 world, class_2338 | + | protected void beforeDestroyingBlock(LevelAccessor level, BlockPos |
| - | final | + | final |
| - | class_2248.method_9610(state, | + | Block.dropResources(state, |
| } | } | ||
| /** | /** | ||
| - | * Lava returns true if its FluidState is above a certain height and the | + | * Lava returns true if it' |
| * Fluid is Water. | * Fluid is Water. | ||
| * | * | ||
| Line 41: | Line 41: | ||
| */ | */ | ||
| @Override | @Override | ||
| - | protected boolean | + | protected boolean |
| 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 | + | * Water returns 1. Lava returns 2 in the overworld |
| + | * @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 | + | <code java [enable_line_numbers=" |
| public abstract class AcidFluid extends TutorialFluid { | public abstract class AcidFluid extends TutorialFluid { | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return | + | return |
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| return YOUR_FLOWING_FLUID_HERE; | return YOUR_FLOWING_FLUID_HERE; | ||
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| return YOUR_BUCKET_ITEM_HERE; | return YOUR_BUCKET_ITEM_HERE; | ||
| } | } | ||
| @Override | @Override | ||
| - | protected | + | protected |
| - | return YOUR_FLUID_BLOCK_HERE.method_9564().method_11657(class_2741.field_12538, method_15741(fluidState)); | + | return YOUR_FLUID_BLOCK_HERE.defaultBlockState().setValue(BlockStateProperties.LEVEL, getLegacyLevel(state)); |
| } | } | ||
| public static class Flowing extends AcidFluid { | public static class Flowing extends AcidFluid { | ||
| @Override | @Override | ||
| - | protected void method_15775(class_2689.class_2690<class_3611, class_3610> builder) { | + | protected void createFluidStateDefinition(StateDefinition.Builder<Fluid, FluidState> builder) { |
| - | super.method_15775(builder); | + | super.createFluidStateDefinition(builder); |
| - | builder.method_11667(field_15900); | + | builder.add(LEVEL); |
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return | + | return |
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return | + | return |
| } | } | ||
| } | } | ||
| Line 125: | Line 127: | ||
| public static class Still extends AcidFluid { | public static class Still extends AcidFluid { | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return | + | return |
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return | + | return |
| } | } | ||
| } | } | ||
| } | } | ||
| - | </yarncode> | + | </code> |
| Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your '' | Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your '' | ||
| - | <yarncode | + | <code java [enable_line_numbers=" |
| - | public static | + | public static |
| - | public static | + | public static |
| - | public static | + | public static |
| @Override | @Override | ||
| public void onInitialize() { | public void onInitialize() { | ||
| - | STILL_ACID | + | ACID_SOURCE |
| - | FLOWING_ACID = class_2378.method_10230(class_2378.field_11154, new class_2960(MOD_ID, " | + | FLOWING_ACID = |
| - | ACID_BUCKET = class_2378.method_10230(class_2378.field_11142, new class_2960(MOD_ID, " | + | ACID_BUCKET = Registry.register(BuiltInRegistries.ITEM, Identifier.fromNamespaceAndPath(" |
| - | new class_1755(STILL_ACID, new class_1792.class_1793().method_7896(class_1802.field_8550).method_7889(1))); | + | new BucketItem(ACID_SOURCE, |
| // ... | // ... | ||
| 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 '' | + | To make a custom fluid behave more like water or lava, you must add it to a corresponding fluid tag: For water, make a '' |
| <code json [enable_line_numbers=" | <code json [enable_line_numbers=" | ||
| { | { | ||
| Line 163: | Line 165: | ||
| " | " | ||
| [ | [ | ||
| - | "your_mod_id: | + | "tutorial: |
| - | "your_mod_id: | + | "tutorial: |
| ] | ] | ||
| } | } | ||
| 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. '' | + | Next we need to create a block which will represent acid in the world. '' |
| - | <yarncode | + | <code java [enable_line_numbers=" |
| - | public static | + | public static |
| @Override | @Override | ||
| public void onInitialize() { | public void onInitialize() { | ||
| - | ACID = class_2378.method_10230(class_2378.field_11146, new class_2960(MOD_ID, " | + | ACID = Registry.register(BuiltInRegistries.BLOCK, Identifier.fromNamespaceAndPath(" |
| // ... | // ... | ||
| } | } | ||
| - | </yarncode> | + | </code> |
| Now that we have these static objects, we can go back to '' | Now that we have these static objects, we can go back to '' | ||
| - | <yarncode | + | <code java [enable_line_numbers=" |
| public abstract class AcidFluid extends TutorialFluid { | public abstract class AcidFluid extends TutorialFluid { | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return TutorialMod.STILL_ACID; | + | return TutorialMod.ACID_SOURCE; |
| } | } | ||
| - | + | ||
| @Override | @Override | ||
| - | public | + | public |
| return TutorialMod.FLOWING_ACID; | return TutorialMod.FLOWING_ACID; | ||
| } | } | ||
| - | + | ||
| @Override | @Override | ||
| - | public | + | public |
| return TutorialMod.ACID_BUCKET; | return TutorialMod.ACID_BUCKET; | ||
| } | } | ||
| - | + | ||
| @Override | @Override | ||
| - | protected | + | protected |
| - | // 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.LEVEL, getLegacyLevel(state)); |
| - | return TutorialMod.ACID.method_9564().method_11657(class_2741.field_12538, method_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_3611, class_3610> builder) { | + | protected void createFluidStateDefinition(StateDefinition.Builder<Fluid, FluidState> builder) { |
| - | super.method_15775(builder); | + | super.createFluidStateDefinition(builder); |
| - | builder.method_11667(field_15900); | + | builder.add(LEVEL); |
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return | + | return |
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return | + | return |
| } | } | ||
| } | } | ||
| Line 228: | Line 229: | ||
| public static class Still extends AcidFluid { | public static class Still extends AcidFluid { | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return | + | return |
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| - | return | + | return |
| } | } | ||
| } | } | ||
| - | } | + | } |
| - | </yarncode> | + | </code> |
| ===== Rendering setup ===== | ===== Rendering setup ===== | ||
| - | For your fluids to have textures or be tinted with a color, you will need to register a '' | + | For your fluids to have textures or be tinted with a color, you will need to register a '' |
| - | <yarncode | + | <code java [enable_line_numbers=" |
| + | @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, | + | FluidRenderHandlerRegistry.INSTANCE.register(TutorialMod.ACID_SOURCE, TutorialMod.FLOWING_ACID, |
| - | BlockRenderLayerMap.INSTANCE.putFluids(class_1921.method_23583(), | + | |
| - | // ... | + | BlockRenderLayerMap.putFluids(ChunkSectionLayer.TRANSLUCENT, TutorialMod.ACID_SOURCE, TutorialMod.FLOWING_ACID); |
| - | } | + | |
| - | + | ||
| - | public static void setupFluidRendering(final class_3611 still, final class_3611 flowing, final class_2960 textureFluidId, | + | |
| - | final class_2960 stillSpriteId = new class_2960(textureFluidId.method_12836(), " | + | |
| - | final class_2960 flowingSpriteId = new class_2960(textureFluidId.method_12836(), " | + | |
| - | + | ||
| - | // If they' | + | |
| - | ClientSpriteRegistryCallback.event(class_1059.field_5275).register((atlasTexture, | + | |
| - | 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(), | + | |
| - | + | ||
| - | 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< | + | |
| - | 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, | + | |
| - | FluidRenderHandlerRegistry.INSTANCE.register(flowing, | + | |
| } | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | If you want to use your own fluid textures, you can refer to vanilla' | ||
| - | |||
| - | ===== Generation in the world ===== | ||
| - | To make lakes of acid generate in the world, you can create a ''< | ||
| - | |||
| - | <code java [enable_line_numbers=" | ||
| - | public static LakeFeature ACID_LAKE; | ||
| - | |||
| - | @Override | ||
| - | public void onInitialize() { | ||
| - | ACID_LAKE = Registry.register(Registry.FEATURE, | ||
| - | |||
| - | // 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))) | ||
| - | ); | ||
| } | } | ||
| </ | </ | ||
| + | ===== 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 [EXPERT] ===== | + | The first texture |
| - | **First of all, this section is only for expert users, so you should | + | The second texture should be named YOUR_FLUID_NAME_flowing.png, and should have double |
| - | If you think that mixins are something good to eat... go learn more about mixins before! You can find more info on [[tutorial: | + | The third will be named YOUR_FLUID_NAME_overlay.png, and should not be animated. |
| - | + | ||
| - | You may notice that your custom fluid is very similar to water. | + | |
| - | In this section we'll cover some customizations, like adding custom fog and fog color, " | + | |
| - | + | ||
| - | Unfortunately, for now, there isn't a way to do this normally even with Fabric API, so we need to use mixins to change | + | |
| - | + | ||
| - | 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=" | <code java [enable_line_numbers=" | ||
| - | //Add in the main mod file | + | @Environment(EnvType.CLIENT) |
| - | public static final Tag< | + | public class TutorialModClient implements ClientModInitializer { |
| - | </ | + | |
| - | Then add all fluids to your custom tag for customization: | + | @Override |
| + | public void onInitializeClient() { | ||
| + | FluidRenderHandlerRegistry.INSTANCE.register(TutorialMod.ACID_SOURCE, | ||
| + | Identifier.fromNamespaceAndPath(" | ||
| + | Identifier.fromNamespaceAndPath(" | ||
| + | Identifier.fromNamespaceAndPath(" | ||
| - | ''/ | + | BlockRenderLayerMap.putFluids(ChunkSectionLayer.TRANSLUCENT, TutorialMod.ACID_SOURCE, TutorialMod.FLOWING_ACID); |
| - | + | } | |
| - | <code json [enable_line_numbers=" | + | |
| - | { | + | |
| - | " | + | |
| - | " | + | |
| - | [ | + | |
| - | " | + | |
| - | " | + | |
| - | ] | + | |
| } | } | ||
| </ | </ | ||
| - | ==== Adding fog customizations ==== | + | You can refer to vanilla' |
| - | To make things simpler and more customizable, | + | |
| - | + | ||
| - | <code java [enable_line_numbers=" | + | |
| - | 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); | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Now you must implement this interface to the previous example fluid class: | + | |
| - | + | ||
| - | <code java [enable_line_numbers=" | + | |
| - | 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 | + | |
| - | //if (entity instanceof LivingEntity && | + | |
| - | 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) { | + | |
| - | | + | |
| - | //if (entity instanceof LivingEntity && ((LivingEntity)entity).hasStatusEffect(StatusEffects.FIRE_RESISTANCE)) return 3.0f | + | |
| - | return 1.0f; | + | |
| - | } | + | |
| - | + | ||
| - | [...] | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | To make the game use these values we must use an inject mixin to two methods inside the '' | + | |
| - | + | ||
| - | * '' | + | |
| - | * '' | + | |
| - | + | ||
| - | <code java [enable_line_numbers=" | + | |
| - | @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 = " | + | |
| - | at = @At(" | + | |
| - | 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 = (fogColor >> 16 & 255) / 255f; | + | |
| - | green = (fogColor >> 8 & 255) / 255f; | + | |
| - | blue = (fogColor & 255) / 255f; | + | |
| - | + | ||
| - | //This is for compatibility, | + | |
| - | lastWaterFogColorUpdateTime = -1L; | + | |
| - | + | ||
| - | //Apply the color, then return. | + | |
| - | RenderSystem.clearColor(red, | + | |
| - | + | ||
| - | ci.cancel(); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | @Inject(method = " | + | |
| - | at = @At(" | + | |
| - | cancellable = true) | + | |
| - | private static void applyFog(Camera camera, BackgroundRenderer.FogType fogType, float viewDistance, | + | |
| - | + | ||
| - | //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(); | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | From 1.17 camera has not the '' | + | |
| - | + | ||
| - | Add a '' | + | |
| - | + | ||
| - | <code java [enable_line_numbers=" | + | |
| - | 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=" | + | |
| - | @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); | + | |
| - | } | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | ==== Adding push back, custom sounds and custom particles ==== | + | |
| - | For first, extend the previous | + | |
| - | + | ||
| - | Now it will be like this: | + | |
| - | + | ||
| - | <code java [enable_line_numbers=" | + | |
| - | 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< | + | |
| - | + | ||
| - | /** | + | |
| - | * Make things when the player splashes on the fluid (like jumping). | + | |
| - | */ | + | |
| - | void onSplash(World world, Vec3d pos, Entity entity); | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Add the new methods implementations to the fluid class: | + | |
| - | + | ||
| - | <code java [enable_line_numbers=" | + | |
| - | public abstract class AcidFluid extends TutorialFluid implements FabricFlowableFluid { | + | |
| - | + | ||
| - | [...] | + | |
| - | + | ||
| - | /** | + | |
| - | * Get the fluid pushing strength. (Water uses 0.014d, Lava uses 0.0023333333333333335d, | + | |
| - | */ | + | |
| - | @Override | + | |
| - | double getStrength() { | + | |
| - | return 0.014d; | + | |
| - | } | + | |
| - | + | ||
| - | /** | + | |
| - | * Get the fluid splash sound. | + | |
| - | */ | + | |
| - | @Override | + | |
| - | Optional< | + | |
| - | //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 where the player hitted the fluid. | + | |
| - | //entity is the entity that caused the splash event. | + | |
| - | world.addParticle(ParticleTypes.SMOKE, | + | |
| - | } | + | |
| - | + | ||
| - | [...] | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Now we must add a mixin to '' | + | |
| - | + | ||
| - | For now we will disable the swimming on the fluid, this will be covered in the future. | + | |
| - | + | ||
| - | <code java [enable_line_numbers=" | + | |
| - | @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< | + | |
| - | @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 = " | + | |
| - | at = @At(" | + | |
| - | cancellable = true) | + | |
| - | void checkWaterState(CallbackInfo ci) { | + | |
| - | if (this.getVehicle() instanceof BoatEntity) { | + | |
| - | + | ||
| - | //If the player is on a boat it doesn' | + | |
| - | 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, | + | |
| - | 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 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 = " | + | |
| - | at = @At(" | + | |
| - | 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, | + | |
| - | + | ||
| - | //Execute the onSplash event | + | |
| - | fluid.onSplash(this.world, | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | </ | + | |
tutorial/fluids.1634762954.txt.gz · Last modified: 2021/10/20 20:49 by salvopelux