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/19 23:54] – Fixed some typos. salvopelux | tutorial:fluids [2023/05/04 11:31] (current) – [Rendering setup] solidblock | ||
---|---|---|---|
Line 17: | Line 17: | ||
/** | /** | ||
- | * @return whether the fluid infinite like water | + | * @return whether the fluid is infinite |
*/ | */ | ||
@Override | @Override | ||
Line 25: | Line 25: | ||
/** | /** | ||
- | * 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' | ||
*/ | */ | ||
Line 35: | Line 35: | ||
/** | /** | ||
- | * 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 146: | Line 146: | ||
@Override | @Override | ||
public void onInitialize() { | public void onInitialize() { | ||
- | STILL_ACID = class_2378.method_10230(class_2378.field_11154, new class_2960(MOD_ID, " | + | STILL_ACID = class_2378.method_10230(class_7923.field_41173, new class_2960(" |
- | FLOWING_ACID = class_2378.method_10230(class_2378.field_11154, new class_2960(MOD_ID, " | + | FLOWING_ACID = class_2378.method_10230(class_7923.field_41173, new class_2960(" |
- | ACID_BUCKET = class_2378.method_10230(class_2378.field_11142, new class_2960(MOD_ID, " | + | ACID_BUCKET = class_2378.method_10230(class_7923.field_41178, new class_2960(" |
new class_1755(STILL_ACID, | new class_1755(STILL_ACID, | ||
Line 163: | Line 163: | ||
" | " | ||
[ | [ | ||
- | "your_mod_id: | + | "tutorial: |
- | "your_mod_id: | + | "tutorial: |
] | ] | ||
} | } | ||
Line 177: | Line 177: | ||
@Override | @Override | ||
public void onInitialize() { | public void onInitialize() { | ||
- | ACID = class_2378.method_10230(class_2378.field_11146, new class_2960(MOD_ID, | + | ACID = class_2378.method_10230(class_7923.field_41175, new class_2960(MOD_ID, |
// ... | // ... | ||
Line 241: | Line 241: | ||
===== 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 '' |
< | < | ||
+ | @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, | + | FluidRenderHandlerRegistry.INSTANCE.register(TutorialMod.STILL_ACID, |
+ | new class_2960(" | ||
+ | new class_2960("minecraft: | ||
+ | 0x4CC248 | ||
+ | )); | ||
BlockRenderLayerMap.INSTANCE.putFluids(class_1921.method_23583(), | BlockRenderLayerMap.INSTANCE.putFluids(class_1921.method_23583(), | ||
+ | |||
+ | //if you want to use custom textures they needs to be registered. | ||
+ | //In this example this is unnecessary because the vanilla water textures are already registered. | ||
+ | //To register your custom textures use this method. | ||
+ | // | ||
+ | // registry.register(new Identifier(" | ||
+ | // registry.register(new Identifier(" | ||
+ | //}); | ||
// ... | // ... | ||
- | } | ||
- | |||
- | 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, | ||
} | } | ||
} | } | ||
Line 309: | Line 273: | ||
===== Generation in the world ===== | ===== Generation in the world ===== | ||
- | To make lakes of acid generate in the world, you can create a ''< | + | TODO Update |
- | + | ||
- | <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))) | + | |
- | ); | + | |
- | } | + | |
</ | </ | ||
- | |||
- | ===== Advanced customization ===== | ||
- | You may be noticed that your custom fluid is too much similar to water. | ||
- | In this section we'll cover some customizations, | ||
- | |||
- | Unfortunately, | ||
- | |||
- | To start, remove the fluid from the water tag we added previously, and add a custom tag for custom behaviours: | ||
- | |||
- | <code java [enable_line_numbers=" | ||
- | //Add in the main mod file | ||
- | public static final Tag< | ||
- | </ | ||
- | |||
- | Then set all fluids that you want to customize to your custom tag: | ||
- | |||
- | ''/ | ||
- | |||
- | <code json [enable_line_numbers=" | ||
- | { | ||
- | " | ||
- | " | ||
- | [ | ||
- | " | ||
- | " | ||
- | ] | ||
- | } | ||
- | </ | ||
- | |||
- | ==== Adding fog customizations ==== | ||
- | 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 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; | ||
- | } | ||
- | | ||
- | [...] | ||
- | } | ||
- | </ | ||
- | |||
- | 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(); | ||
- | } | ||
- | </ | ||
- | |||
- | 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 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, | ||
- | } | ||
- | | ||
- | [...] | ||
- | } | ||
- | </ | ||
- | |||
- | 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.1634687662.txt.gz · Last modified: 2021/10/19 23:54 by salvopelux