minecraft-src/net/minecraft/world/level/block/PointedDripstoneBlock.java
2025-07-04 01:41:11 +03:00

618 lines
25 KiB
Java

package net.minecraft.world.level.block;
import com.google.common.annotations.VisibleForTesting;
import com.mojang.serialization.MapCodec;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.FluidTags;
import net.minecraft.util.RandomSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.FallingBlockEntity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.projectile.ThrownTrident;
import net.minecraft.world.item.context.BlockPlaceContext;
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.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.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.block.state.properties.DripstoneThickness;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.BooleanOp;
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 PointedDripstoneBlock extends Block implements Fallable, SimpleWaterloggedBlock {
public static final MapCodec<PointedDripstoneBlock> CODEC = simpleCodec(PointedDripstoneBlock::new);
public static final DirectionProperty TIP_DIRECTION = BlockStateProperties.VERTICAL_DIRECTION;
public static final EnumProperty<DripstoneThickness> THICKNESS = BlockStateProperties.DRIPSTONE_THICKNESS;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
private static final int MAX_SEARCH_LENGTH_WHEN_CHECKING_DRIP_TYPE = 11;
private static final int DELAY_BEFORE_FALLING = 2;
private static final float DRIP_PROBABILITY_PER_ANIMATE_TICK = 0.02F;
private static final float DRIP_PROBABILITY_PER_ANIMATE_TICK_IF_UNDER_LIQUID_SOURCE = 0.12F;
private static final int MAX_SEARCH_LENGTH_BETWEEN_STALACTITE_TIP_AND_CAULDRON = 11;
private static final float WATER_TRANSFER_PROBABILITY_PER_RANDOM_TICK = 0.17578125F;
private static final float LAVA_TRANSFER_PROBABILITY_PER_RANDOM_TICK = 0.05859375F;
private static final double MIN_TRIDENT_VELOCITY_TO_BREAK_DRIPSTONE = 0.6;
private static final float STALACTITE_DAMAGE_PER_FALL_DISTANCE_AND_SIZE = 1.0F;
private static final int STALACTITE_MAX_DAMAGE = 40;
private static final int MAX_STALACTITE_HEIGHT_FOR_DAMAGE_CALCULATION = 6;
private static final float STALAGMITE_FALL_DISTANCE_OFFSET = 2.0F;
private static final int STALAGMITE_FALL_DAMAGE_MODIFIER = 2;
private static final float AVERAGE_DAYS_PER_GROWTH = 5.0F;
private static final float GROWTH_PROBABILITY_PER_RANDOM_TICK = 0.011377778F;
private static final int MAX_GROWTH_LENGTH = 7;
private static final int MAX_STALAGMITE_SEARCH_RANGE_WHEN_GROWING = 10;
private static final float STALACTITE_DRIP_START_PIXEL = 0.6875F;
private static final VoxelShape TIP_MERGE_SHAPE = Block.box(5.0, 0.0, 5.0, 11.0, 16.0, 11.0);
private static final VoxelShape TIP_SHAPE_UP = Block.box(5.0, 0.0, 5.0, 11.0, 11.0, 11.0);
private static final VoxelShape TIP_SHAPE_DOWN = Block.box(5.0, 5.0, 5.0, 11.0, 16.0, 11.0);
private static final VoxelShape FRUSTUM_SHAPE = Block.box(4.0, 0.0, 4.0, 12.0, 16.0, 12.0);
private static final VoxelShape MIDDLE_SHAPE = Block.box(3.0, 0.0, 3.0, 13.0, 16.0, 13.0);
private static final VoxelShape BASE_SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 16.0, 14.0);
private static final float MAX_HORIZONTAL_OFFSET = 0.125F;
private static final VoxelShape REQUIRED_SPACE_TO_DRIP_THROUGH_NON_SOLID_BLOCK = Block.box(6.0, 0.0, 6.0, 10.0, 16.0, 10.0);
@Override
public MapCodec<PointedDripstoneBlock> codec() {
return CODEC;
}
public PointedDripstoneBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(
this.stateDefinition.any().setValue(TIP_DIRECTION, Direction.UP).setValue(THICKNESS, DripstoneThickness.TIP).setValue(WATERLOGGED, false)
);
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(TIP_DIRECTION, THICKNESS, WATERLOGGED);
}
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
return isValidPointedDripstonePlacement(level, pos, state.getValue(TIP_DIRECTION));
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
if ((Boolean)state.getValue(WATERLOGGED)) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
if (direction != Direction.UP && direction != Direction.DOWN) {
return state;
} else {
Direction direction2 = state.getValue(TIP_DIRECTION);
if (direction2 == Direction.DOWN && level.getBlockTicks().hasScheduledTick(pos, this)) {
return state;
} else if (direction == direction2.getOpposite() && !this.canSurvive(state, level, pos)) {
if (direction2 == Direction.DOWN) {
level.scheduleTick(pos, this, 2);
} else {
level.scheduleTick(pos, this, 1);
}
return state;
} else {
boolean bl = state.getValue(THICKNESS) == DripstoneThickness.TIP_MERGE;
DripstoneThickness dripstoneThickness = calculateDripstoneThickness(level, pos, direction2, bl);
return state.setValue(THICKNESS, dripstoneThickness);
}
}
}
@Override
protected void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
if (!level.isClientSide) {
BlockPos blockPos = hit.getBlockPos();
if (projectile.mayInteract(level, blockPos)
&& projectile.mayBreak(level)
&& projectile instanceof ThrownTrident
&& projectile.getDeltaMovement().length() > 0.6) {
level.destroyBlock(blockPos, true);
}
}
}
@Override
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
if (state.getValue(TIP_DIRECTION) == Direction.UP && state.getValue(THICKNESS) == DripstoneThickness.TIP) {
entity.causeFallDamage(fallDistance + 2.0F, 2.0F, level.damageSources().stalagmite());
} else {
super.fallOn(level, state, pos, entity, fallDistance);
}
}
@Override
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
if (canDrip(state)) {
float f = random.nextFloat();
if (!(f > 0.12F)) {
getFluidAboveStalactite(level, pos, state)
.filter(fluidInfo -> f < 0.02F || canFillCauldron(fluidInfo.fluid))
.ifPresent(fluidInfo -> spawnDripParticle(level, pos, state, fluidInfo.fluid));
}
}
}
@Override
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (isStalagmite(state) && !this.canSurvive(state, level, pos)) {
level.destroyBlock(pos, true);
} else {
spawnFallingStalactite(state, level, pos);
}
}
@Override
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
maybeTransferFluid(state, level, pos, random.nextFloat());
if (random.nextFloat() < 0.011377778F && isStalactiteStartPos(state, level, pos)) {
growStalactiteOrStalagmiteIfPossible(state, level, pos, random);
}
}
@VisibleForTesting
public static void maybeTransferFluid(BlockState state, ServerLevel level, BlockPos pos, float randChance) {
if (!(randChance > 0.17578125F) || !(randChance > 0.05859375F)) {
if (isStalactiteStartPos(state, level, pos)) {
Optional<PointedDripstoneBlock.FluidInfo> optional = getFluidAboveStalactite(level, pos, state);
if (!optional.isEmpty()) {
Fluid fluid = ((PointedDripstoneBlock.FluidInfo)optional.get()).fluid;
float f;
if (fluid == Fluids.WATER) {
f = 0.17578125F;
} else {
if (fluid != Fluids.LAVA) {
return;
}
f = 0.05859375F;
}
if (!(randChance >= f)) {
BlockPos blockPos = findTip(state, level, pos, 11, false);
if (blockPos != null) {
if (((PointedDripstoneBlock.FluidInfo)optional.get()).sourceState.is(Blocks.MUD) && fluid == Fluids.WATER) {
BlockState blockState = Blocks.CLAY.defaultBlockState();
level.setBlockAndUpdate(((PointedDripstoneBlock.FluidInfo)optional.get()).pos, blockState);
Block.pushEntitiesUp(
((PointedDripstoneBlock.FluidInfo)optional.get()).sourceState, blockState, level, ((PointedDripstoneBlock.FluidInfo)optional.get()).pos
);
level.gameEvent(GameEvent.BLOCK_CHANGE, ((PointedDripstoneBlock.FluidInfo)optional.get()).pos, GameEvent.Context.of(blockState));
level.levelEvent(1504, blockPos, 0);
} else {
BlockPos blockPos2 = findFillableCauldronBelowStalactiteTip(level, blockPos, fluid);
if (blockPos2 != null) {
level.levelEvent(1504, blockPos, 0);
int i = blockPos.getY() - blockPos2.getY();
int j = 50 + i;
BlockState blockState2 = level.getBlockState(blockPos2);
level.scheduleTick(blockPos2, blockState2.getBlock(), j);
}
}
}
}
}
}
}
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
LevelAccessor levelAccessor = context.getLevel();
BlockPos blockPos = context.getClickedPos();
Direction direction = context.getNearestLookingVerticalDirection().getOpposite();
Direction direction2 = calculateTipDirection(levelAccessor, blockPos, direction);
if (direction2 == null) {
return null;
} else {
boolean bl = !context.isSecondaryUseActive();
DripstoneThickness dripstoneThickness = calculateDripstoneThickness(levelAccessor, blockPos, direction2, bl);
return dripstoneThickness == null
? null
: this.defaultBlockState()
.setValue(TIP_DIRECTION, direction2)
.setValue(THICKNESS, dripstoneThickness)
.setValue(WATERLOGGED, levelAccessor.getFluidState(blockPos).getType() == Fluids.WATER);
}
}
@Override
protected FluidState getFluidState(BlockState state) {
return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state);
}
@Override
protected VoxelShape getOcclusionShape(BlockState state, BlockGetter level, BlockPos pos) {
return Shapes.empty();
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
DripstoneThickness dripstoneThickness = state.getValue(THICKNESS);
VoxelShape voxelShape;
if (dripstoneThickness == DripstoneThickness.TIP_MERGE) {
voxelShape = TIP_MERGE_SHAPE;
} else if (dripstoneThickness == DripstoneThickness.TIP) {
if (state.getValue(TIP_DIRECTION) == Direction.DOWN) {
voxelShape = TIP_SHAPE_DOWN;
} else {
voxelShape = TIP_SHAPE_UP;
}
} else if (dripstoneThickness == DripstoneThickness.FRUSTUM) {
voxelShape = FRUSTUM_SHAPE;
} else if (dripstoneThickness == DripstoneThickness.MIDDLE) {
voxelShape = MIDDLE_SHAPE;
} else {
voxelShape = BASE_SHAPE;
}
Vec3 vec3 = state.getOffset(level, pos);
return voxelShape.move(vec3.x, 0.0, vec3.z);
}
@Override
protected boolean isCollisionShapeFullBlock(BlockState state, BlockGetter level, BlockPos pos) {
return false;
}
@Override
protected float getMaxHorizontalOffset() {
return 0.125F;
}
@Override
public void onBrokenAfterFall(Level level, BlockPos pos, FallingBlockEntity fallingBlock) {
if (!fallingBlock.isSilent()) {
level.levelEvent(1045, pos, 0);
}
}
@Override
public DamageSource getFallDamageSource(Entity entity) {
return entity.damageSources().fallingStalactite(entity);
}
private static void spawnFallingStalactite(BlockState state, ServerLevel level, BlockPos pos) {
BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
BlockState blockState = state;
while (isStalactite(blockState)) {
FallingBlockEntity fallingBlockEntity = FallingBlockEntity.fall(level, mutableBlockPos, blockState);
if (isTip(blockState, true)) {
int i = Math.max(1 + pos.getY() - mutableBlockPos.getY(), 6);
float f = 1.0F * i;
fallingBlockEntity.setHurtsEntities(f, 40);
break;
}
mutableBlockPos.move(Direction.DOWN);
blockState = level.getBlockState(mutableBlockPos);
}
}
@VisibleForTesting
public static void growStalactiteOrStalagmiteIfPossible(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
BlockState blockState = level.getBlockState(pos.above(1));
BlockState blockState2 = level.getBlockState(pos.above(2));
if (canGrow(blockState, blockState2)) {
BlockPos blockPos = findTip(state, level, pos, 7, false);
if (blockPos != null) {
BlockState blockState3 = level.getBlockState(blockPos);
if (canDrip(blockState3) && canTipGrow(blockState3, level, blockPos)) {
if (random.nextBoolean()) {
grow(level, blockPos, Direction.DOWN);
} else {
growStalagmiteBelow(level, blockPos);
}
}
}
}
}
private static void growStalagmiteBelow(ServerLevel level, BlockPos pos) {
BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
for (int i = 0; i < 10; i++) {
mutableBlockPos.move(Direction.DOWN);
BlockState blockState = level.getBlockState(mutableBlockPos);
if (!blockState.getFluidState().isEmpty()) {
return;
}
if (isUnmergedTipWithDirection(blockState, Direction.UP) && canTipGrow(blockState, level, mutableBlockPos)) {
grow(level, mutableBlockPos, Direction.UP);
return;
}
if (isValidPointedDripstonePlacement(level, mutableBlockPos, Direction.UP) && !level.isWaterAt(mutableBlockPos.below())) {
grow(level, mutableBlockPos.below(), Direction.UP);
return;
}
if (!canDripThrough(level, mutableBlockPos, blockState)) {
return;
}
}
}
private static void grow(ServerLevel server, BlockPos pos, Direction direction) {
BlockPos blockPos = pos.relative(direction);
BlockState blockState = server.getBlockState(blockPos);
if (isUnmergedTipWithDirection(blockState, direction.getOpposite())) {
createMergedTips(blockState, server, blockPos);
} else if (blockState.isAir() || blockState.is(Blocks.WATER)) {
createDripstone(server, blockPos, direction, DripstoneThickness.TIP);
}
}
private static void createDripstone(LevelAccessor level, BlockPos pos, Direction direction, DripstoneThickness thickness) {
BlockState blockState = Blocks.POINTED_DRIPSTONE
.defaultBlockState()
.setValue(TIP_DIRECTION, direction)
.setValue(THICKNESS, thickness)
.setValue(WATERLOGGED, level.getFluidState(pos).getType() == Fluids.WATER);
level.setBlock(pos, blockState, 3);
}
private static void createMergedTips(BlockState state, LevelAccessor level, BlockPos pos) {
BlockPos blockPos2;
BlockPos blockPos;
if (state.getValue(TIP_DIRECTION) == Direction.UP) {
blockPos = pos;
blockPos2 = pos.above();
} else {
blockPos2 = pos;
blockPos = pos.below();
}
createDripstone(level, blockPos2, Direction.DOWN, DripstoneThickness.TIP_MERGE);
createDripstone(level, blockPos, Direction.UP, DripstoneThickness.TIP_MERGE);
}
public static void spawnDripParticle(Level level, BlockPos pos, BlockState state) {
getFluidAboveStalactite(level, pos, state).ifPresent(fluidInfo -> spawnDripParticle(level, pos, state, fluidInfo.fluid));
}
private static void spawnDripParticle(Level level, BlockPos pos, BlockState state, Fluid fluid) {
Vec3 vec3 = state.getOffset(level, pos);
double d = 0.0625;
double e = pos.getX() + 0.5 + vec3.x;
double f = pos.getY() + 1 - 0.6875F - 0.0625;
double g = pos.getZ() + 0.5 + vec3.z;
Fluid fluid2 = getDripFluid(level, fluid);
ParticleOptions particleOptions = fluid2.is(FluidTags.LAVA) ? ParticleTypes.DRIPPING_DRIPSTONE_LAVA : ParticleTypes.DRIPPING_DRIPSTONE_WATER;
level.addParticle(particleOptions, e, f, g, 0.0, 0.0, 0.0);
}
@Nullable
private static BlockPos findTip(BlockState state, LevelAccessor level, BlockPos pos, int maxIterations, boolean isTipMerge) {
if (isTip(state, isTipMerge)) {
return pos;
} else {
Direction direction = state.getValue(TIP_DIRECTION);
BiPredicate<BlockPos, BlockState> biPredicate = (blockPos, blockState) -> blockState.is(Blocks.POINTED_DRIPSTONE)
&& blockState.getValue(TIP_DIRECTION) == direction;
return (BlockPos)findBlockVertical(level, pos, direction.getAxisDirection(), biPredicate, blockState -> isTip(blockState, isTipMerge), maxIterations)
.orElse(null);
}
}
@Nullable
private static Direction calculateTipDirection(LevelReader level, BlockPos pos, Direction dir) {
Direction direction;
if (isValidPointedDripstonePlacement(level, pos, dir)) {
direction = dir;
} else {
if (!isValidPointedDripstonePlacement(level, pos, dir.getOpposite())) {
return null;
}
direction = dir.getOpposite();
}
return direction;
}
private static DripstoneThickness calculateDripstoneThickness(LevelReader level, BlockPos pos, Direction dir, boolean isTipMerge) {
Direction direction = dir.getOpposite();
BlockState blockState = level.getBlockState(pos.relative(dir));
if (isPointedDripstoneWithDirection(blockState, direction)) {
return !isTipMerge && blockState.getValue(THICKNESS) != DripstoneThickness.TIP_MERGE ? DripstoneThickness.TIP : DripstoneThickness.TIP_MERGE;
} else if (!isPointedDripstoneWithDirection(blockState, dir)) {
return DripstoneThickness.TIP;
} else {
DripstoneThickness dripstoneThickness = blockState.getValue(THICKNESS);
if (dripstoneThickness != DripstoneThickness.TIP && dripstoneThickness != DripstoneThickness.TIP_MERGE) {
BlockState blockState2 = level.getBlockState(pos.relative(direction));
return !isPointedDripstoneWithDirection(blockState2, dir) ? DripstoneThickness.BASE : DripstoneThickness.MIDDLE;
} else {
return DripstoneThickness.FRUSTUM;
}
}
}
public static boolean canDrip(BlockState state) {
return isStalactite(state) && state.getValue(THICKNESS) == DripstoneThickness.TIP && !(Boolean)state.getValue(WATERLOGGED);
}
private static boolean canTipGrow(BlockState state, ServerLevel level, BlockPos pos) {
Direction direction = state.getValue(TIP_DIRECTION);
BlockPos blockPos = pos.relative(direction);
BlockState blockState = level.getBlockState(blockPos);
if (!blockState.getFluidState().isEmpty()) {
return false;
} else {
return blockState.isAir() ? true : isUnmergedTipWithDirection(blockState, direction.getOpposite());
}
}
private static Optional<BlockPos> findRootBlock(Level level, BlockPos pos, BlockState state, int maxIterations) {
Direction direction = state.getValue(TIP_DIRECTION);
BiPredicate<BlockPos, BlockState> biPredicate = (blockPos, blockState) -> blockState.is(Blocks.POINTED_DRIPSTONE)
&& blockState.getValue(TIP_DIRECTION) == direction;
return findBlockVertical(
level, pos, direction.getOpposite().getAxisDirection(), biPredicate, blockState -> !blockState.is(Blocks.POINTED_DRIPSTONE), maxIterations
);
}
private static boolean isValidPointedDripstonePlacement(LevelReader level, BlockPos pos, Direction dir) {
BlockPos blockPos = pos.relative(dir.getOpposite());
BlockState blockState = level.getBlockState(blockPos);
return blockState.isFaceSturdy(level, blockPos, dir) || isPointedDripstoneWithDirection(blockState, dir);
}
private static boolean isTip(BlockState state, boolean isTipMerge) {
if (!state.is(Blocks.POINTED_DRIPSTONE)) {
return false;
} else {
DripstoneThickness dripstoneThickness = state.getValue(THICKNESS);
return dripstoneThickness == DripstoneThickness.TIP || isTipMerge && dripstoneThickness == DripstoneThickness.TIP_MERGE;
}
}
private static boolean isUnmergedTipWithDirection(BlockState state, Direction dir) {
return isTip(state, false) && state.getValue(TIP_DIRECTION) == dir;
}
private static boolean isStalactite(BlockState state) {
return isPointedDripstoneWithDirection(state, Direction.DOWN);
}
private static boolean isStalagmite(BlockState state) {
return isPointedDripstoneWithDirection(state, Direction.UP);
}
private static boolean isStalactiteStartPos(BlockState state, LevelReader level, BlockPos pos) {
return isStalactite(state) && !level.getBlockState(pos.above()).is(Blocks.POINTED_DRIPSTONE);
}
@Override
protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) {
return false;
}
private static boolean isPointedDripstoneWithDirection(BlockState state, Direction dir) {
return state.is(Blocks.POINTED_DRIPSTONE) && state.getValue(TIP_DIRECTION) == dir;
}
@Nullable
private static BlockPos findFillableCauldronBelowStalactiteTip(Level level, BlockPos pos, Fluid fluid) {
Predicate<BlockState> predicate = blockState -> blockState.getBlock() instanceof AbstractCauldronBlock
&& ((AbstractCauldronBlock)blockState.getBlock()).canReceiveStalactiteDrip(fluid);
BiPredicate<BlockPos, BlockState> biPredicate = (blockPos, blockState) -> canDripThrough(level, blockPos, blockState);
return (BlockPos)findBlockVertical(level, pos, Direction.DOWN.getAxisDirection(), biPredicate, predicate, 11).orElse(null);
}
@Nullable
public static BlockPos findStalactiteTipAboveCauldron(Level level, BlockPos pos) {
BiPredicate<BlockPos, BlockState> biPredicate = (blockPos, blockState) -> canDripThrough(level, blockPos, blockState);
return (BlockPos)findBlockVertical(level, pos, Direction.UP.getAxisDirection(), biPredicate, PointedDripstoneBlock::canDrip, 11).orElse(null);
}
public static Fluid getCauldronFillFluidType(ServerLevel level, BlockPos pos) {
return (Fluid)getFluidAboveStalactite(level, pos, level.getBlockState(pos))
.map(fluidInfo -> fluidInfo.fluid)
.filter(PointedDripstoneBlock::canFillCauldron)
.orElse(Fluids.EMPTY);
}
private static Optional<PointedDripstoneBlock.FluidInfo> getFluidAboveStalactite(Level level, BlockPos pos, BlockState state) {
return !isStalactite(state) ? Optional.empty() : findRootBlock(level, pos, state, 11).map(blockPos -> {
BlockPos blockPos2 = blockPos.above();
BlockState blockState = level.getBlockState(blockPos2);
Fluid fluid;
if (blockState.is(Blocks.MUD) && !level.dimensionType().ultraWarm()) {
fluid = Fluids.WATER;
} else {
fluid = level.getFluidState(blockPos2).getType();
}
return new PointedDripstoneBlock.FluidInfo(blockPos2, fluid, blockState);
});
}
private static boolean canFillCauldron(Fluid fluid) {
return fluid == Fluids.LAVA || fluid == Fluids.WATER;
}
private static boolean canGrow(BlockState dripstoneState, BlockState state) {
return dripstoneState.is(Blocks.DRIPSTONE_BLOCK) && state.is(Blocks.WATER) && state.getFluidState().isSource();
}
private static Fluid getDripFluid(Level level, Fluid fluid) {
if (fluid.isSame(Fluids.EMPTY)) {
return level.dimensionType().ultraWarm() ? Fluids.LAVA : Fluids.WATER;
} else {
return fluid;
}
}
private static Optional<BlockPos> findBlockVertical(
LevelAccessor level,
BlockPos pos,
Direction.AxisDirection axis,
BiPredicate<BlockPos, BlockState> positionalStatePredicate,
Predicate<BlockState> statePredicate,
int maxIterations
) {
Direction direction = Direction.get(axis, Direction.Axis.Y);
BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
for (int i = 1; i < maxIterations; i++) {
mutableBlockPos.move(direction);
BlockState blockState = level.getBlockState(mutableBlockPos);
if (statePredicate.test(blockState)) {
return Optional.of(mutableBlockPos.immutable());
}
if (level.isOutsideBuildHeight(mutableBlockPos.getY()) || !positionalStatePredicate.test(mutableBlockPos, blockState)) {
return Optional.empty();
}
}
return Optional.empty();
}
private static boolean canDripThrough(BlockGetter level, BlockPos pos, BlockState state) {
if (state.isAir()) {
return true;
} else if (state.isSolidRender(level, pos)) {
return false;
} else if (!state.getFluidState().isEmpty()) {
return false;
} else {
VoxelShape voxelShape = state.getCollisionShape(level, pos);
return !Shapes.joinIsNotEmpty(REQUIRED_SPACE_TO_DRIP_THROUGH_NON_SOLID_BLOCK, voxelShape, BooleanOp.AND);
}
}
record FluidInfo(BlockPos pos, Fluid fluid, BlockState sourceState) {
}
}