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 11:43] – lake generation alexiy | tutorial:fluids [2020/01/18 15:40] – Update tutorial to 1.15.1 + some cleanup virtuoel | ||
---|---|---|---|
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> | + | <code java [enable_line_numbers=" |
- | public abstract class BasicFluid | + | public abstract class TutorialFluid |
{ | { | ||
- | | + | /** |
- | | + | * @return |
- | | + | */ |
- | @Override | + | @Override |
- | | + | public |
- | { | + | { |
- | return | + | return |
- | } | + | } |
- | + | ||
- | // make it transparent | + | /** |
- | @Override | + | * @return |
- | protected BlockRenderLayer getRenderLayer() | + | */ |
- | { | + | @Override |
- | return BlockRenderLayer.TRANSLUCENT; | + | protected boolean isInfinite() |
- | } | + | { |
- | + | return false; | |
- | /** | + | } |
- | * | + | |
- | * @return | + | /** |
- | | + | * Perform actions when fluid flows into a replaceable block. Water drops |
- | @Override | + | * the block' |
- | | + | */ |
- | + | @Override | |
- | /** | + | protected |
- | | + | { |
- | | + | final BlockEntity blockEntity = state.getBlock().hasBlockEntity() ? world.getBlockEntity(pos) : null; |
- | | + | Block.dropStacks(state, |
- | @Override | + | } |
- | protected | + | |
- | + | /** | |
- | /** | + | * Lava returns true if its FluidState is above a certain height and the |
- | | + | * Fluid is Water. |
- | | + | * |
- | */ | + | * @return |
- | @Override | + | */ |
- | public abstract | + | @Override |
- | + | protected boolean method_15777(FluidState fluidState, BlockView blockView, BlockPos blockPos, | |
- | /** | + | { |
- | * | + | return |
- | * @return | + | } |
- | | + | |
- | @Override | + | /** |
- | | + | * 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. | |
- | // how much does the height of the fluid block decreases | + | */ |
- | @Override | + | @Override |
- | protected int getLevelDecreasePerBlock(ViewableWorld var1) | + | protected int method_15733(WorldView worldView) |
- | { | + | { |
- | return | + | return 4; |
- | } | + | } |
- | + | ||
- | /** | + | /** |
- | | + | * Water returns 1. Lava returns 1 in the Overworld and 2 in the Nether. |
- | | + | */ |
- | | + | @Override |
- | @Override | + | protected |
- | | + | { |
- | { | + | return |
- | return 5; | + | } |
- | } | + | |
- | + | /** | |
- | @Override | + | * Water returns 5. Lava returns 30 in the Overworld and 10 in the Nether. |
- | protected float getBlastResistance() | + | */ |
- | { | + | @Override |
- | return 100; | + | public |
- | } | + | { |
- | + | return 5; | |
- | // this seems to determine fluid' | + | } |
- | @Override | + | |
- | | + | /** |
- | { | + | * Water and Lava both return 100.0F. |
- | return 4; | + | */ |
- | } | + | @Override |
- | + | protected float getBlastResistance() | |
- | // I don't know what this does, but it's present | + | { |
- | @Override | + | return |
- | protected | + | } |
- | | + | |
- | Block.dropStacks(blockState_1, | + | |
- | } | + | |
- | + | ||
- | // also don't know what it does | + | |
- | public boolean method_15777(FluidState fluidState_1, | + | |
- | return | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | | + | |
- | * @return is given fluid instance of this fluid? | + | |
- | */ | + | |
- | @Override | + | |
- | public | + | |
- | + | ||
- | /** | + | |
- | | + | |
- | | + | |
- | @Override | + | |
- | | + | |
- | { | + | |
- | return | + | |
- | } | + | |
} | } | ||
</ | </ | ||
===== 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' |
- | <code java> | + | <code java [enable_line_numbers=" |
- | public abstract class Acid extends | + | public abstract class AcidFluid |
{ | { | ||
- | | + | @Override |
- | public Item getBucketItem() | + | public Fluid getStill() |
- | { | + | { |
- | return | + | return < |
- | } | + | } |
- | @Override | + | |
- | protected BlockState toBlockState(FluidState | + | @Override |
- | { | + | public Fluid getFlowing() |
- | return | + | { |
- | } | + | return < |
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Item getBucketItem() | ||
+ | { | ||
+ | return | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected BlockState toBlockState(FluidState | ||
+ | { | ||
+ | // method_15741 converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses | ||
+ | return | ||
+ | } | ||
+ | |||
+ | public static class Flowing extends AcidFluid | ||
+ | { | ||
+ | @Override | ||
+ | protected void appendProperties(StateManager.Builder< | ||
+ | { | ||
+ | super.appendProperties(builder); | ||
+ | builder.add(LEVEL); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public int getLevel(FluidState fluidState) | ||
+ | { | ||
+ | return fluidState.get(LEVEL); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public boolean isStill(FluidState fluidState) | ||
+ | { | ||
+ | return false; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public static class Still extends AcidFluid | ||
+ | { | ||
+ | @Override | ||
+ | public int getLevel(FluidState fluidState) | ||
+ | { | ||
+ | return 8; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public boolean isStill(FluidState fluidState) | ||
+ | { | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
- | @Override | + | Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your '' |
- | public Fluid getFlowing() | + | |
- | { | + | |
- | return null; | + | |
- | } | + | |
- | @Override | + | <code java [enable_line_numbers=" |
- | | + | // ... |
- | { | + | |
- | return null; | + | |
- | } | + | |
- | @Override | + | public static BaseFluid STILL_ACID; |
- | public | + | public |
- | { | + | |
- | return false; | + | |
- | } | + | |
- | // still acid | + | public static |
- | | + | |
- | { | + | |
- | @Override | + | // ... |
- | public boolean isStill(FluidState fluidState) | + | |
- | { | + | |
- | return true; | + | |
- | } | + | |
- | /** | + | @Override |
- | * @return height of the fluid block | + | public |
- | */ | + | { |
- | | + | // ... |
- | public | + | |
- | { | + | STILL_ACID = Registry.FLUID.add(new Identifier(MOD_ID, |
- | | + | |
- | } | + | FLOWING_ACID = Registry.FLUID.add(new Identifier(MOD_ID, |
- | } | + | |
+ | ACID_BUCKET = Registry.ITEM.add(new Identifier(MOD_ID, | ||
+ | |||
+ | // ... | ||
+ | } | ||
- | | + | // ... |
- | | + | </ |
- | { | + | |
- | @Override | + | To make a custom fluid behave more like water or lava, you must add it to a corresponding fluid tag: For water, make a '' |
- | public boolean isStill(FluidState fluidState) | + | <code json [enable_line_numbers=" |
- | { | + | { |
- | return false; | + | " |
- | } | + | " |
- | + | [ | |
- | | + | " |
- | * @return height | + | " |
- | */ | + | ] |
- | @Override | + | |
- | public int getLevel(FluidState fluidState) | + | |
- | { | + | |
- | | + | |
- | } | + | |
- | + | ||
- | | + | |
- | protected void appendProperties(StateFactory.Builder< | + | |
- | { | + | |
- | super.appendProperties(stateFactory$Builder_1); | + | |
- | stateFactory$Builder_1.add(LEVEL); | + | |
- | } | + | |
- | } | + | |
} | } | ||
</ | </ | ||
- | Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your **ModInitializer**: | + | ===== Making a fluid block ===== |
+ | Next we need to create a block which will represent acid in the world. '' | ||
- | <code java> | + | <code java [enable_line_numbers=" |
+ | // ... | ||
- | + | public static | |
- | | + | |
- | public static Acid flowingAcid; | + | |
- | + | ||
- | public static BucketItem acidBucket; | + | |
- | @Override | + | // ... |
- | public void onInitialize() | + | |
- | { | + | |
- | + | ||
- | stillAcid = Registry.register(Registry.FLUID,new Identifier(MODID," | + | |
- | flowingAcid = Registry.register(Registry.FLUID, | + | |
- | + | ||
- | acidBucket=new BucketItem(stillAcid, | + | |
- | Registry.register(Registry.ITEM, | + | |
- | } | + | |
- | </ | + | |
- | ==== Making a fluid block ==== | + | @Override |
- | Next we need to create a block which will represent acid in the world. **net.minecraft.block.FluidBlock** is the class we need to use, but for " | + | public |
- | + | ||
- | <code java> | + | |
- | public | + | |
{ | { | ||
- | public BaseFluidBlock(BaseFluid fluid, Settings settings) | + | // ... |
- | { | + | |
- | super(fluid, settings); | + | ACID = Registry.BLOCK.add(new Identifier(MOD_ID, " |
- | } | + | |
+ | // ... | ||
} | } | ||
- | </ | ||
- | Now make a static block instance: | + | // ... |
- | + | ||
- | <code java> | + | |
- | | + | |
- | + | ||
- | public static FluidBlock acid; | + | |
- | + | ||
- | @Override | + | |
- | public void onInitialize() | + | |
- | { | + | |
- | + | ||
- | ... | + | |
- | + | ||
- | acid=new BaseFluidBlock(stillAcid, | + | |
- | Registry.register(Registry.BLOCK, | + | |
- | } | + | |
</ | </ | ||
- | Now when we have these static objects, we go back to **Acid** class and complete | + | Now that we have these static objects, we can go back to '' |
- | <code java> | + | <code java [enable_line_numbers=" |
- | public abstract class Acid extends | + | public abstract class AcidFluid |
{ | { | ||
- | | + | @Override |
- | public | + | public |
- | { | + | { |
- | return | + | return |
- | } | + | } |
- | + | ||
- | @Override | + | @Override |
- | | + | public Fluid getFlowing() |
- | { | + | { |
- | // | + | return |
- | return [ModInitializer].acid.getDefaultState().with(FluidBlock.LEVEL, | + | } |
- | } | + | |
- | + | @Override | |
- | @Override | + | public |
- | | + | { |
- | { | + | return |
- | return | + | } |
- | } | + | |
- | + | @Override | |
- | @Override | + | protected BlockState toBlockState(FluidState fluidState) |
- | public | + | { |
- | { | + | // method_15741 converts the LEVEL_1_8 of the fluid state to the LEVEL_15 the fluid block uses |
- | return | + | return |
- | } | + | } |
- | + | ||
- | @Override | + | // ... |
- | | + | |
- | { | + | |
- | return | + | |
- | } | + | |
- | + | ||
- | ... | + | |
- | | + | |
} | } | ||
</ | </ | ||
- | |||
- | 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 | + | <code java [enable_line_numbers=" |
+ | public class TutorialModClient implements ClientModInitializer | ||
+ | { | ||
+ | // ... | ||
+ | |||
+ | @Override | ||
+ | public void onInitializeClient() | ||
+ | { | ||
+ | // ... | ||
+ | |||
+ | setupFluidRendering(TutorialMod.STILL_ACID, | ||
+ | BlockRenderLayerMap.INSTANCE.putFluids(RenderLayer.getTranslucent(), | ||
+ | |||
+ | // ... | ||
+ | } | ||
+ | |||
+ | // ... | ||
+ | |||
+ | public static void setupFluidRendering(final Fluid still, final Fluid flowing, final Identifier textureFluidId, | ||
+ | { | ||
+ | final Identifier stillSpriteId = new Identifier(textureFluidId.getNamespace(), | ||
+ | final Identifier flowingSpriteId = new Identifier(textureFluidId.getNamespace(), | ||
+ | |||
+ | // If they' | ||
+ | ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register((atlasTexture, | ||
+ | { | ||
+ | registry.register(stillSpriteId); | ||
+ | registry.register(flowingSpriteId); | ||
+ | }); | ||
+ | |||
+ | final Identifier fluidId = Registry.FLUID.getId(still); | ||
+ | final Identifier listenerId = new Identifier(fluidId.getNamespace(), | ||
+ | |||
+ | final Sprite[] fluidSprites = new Sprite[] { null, null }; | ||
+ | |||
+ | ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener() | ||
+ | { | ||
+ | @Override | ||
+ | public Identifier getFabricId() | ||
+ | { | ||
+ | return listenerId; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Get the sprites from the block atlas when resources are reloaded | ||
+ | */ | ||
+ | @Override | ||
+ | public void apply(ResourceManager resourceManager) | ||
+ | { | ||
+ | final Function< | ||
+ | fluidSprites[0] = atlas.apply(stillSpriteId); | ||
+ | fluidSprites[1] = atlas.apply(flowingSpriteId); | ||
+ | } | ||
+ | }); | ||
+ | |||
+ | // The FluidRenderer gets the sprites and color from a FluidRenderHandler during | ||
+ | final FluidRenderHandler renderHandler = new FluidRenderHandler() | ||
+ | { | ||
+ | @Override | ||
+ | public Sprite[] getFluidSprites(BlockRenderView view, BlockPos pos, FluidState state) | ||
+ | { | ||
+ | return fluidSprites; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public int getFluidColor(BlockRenderView view, BlockPos pos, FluidState state) | ||
+ | { | ||
+ | return | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | FluidRenderHandlerRegistry.INSTANCE.register(still, | ||
+ | FluidRenderHandlerRegistry.INSTANCE.register(flowing, | ||
+ | } | ||
+ | |||
+ | // ... | ||
+ | } | ||
+ | </ | ||
- | <code java> | + | If you want to use your own fluid textures, you can refer to vanilla' |
- | @Override | + | |
- | public void onInitializeClient() | + | |
- | { | + | |
- | Identifier stillSpriteLocation=new Identifier("block/water_still" | + | |
- | Identifier dynamicSpriteLocation=new Identifier(" | + | |
- | | + | |
- | FabricSprite stillAcidSprite = new FabricSprite(stillSpriteLocation, | + | |
- | | + | |
- | FabricSprite dynamicAcidSprite=new FabricSprite(dynamicSpriteLocation, | + | |
- | + | ||
- | | + | |
- | ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register((spriteAtlasTexture, | + | |
- | registry.register(stillAcidSprite); | + | |
- | registry.register(dynamicAcidSprite); | + | |
- | }); | + | |
- | // this renderer is responsible for drawing fluids | + | ===== Generation |
- | FluidRenderHandler acidRenderHandler=new FluidRenderHandler() | + | To make lakes of acid generate in the world, you can create a '' |
- | { | + | |
- | // return | + | |
- | @Override | + | |
- | public Sprite[] getFluidSprites(ExtendedBlockView extendedBlockView, | + | |
- | { | + | |
- | return new Sprite[]{stillAcidSprite, | + | |
- | } | + | |
- | | + | <code java [enable_line_numbers=" |
- | @Override | + | // ... |
- | public int getFluidColor(ExtendedBlockView view, BlockPos pos, FluidState state) | + | |
- | { | + | |
- | return 0x4cc248; | + | |
- | } | + | |
- | }; | + | |
- | // registering the same renderer for both fluid variants is intentional | + | public static LakeFeature ACID_LAKE; |
- | FluidRenderHandlerRegistry.INSTANCE.register([ModInitializer].stillAcid, | + | // ... |
- | FluidRenderHandlerRegistry.INSTANCE.register([ModInitializer].flowingAcid, | + | |
- | </ | + | |
- | Then what's left to do is to create necessary Json files and textures, but you should know how to do that at this point. | + | @Override |
+ | public void onInitialize() | ||
+ | { | ||
+ | // ... | ||
+ | |||
+ | ACID_LAKE = Registry.register(Registry.FEATURE, | ||
+ | |||
+ | // generate in swamps, similar | ||
+ | Biomes.SWAMP.addFeature( | ||
+ | GenerationStep.Feature.LOCAL_MODIFICATIONS, | ||
+ | ACID_LAKE.configure(new SingleStateFeatureConfig(ACID.getDefaultState())) | ||
+ | .createDecoratedFeature(Decorator.WATER_LAKE.configure(new ChanceDecoratorConfig(40))) | ||
+ | ); | ||
+ | |||
+ | // ... | ||
+ | } | ||
- | ===== Generation in a world ===== | + | // ... |
- | + | ||
- | To make acid lakes generate in the world, you can use **net.minecraft.world.gen.feature.LakeFeature**, | + | |
- | <code java> | + | |
- | + | ||
- | LakeFeature acidFeature=Registry.register(Registry.FEATURE, | + | |
- | + | ||
- | </code> | + | |
- | Then put it into desired biomes to generate: | + | |
- | <code java> | + | |
- | | + | |
- | 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