This is an old revision of the document!
Table of Contents
创建流体
Overview
在这里,我们将介绍自定义流体的创建。 如果计划创建多个流体,建议创建一个抽象的基本流体类,在其中设置必要的默认值,这些默认值将在其子类中共享。 我们还将使其像湖泊一样在世界上产生。
使抽象流畅
香草流体扩展了net.minecraft.fluid.BaseFluid类,我们的抽象流体也将扩展。 可能是这样的:
public abstract class BasicFluid extends BaseFluid { /** * @return does it produce infinite fluid (like water)? */ @Override protected boolean isInfinite() { return false; } // make it transparent @Override protected BlockRenderLayer getRenderLayer() { return BlockRenderLayer.TRANSLUCENT; } /** * * @return an associated item that "holds" this fluid */ @Override public abstract Item getBucketItem(); /** * * @return a blockstate of the associated {@linkplain net.minecraft.block.FluidBlock} with {@linkplain net.minecraft.block.FluidBlock#LEVEL} */ @Override protected abstract BlockState toBlockState(FluidState var1); /** * * @return flowing static instance of this fluid */ @Override public abstract Fluid getFlowing(); /** * * @return still static instance of this fluid */ @Override public abstract Fluid getStill(); // how much does the height of the fluid block decreases @Override protected int getLevelDecreasePerBlock(ViewableWorld world) { return 1; } /** * * @return update rate */ @Override public int getTickRate(ViewableWorld world) { return 5; } @Override protected float getBlastResistance() { return 100; } // this seems to determine fluid's spread speed (higher value means faster) @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, world.getWorld(), blockPos, blockEntity); } // 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); }
Implementation
Now let's make an actual fluid; it will have a still and flowing variants; will name it “Acid”:
public abstract class Acid extends BasicFluid { @Override public Item getBucketItem() { return null; } @Override protected BlockState toBlockState(FluidState var1) { return null; } @Override public Fluid getFlowing() { return null; } @Override public Fluid getStill() { return null; } @Override public boolean matchesType(Fluid fluid) { return false; } // still acid public static class Still extends Acid { @Override public boolean isStill(FluidState fluidState) { return true; } /** * @return height of the fluid block */ @Override public int getLevel(FluidState fluidState) { return 8; } } // flowing acid public static class Flowing extends Acid { @Override public boolean isStill(FluidState fluidState) { return false; } /** * @return height of the fluid block */ @Override public int getLevel(FluidState fluidState) { return fluidState.get(LEVEL); } @Override protected void appendProperties(StateFactory.Builder<Fluid, FluidState> stateFactoryBuilder) { super.appendProperties(stateFactoryBuilder); stateFactoryBuilder.add(LEVEL); } } }
Next, we'll make static instances of still and flowing acid variants, and an acid bucket. In your ModInitializer:
public static Acid stillAcid; public static Acid flowingAcid; public static BucketItem acidBucket; @Override public void onInitialize() { stillAcid = Registry.register(Registry.FLUID, new Identifier(MODID,"acid_still"), new Acid.Still()); flowingAcid = Registry.register(Registry.FLUID, new Identifier(MODID,"acid_flowing"), new Acid.Flowing()); acidBucket = new BucketItem(stillAcid, new Item.Settings().maxCount(1)); Registry.register(Registry.ITEM, new Identifier(MODID,"acid_bucket"), acidBucket); }
To make the custom fluid behave like water or lava, you must add it to a corresponding fluid tag: make a file “data/minecraft/tags/fluids/water.json” and write identifiers of your fluids in there:
{ "replace": false, "values": [ "modid:acid_still", "modid:acid_flowing" ] }
Making a fluid block
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 “mojang” reasons its constructor is protected. The solution is well-known - make a subclass of it and change the visibility of the constructor:
public class BaseFluidBlock extends FluidBlock { public BaseFluidBlock(BaseFluid fluid, Settings settings) { super(fluid, settings); } }
Now make a static block instance:
... public static FluidBlock acid; @Override public void onInitialize() { ... acid = new BaseFluidBlock(stillAcid, FabricBlockSettings.of(Material.WATER).dropsNothing().build()); Registry.register(Registry.BLOCK, new Identifier(MODID, "acid_block"), acid); }
Now when we have these static objects, we go back to Acid class and complete the overridden methods:
public abstract class Acid extends BasicFluid { @Override public Item getBucketItem() { return Mod.acidBucket; } @Override protected BlockState toBlockState(FluidState fluidState) { //don't ask me what **method_15741** does... return Mod.acid.getDefaultState().with(FluidBlock.LEVEL, method_15741(fluidState)); } @Override public Fluid getFlowing() { return Mod.flowingAcid; } @Override public Fluid getStill() { return Mod.stillAcid; } @Override public boolean matchesType(Fluid fluid_1) { return fluid_1==Mod.flowingAcid || fluid_1==Mod.stillAcid; } ... }
Now we can assert that the Acid class is complete.
Rendering setup
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.
@Override public void onInitializeClient() { // adding the sprites to the block texture atlas ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register((spriteAtlasTexture, registry) -> { Identifier stillSpriteLocation = new Identifier("block/water_still"); Identifier dynamicSpriteLocation = new Identifier("block/water_flow"); // here I tell to use only 16x16 area of the water texture FabricSprite stillAcidSprite = new FabricSprite(stillSpriteLocation, 16, 16); // same, but 32 FabricSprite dynamicAcidSprite = new FabricSprite(dynamicSpriteLocation, 32, 32); registry.register(stillAcidSprite); registry.register(dynamicAcidSprite); // this renderer is responsible for drawing fluids in a world FluidRenderHandler acidRenderHandler = new FluidRenderHandler() { // return the sprites: still sprite goes first into the array, flowing/dynamic goes last @Override public Sprite[] getFluidSprites(ExtendedBlockView extendedBlockView, BlockPos blockPos, FluidState fluidState) { return new Sprite[] {stillAcidSprite, dynamicAcidSprite}; } // apply light green color @Override public int getFluidColor(ExtendedBlockView view, BlockPos pos, FluidState state) { return 0x4cc248; } }; // registering the same renderer for both fluid variants is intentional FluidRenderHandlerRegistry.INSTANCE.register(Mod.stillAcid, acidRenderHandler); FluidRenderHandlerRegistry.INSTANCE.register(Mod.flowingAcid, acidRenderHandler); });
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.
Generation in a world
To make acid lakes generate in the world, you can use net.minecraft.world.gen.feature.LakeFeature, which you create in the ModInitializer:
LakeFeature acidFeature = Registry.register(Registry.FEATURE, new Identifier(MODID,"acid_lake"), new LakeFeature(dynamic -> new LakeFeatureConfig(acid.getDefaultState())));
Then put it into desired biomes to generate:
// 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, Biome.configureFeature(acidFeature, new LakeFeatureConfig(acid.getDefaultState()), Decorator.WATER_LAKE, new LakeDecoratorConfig(40)));
This is the end of the tutorial.