package net.minecraft.world.level.block; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.Collections; import java.util.List; import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.tags.FluidTags; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.ScheduledTickAccess; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.IntegerProperty; import net.minecraft.world.level.material.FlowingFluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.level.redstone.Orientation; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.jetbrains.annotations.Nullable; public class LiquidBlock extends Block implements BucketPickup { private static final Codec FLOWING_FLUID = BuiltInRegistries.FLUID .byNameCodec() .comapFlatMap( fluid -> fluid instanceof FlowingFluid flowingFluid ? DataResult.success(flowingFluid) : DataResult.error(() -> "Not a flowing fluid: " + fluid), flowingFluid -> flowingFluid ); public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group(FLOWING_FLUID.fieldOf("fluid").forGetter(liquidBlock -> liquidBlock.fluid), propertiesCodec()).apply(instance, LiquidBlock::new) ); public static final IntegerProperty LEVEL = BlockStateProperties.LEVEL; protected final FlowingFluid fluid; private final List stateCache; public static final VoxelShape STABLE_SHAPE = Block.box(0.0, 0.0, 0.0, 16.0, 8.0, 16.0); public static final ImmutableList POSSIBLE_FLOW_DIRECTIONS = ImmutableList.of( Direction.DOWN, Direction.SOUTH, Direction.NORTH, Direction.EAST, Direction.WEST ); @Override public MapCodec codec() { return CODEC; } protected LiquidBlock(FlowingFluid fluid, BlockBehaviour.Properties properties) { super(properties); this.fluid = fluid; this.stateCache = Lists.newArrayList(); this.stateCache.add(fluid.getSource(false)); for (int i = 1; i < 8; i++) { this.stateCache.add(fluid.getFlowing(8 - i, false)); } this.stateCache.add(fluid.getFlowing(8, true)); this.registerDefaultState(this.stateDefinition.any().setValue(LEVEL, 0)); } @Override protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return context.isAbove(STABLE_SHAPE, pos, true) && state.getValue(LEVEL) == 0 && context.canStandOnFluid(level.getFluidState(pos.above()), state.getFluidState()) ? STABLE_SHAPE : Shapes.empty(); } @Override protected boolean isRandomlyTicking(BlockState state) { return state.getFluidState().isRandomlyTicking(); } @Override protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { state.getFluidState().randomTick(level, pos, random); } @Override protected boolean propagatesSkylightDown(BlockState blockState) { return false; } @Override protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { return !this.fluid.is(FluidTags.LAVA); } @Override protected FluidState getFluidState(BlockState state) { int i = (Integer)state.getValue(LEVEL); return (FluidState)this.stateCache.get(Math.min(i, 8)); } @Override protected boolean skipRendering(BlockState state, BlockState adjacentState, Direction direction) { return adjacentState.getFluidState().getType().isSame(this.fluid); } @Override protected RenderShape getRenderShape(BlockState state) { return RenderShape.INVISIBLE; } @Override protected List getDrops(BlockState state, LootParams.Builder params) { return Collections.emptyList(); } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return Shapes.empty(); } @Override protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) { if (this.shouldSpreadLiquid(level, pos, state)) { level.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(level)); } } @Override protected BlockState updateShape( BlockState blockState, LevelReader levelReader, ScheduledTickAccess scheduledTickAccess, BlockPos blockPos, Direction direction, BlockPos blockPos2, BlockState blockState2, RandomSource randomSource ) { if (blockState.getFluidState().isSource() || blockState2.getFluidState().isSource()) { scheduledTickAccess.scheduleTick(blockPos, blockState.getFluidState().getType(), this.fluid.getTickDelay(levelReader)); } return super.updateShape(blockState, levelReader, scheduledTickAccess, blockPos, direction, blockPos2, blockState2, randomSource); } @Override protected void neighborChanged(BlockState blockState, Level level, BlockPos blockPos, Block block, @Nullable Orientation orientation, boolean bl) { if (this.shouldSpreadLiquid(level, blockPos, blockState)) { level.scheduleTick(blockPos, blockState.getFluidState().getType(), this.fluid.getTickDelay(level)); } } private boolean shouldSpreadLiquid(Level level, BlockPos pos, BlockState state) { if (this.fluid.is(FluidTags.LAVA)) { boolean bl = level.getBlockState(pos.below()).is(Blocks.SOUL_SOIL); for (Direction direction : POSSIBLE_FLOW_DIRECTIONS) { BlockPos blockPos = pos.relative(direction.getOpposite()); if (level.getFluidState(blockPos).is(FluidTags.WATER)) { Block block = level.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE; level.setBlockAndUpdate(pos, block.defaultBlockState()); this.fizz(level, pos); return false; } if (bl && level.getBlockState(blockPos).is(Blocks.BLUE_ICE)) { level.setBlockAndUpdate(pos, Blocks.BASALT.defaultBlockState()); this.fizz(level, pos); return false; } } } return true; } private void fizz(LevelAccessor level, BlockPos pos) { level.levelEvent(1501, pos, 0); } @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(LEVEL); } @Override public ItemStack pickupBlock(@Nullable Player player, LevelAccessor level, BlockPos pos, BlockState state) { if ((Integer)state.getValue(LEVEL) == 0) { level.setBlock(pos, Blocks.AIR.defaultBlockState(), 11); return new ItemStack(this.fluid.getBucket()); } else { return ItemStack.EMPTY; } } @Override public Optional getPickupSound() { return this.fluid.getPickupSound(); } }