tutorial:fluids
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
tutorial:fluids [2019/09/24 15:02] – code style alexiy | tutorial:fluids [2022/08/17 22:38] – Fixed small typos clomclem | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Creating a fluid ====== | ====== Creating a fluid ====== | ||
+ | |||
===== Overview ===== | ===== 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. | 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 ===== | ===== Making an abstract fluid ===== | ||
- | Vanilla fluids extend | + | Vanilla fluids extend |
- | <code java> | + | <yarncode |
- | public abstract class BasicFluid | + | public abstract class TutorialFluid |
- | { | + | /** |
- | /** | + | * @return |
- | | + | */ |
- | | + | @Override |
- | @Override | + | public |
- | | + | return |
- | | + | } |
- | return | + | |
- | } | + | |
- | | + | /** |
- | @Override | + | * @return whether the fluid is infinite like water |
- | protected | + | */ |
- | | + | @Override |
- | return | + | protected |
- | } | + | return |
+ | } | ||
- | | + | /** |
- | | + | * Perform actions when the fluid flows into a replaceable block. Water drops |
- | | + | * the block' |
- | | + | */ |
- | @Override | + | @Override |
- | | + | protected void method_15730(class_1936 world, class_2338 pos, class_2680 state) { |
+ | final class_2586 blockEntity = state.method_31709() ? world.method_8321(pos) : null; | ||
+ | class_2248.method_9610(state, | ||
+ | } | ||
- | | + | /** |
- | | + | * Lava returns true if it's FluidState is above a certain height and the |
- | | + | * Fluid is Water. |
- | | + | * |
- | @Override | + | * @return |
- | protected | + | */ |
+ | @Override | ||
+ | protected | ||
+ | return false; | ||
+ | } | ||
- | | + | /** |
- | | + | * Possibly related to the distance checks for flowing into nearby holes? |
- | | + | * Water returns 4. Lava returns 2 in the Overworld and 4 in the Nether. |
- | | + | */ |
- | @Override | + | @Override |
- | | + | protected int method_15733(class_4538 worldView) { |
+ | return 4; | ||
+ | } | ||
- | | + | /** |
- | | + | * Water returns 1. Lava returns 2 in the Overworld and 1 in the Nether. |
- | * @return still static instance of this fluid | + | */ |
- | */ | + | @Override |
- | @Override | + | protected int method_15739(class_4538 worldView) { |
- | | + | return 1; |
+ | } | ||
- | // how much does the height of the fluid block decreases | + | /** |
- | @Override | + | * Water returns 5. Lava returns 30 in the Overworld and 10 in the Nether. |
- | protected int getLevelDecreasePerBlock(ViewableWorld world) | + | */ |
- | { | + | @Override |
- | return 1; | + | public int method_15789(class_4538 worldView) { |
- | } | + | return 5; |
- | + | } | |
- | | + | |
- | | + | |
- | * @return update rate | + | |
- | */ | + | |
- | @Override | + | |
- | public int getTickRate(ViewableWorld world) | + | |
- | | + | |
- | return 5; | + | |
- | } | + | |
- | + | ||
- | @Override | + | |
- | protected float getBlastResistance() | + | |
- | { | + | |
- | return 100; | + | |
- | } | + | |
- | + | ||
- | // this seems to determine fluid' | + | |
- | @Override | + | |
- | protected int method_15733(ViewableWorld world) | + | |
- | { | + | |
- | return 4; | + | |
- | } | + | |
- | + | ||
- | // I don't know what this does, but it's present in the water fluid | + | |
- | @Override | + | |
- | protected void beforeBreakingBlock(IWorld world, BlockPos blockPos, BlockState blockState) { | + | |
- | BlockEntity blockEntity = blockState.getBlock().hasBlockEntity() ? world.getBlockEntity(blockPos) : null; | + | |
- | Block.dropStacks(blockState, | + | |
- | } | + | |
- | + | ||
- | // also don't know what it does | + | |
- | public boolean method_15777(FluidState fluidState, BlockView blockView, BlockPos blockPos, Fluid fluid, Direction direction) { | + | |
- | return direction == Direction.DOWN; | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * | + | |
- | * @return is given fluid instance of this fluid? | + | |
- | */ | + | |
- | @Override | + | |
- | public abstract boolean matchesType(Fluid fluid); | + | |
+ | /** | ||
+ | * Water and Lava both return 100.0F. | ||
+ | */ | ||
+ | @Override | ||
+ | protected float method_15784() { | ||
+ | return 100.0F; | ||
+ | } | ||
} | } | ||
- | </code> | + | </yarncode> |
===== Implementation ===== | ===== Implementation ===== | ||
- | Now let's make an actual fluid; it will have a //still// and //flowing// variants; will name it "Acid": | + | 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. |
- | <code java> | + | <yarncode |
- | public abstract class Acid extends | + | public abstract class AcidFluid |
- | { | + | @Override |
- | @Override | + | public |
- | public | + | return |
- | { | + | } |
- | return null; | + | |
- | } | + | |
- | @Override | + | |
- | protected BlockState toBlockState(FluidState var1) | + | |
- | | + | |
- | return | + | |
- | } | + | |
- | | + | @Override |
- | public | + | public |
- | | + | return |
- | return | + | } |
- | } | + | |
- | | + | @Override |
- | public | + | public |
- | | + | return |
- | return | + | } |
- | } | + | |
- | | + | @Override |
- | | + | protected class_2680 method_15790(class_3610 fluidState) { |
- | | + | return |
- | return | + | } |
- | } | + | |
- | // still acid | + | public static class Flowing |
- | | + | @Override |
- | { | + | protected void method_15775(class_2689.class_2690< |
+ | super.method_15775(builder); | ||
+ | builder.method_11667(field_15900); | ||
+ | } | ||
- | | + | @Override |
- | public | + | public |
- | | + | return |
- | return | + | } |
- | } | + | |
- | /** | + | @Override |
- | * @return height of the fluid block | + | public |
- | */ | + | return |
- | | + | } |
- | public | + | } |
- | | + | |
- | return | + | |
- | } | + | |
- | } | + | |
- | // flowing acid | + | public static class Still extends |
- | | + | @Override |
- | { | + | public int method_15779(class_3610 fluidState) |
+ | return 8; | ||
+ | } | ||
- | | + | @Override |
- | public boolean | + | public boolean |
- | | + | return |
- | return | + | } |
- | } | + | } |
+ | } | ||
+ | </ | ||
- | /** | + | Next, we'll make static instances |
- | * @return height | + | |
- | */ | + | |
- | @Override | + | |
- | public int getLevel(FluidState fluidState) | + | |
- | { | + | |
- | return fluidState.get(LEVEL); | + | |
- | } | + | |
- | | + | < |
- | | + | public static class_3609 STILL_ACID; |
- | | + | public static class_3609 FLOWING_ACID; |
- | super.appendProperties(stateFactoryBuilder); | + | public static class_1792 ACID_BUCKET; |
- | | + | |
- | | + | @Override |
- | } | + | public |
+ | STILL_ACID = class_2378.method_10230(class_2378.field_11154, | ||
+ | FLOWING_ACID = class_2378.method_10230(class_2378.field_11154, | ||
+ | ACID_BUCKET = class_2378.method_10230(class_2378.field_11142, | ||
+ | | ||
+ | |||
+ | // ... | ||
} | } | ||
- | </code> | + | |
+ | // ... | ||
+ | </yarncode> | ||
- | Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your **ModInitializer**: | + | To make a custom fluid behave |
- | + | <code json [enable_line_numbers=" | |
- | <code java> | + | |
- | + | ||
- | + | ||
- | public static Acid stillAcid; | + | |
- | public static Acid flowingAcid; | + | |
- | + | ||
- | public static BucketItem acidBucket; | + | |
- | + | ||
- | @Override | + | |
- | public void onInitialize() | + | |
- | { | + | |
- | + | ||
- | stillAcid = Registry.register(Registry.FLUID, | + | |
- | flowingAcid = Registry.register(Registry.FLUID, | + | |
- | + | ||
- | acidBucket = new BucketItem(stillAcid, | + | |
- | Registry.register(Registry.ITEM, | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | To make the custom fluid behave like water or lava, you must add it to a corresponding fluid tag: make a file "data/ | + | |
- | <code json> | + | |
{ | { | ||
- | | + | " |
- | " | + | " |
- | "modid:acid_still", | + | [ |
- | "modid:acid_flowing" | + | "your_mod_id:acid", |
- | ] | + | "your_mod_id:flowing_acid" |
+ | ] | ||
} | } | ||
</ | </ | ||
- | ==== 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. |
- | <code java> | + | <yarncode |
- | public | + | public |
- | { | + | |
- | public BaseFluidBlock(BaseFluid fluid, Settings settings) | + | |
- | { | + | |
- | super(fluid, | + | |
- | } | + | |
- | } | + | |
- | </ | + | |
- | Now make a static block instance: | + | @Override |
+ | public void onInitialize() { | ||
+ | ACID = class_2378.method_10230(class_2378.field_11146, | ||
+ | |||
+ | // ... | ||
+ | } | ||
+ | </ | ||
- | <code java> | + | Now that we have these static |
- | ... | + | |
- | + | ||
- | public | + | |
- | | + | < |
- | public | + | public abstract class AcidFluid extends TutorialFluid { |
- | | + | @Override |
- | + | public | |
- | ... | + | return TutorialMod.STILL_ACID; |
- | + | } | |
- | acid = new BaseFluidBlock(stillAcid, FabricBlockSettings.of(Material.WATER).dropsNothing().build()); | + | |
- | Registry.register(Registry.BLOCK, new Identifier(MODID, " | + | @Override |
- | } | + | public class_3611 method_15750() { |
- | </ | + | return TutorialMod.FLOWING_ACID; |
+ | } | ||
+ | |||
+ | @Override | ||
+ | public class_1792 method_15774() { | ||
+ | return TutorialMod.ACID_BUCKET; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected class_2680 method_15790(class_3610 fluidState) { | ||
+ | // method_15741 converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses | ||
+ | return TutorialMod.ACID.method_9564().method_11657(class_2741.field_12538, method_15741(fluidState)); | ||
+ | } | ||
- | Now when we have these static | + | public |
+ | @Override | ||
+ | protected void method_15775(class_2689.class_2690< | ||
+ | super.method_15775(builder); | ||
+ | builder.method_11667(field_15900); | ||
+ | } | ||
- | <code java> | + | @Override |
- | public abstract class Acid extends BasicFluid | + | public |
- | { | + | return fluidState.method_11654(field_15900); |
- | | + | } |
- | public | + | |
- | | + | |
- | return | + | |
- | } | + | |
- | + | ||
- | @Override | + | |
- | protected BlockState toBlockState(FluidState | + | |
- | { | + | |
- | // | + | |
- | return [ModInitializer].acid.getDefaultState().with(FluidBlock.LEVEL, | + | |
- | } | + | |
- | | + | @Override |
- | public | + | public |
- | | + | return |
- | return | + | } |
- | } | + | } |
- | | + | public static class Still extends AcidFluid { |
- | public | + | @Override |
- | | + | public |
- | return | + | return |
- | } | + | } |
- | | + | @Override |
- | public boolean | + | public boolean |
- | | + | return |
- | return | + | } |
- | } | + | } |
- | + | } | |
- | ... | + | </yarncode> |
- | | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | Now we can assert that the Acid class is complete. | + | |
===== Rendering setup ===== | ===== Rendering setup ===== | ||
+ | For your fluids to have textures or be tinted with a color, you will need to register a '' | ||
- | Time to do client-side things. In your **ClientModInitializer** you need to specify locations of sprites for your fluids and define their rendering. I will reuse water textures and just change the color applied to them. | + | < |
+ | public class TutorialModClient implements | ||
- | <code java> | + | @Override |
- | | + | public void onInitializeClient() { |
- | public void onInitializeClient() | + | FluidRenderHandlerRegistry.INSTANCE.register(TutorialMod.STILL_ACID, |
- | | + | new class_2960("minecraft:block/ |
- | | + | new class_2960("minecraft:block/ |
- | | + | 0x4CC248 |
- | // here I tell to use only 16x16 area of the water texture | + | )); |
- | FabricSprite stillAcidSprite = new FabricSprite(stillSpriteLocation, 16, 16); | + | |
- | // same, but 32 | + | |
- | | + | |
- | + | ||
- | // adding the sprites to the block texture atlas | + | |
- | ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register((spriteAtlasTexture, | + | |
- | registry.register(stillAcidSprite); | + | |
- | registry.register(dynamicAcidSprite); | + | |
- | }); | + | |
- | // this renderer is responsible for drawing fluids in a world | + | BlockRenderLayerMap.INSTANCE.putFluids(class_1921.method_23583(), TutorialMod.STILL_ACID, TutorialMod.FLOWING_ACID); |
- | FluidRenderHandler acidRenderHandler = new FluidRenderHandler() | + | |
- | { | + | |
- | // return the sprites: still sprite goes first into the array, flowing/ | + | |
- | @Override | + | |
- | public Sprite[] getFluidSprites(ExtendedBlockView extendedBlockView, BlockPos blockPos, FluidState fluidState) | + | |
- | { | + | |
- | return new Sprite[]{stillAcidSprite, | + | |
- | } | + | |
- | | + | //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(" |
- | }; | + | //}); |
- | | + | // ... |
+ | } | ||
+ | } | ||
+ | </ | ||
- | FluidRenderHandlerRegistry.INSTANCE.register([ModInitializer].stillAcid, acidRenderHandler); | + | If you want to use your own fluid textures, you can refer to vanilla' |
- | FluidRenderHandlerRegistry.INSTANCE.register([ModInitializer].flowingAcid, | + | |
- | </code> | + | |
- | Then what's left to do is to create | + | ===== Generation in the world ===== |
+ | To make lakes of acid generate in the world, you can create | ||
- | ===== Generation in a world ===== | + | <code java [enable_line_numbers=" |
+ | public static LakeFeature ACID_LAKE; | ||
- | To make acid lakes generate in the world, you can use **net.minecraft.world.gen.feature.LakeFeature**, | + | @Override |
- | <code java> | + | public void onInitialize() { |
- | | + | ACID_LAKE |
- | | + | |
+ | // 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))) | ||
+ | ); | ||
+ | } | ||
+ | </ | ||
- | </ | ||
- | Then put it into desired biomes to generate: | ||
- | <code java> | ||
- | // I tell it to generate like water lakes, with a rarity of 40 (the higher is the number, the lesser is the generation chance): | ||
- | Biomes.FOREST.addFeature(GenerationStep.Feature.LOCAL_MODIFICATIONS, | ||
- | </ | ||
- | This is the end of the tutorial. | ||
tutorial/fluids.txt · Last modified: 2023/05/04 11:31 by solidblock