/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.annotations.VisibleForTesting * com.mojang.serialization.MapCodec * org.jspecify.annotations.Nullable */ 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.attribute.EnvironmentAttributes; 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.ScheduledTickAccess; import net.minecraft.world.level.block.AbstractCauldronBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Fallable; import net.minecraft.world.level.block.SimpleWaterloggedBlock; 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.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.jspecify.annotations.Nullable; public class PointedDripstoneBlock extends Block implements SimpleWaterloggedBlock, Fallable { public static final MapCodec CODEC = PointedDripstoneBlock.simpleCodec(PointedDripstoneBlock::new); public static final EnumProperty TIP_DIRECTION = BlockStateProperties.VERTICAL_DIRECTION; public static final EnumProperty 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.5f; 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 VoxelShape SHAPE_TIP_MERGE = Block.column(6.0, 0.0, 16.0); private static final VoxelShape SHAPE_TIP_UP = Block.column(6.0, 0.0, 11.0); private static final VoxelShape SHAPE_TIP_DOWN = Block.column(6.0, 5.0, 16.0); private static final VoxelShape SHAPE_FRUSTUM = Block.column(8.0, 0.0, 16.0); private static final VoxelShape SHAPE_MIDDLE = Block.column(10.0, 0.0, 16.0); private static final VoxelShape SHAPE_BASE = Block.column(12.0, 0.0, 16.0); private static final double STALACTITE_DRIP_START_PIXEL = SHAPE_TIP_DOWN.min(Direction.Axis.Y); private static final float MAX_HORIZONTAL_OFFSET = (float)SHAPE_BASE.min(Direction.Axis.X); private static final VoxelShape REQUIRED_SPACE_TO_DRIP_THROUGH_NON_SOLID_BLOCK = Block.column(4.0, 0.0, 16.0); public MapCodec codec() { return CODEC; } public PointedDripstoneBlock(BlockBehaviour.Properties properties) { super(properties); this.registerDefaultState((BlockState)((BlockState)((BlockState)((BlockState)this.stateDefinition.any()).setValue(TIP_DIRECTION, Direction.UP)).setValue(THICKNESS, DripstoneThickness.TIP)).setValue(WATERLOGGED, false)); } @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(TIP_DIRECTION, THICKNESS, WATERLOGGED); } @Override protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { return PointedDripstoneBlock.isValidPointedDripstonePlacement(level, pos, state.getValue(TIP_DIRECTION)); } @Override protected BlockState updateShape(BlockState state, LevelReader level, ScheduledTickAccess ticks, BlockPos pos, Direction directionToNeighbour, BlockPos neighbourPos, BlockState neighbourState, RandomSource random) { if (state.getValue(WATERLOGGED).booleanValue()) { ticks.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level)); } if (directionToNeighbour != Direction.UP && directionToNeighbour != Direction.DOWN) { return state; } Direction tipDirection = state.getValue(TIP_DIRECTION); if (tipDirection == Direction.DOWN && ticks.getBlockTicks().hasScheduledTick(pos, this)) { return state; } if (directionToNeighbour == tipDirection.getOpposite() && !this.canSurvive(state, level, pos)) { if (tipDirection == Direction.DOWN) { ticks.scheduleTick(pos, this, 2); } else { ticks.scheduleTick(pos, this, 1); } return state; } boolean mergeOpposingTips = state.getValue(THICKNESS) == DripstoneThickness.TIP_MERGE; DripstoneThickness newThickness = PointedDripstoneBlock.calculateDripstoneThickness(level, pos, tipDirection, mergeOpposingTips); return (BlockState)state.setValue(THICKNESS, newThickness); } @Override protected void onProjectileHit(Level level, BlockState state, BlockHitResult blockHit, Projectile projectile) { ServerLevel serverLevel; if (level.isClientSide()) { return; } BlockPos blockPos = blockHit.getBlockPos(); if (level instanceof ServerLevel && projectile.mayInteract(serverLevel = (ServerLevel)level, blockPos) && projectile.mayBreak(serverLevel) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6) { level.destroyBlock(blockPos, true); } } @Override public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance) { if (state.getValue(TIP_DIRECTION) == Direction.UP && state.getValue(THICKNESS) == DripstoneThickness.TIP) { entity.causeFallDamage(fallDistance + 2.5, 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 (!PointedDripstoneBlock.canDrip(state)) { return; } float randomValue = random.nextFloat(); if (randomValue > 0.12f) { return; } PointedDripstoneBlock.getFluidAboveStalactite(level, pos, state).filter(fluidAbove -> randomValue < 0.02f || PointedDripstoneBlock.canFillCauldron(fluidAbove.fluid)).ifPresent(fluidAbove -> PointedDripstoneBlock.spawnDripParticle(level, pos, state, fluidAbove.fluid, fluidAbove.pos)); } @Override protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { if (PointedDripstoneBlock.isStalagmite(state) && !this.canSurvive(state, level, pos)) { level.destroyBlock(pos, true); } else { PointedDripstoneBlock.spawnFallingStalactite(state, level, pos); } } @Override protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { PointedDripstoneBlock.maybeTransferFluid(state, level, pos, random.nextFloat()); if (random.nextFloat() < 0.011377778f && PointedDripstoneBlock.isStalactiteStartPos(state, level, pos)) { PointedDripstoneBlock.growStalactiteOrStalagmiteIfPossible(state, level, pos, random); } } @VisibleForTesting public static void maybeTransferFluid(BlockState state, ServerLevel level, BlockPos pos, float randomValue) { float transferProbability; if (randomValue > 0.17578125f && randomValue > 0.05859375f) { return; } if (!PointedDripstoneBlock.isStalactiteStartPos(state, level, pos)) { return; } Optional fluidInfo = PointedDripstoneBlock.getFluidAboveStalactite(level, pos, state); if (fluidInfo.isEmpty()) { return; } Fluid fluid = fluidInfo.get().fluid; if (fluid == Fluids.WATER) { transferProbability = 0.17578125f; } else if (fluid == Fluids.LAVA) { transferProbability = 0.05859375f; } else { return; } if (randomValue >= transferProbability) { return; } BlockPos stalactiteTipPos = PointedDripstoneBlock.findTip(state, level, pos, 11, false); if (stalactiteTipPos == null) { return; } if (fluidInfo.get().sourceState.is(Blocks.MUD) && fluid == Fluids.WATER) { BlockState newState = Blocks.CLAY.defaultBlockState(); level.setBlockAndUpdate(fluidInfo.get().pos, newState); Block.pushEntitiesUp(fluidInfo.get().sourceState, newState, level, fluidInfo.get().pos); level.gameEvent(GameEvent.BLOCK_CHANGE, fluidInfo.get().pos, GameEvent.Context.of(newState)); level.levelEvent(1504, stalactiteTipPos, 0); return; } BlockPos cauldronPos = PointedDripstoneBlock.findFillableCauldronBelowStalactiteTip(level, stalactiteTipPos, fluid); if (cauldronPos == null) { return; } level.levelEvent(1504, stalactiteTipPos, 0); int fallDistance = stalactiteTipPos.getY() - cauldronPos.getY(); int delay = 50 + fallDistance; BlockState cauldronState = level.getBlockState(cauldronPos); level.scheduleTick(cauldronPos, cauldronState.getBlock(), delay); } @Override public @Nullable BlockState getStateForPlacement(BlockPlaceContext context) { Direction defaultTipDirection; BlockPos pos; Level level = context.getLevel(); Direction tipDirection = PointedDripstoneBlock.calculateTipDirection(level, pos = context.getClickedPos(), defaultTipDirection = context.getNearestLookingVerticalDirection().getOpposite()); if (tipDirection == null) { return null; } boolean mergeOpposingTips = !context.isSecondaryUseActive(); DripstoneThickness thickness = PointedDripstoneBlock.calculateDripstoneThickness(level, pos, tipDirection, mergeOpposingTips); return (BlockState)((BlockState)((BlockState)this.defaultBlockState().setValue(TIP_DIRECTION, tipDirection)).setValue(THICKNESS, thickness)).setValue(WATERLOGGED, level.getFluidState(pos).getType() == Fluids.WATER); } @Override protected FluidState getFluidState(BlockState state) { if (state.getValue(WATERLOGGED).booleanValue()) { return Fluids.WATER.getSource(false); } return super.getFluidState(state); } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { VoxelShape shape = switch (state.getValue(THICKNESS)) { default -> throw new MatchException(null, null); case DripstoneThickness.TIP_MERGE -> SHAPE_TIP_MERGE; case DripstoneThickness.TIP -> { if (state.getValue(TIP_DIRECTION) == Direction.DOWN) { yield SHAPE_TIP_DOWN; } yield SHAPE_TIP_UP; } case DripstoneThickness.FRUSTUM -> SHAPE_FRUSTUM; case DripstoneThickness.MIDDLE -> SHAPE_MIDDLE; case DripstoneThickness.BASE -> SHAPE_BASE; }; return shape.move(state.getOffset(pos)); } @Override protected boolean isCollisionShapeFullBlock(BlockState state, BlockGetter level, BlockPos pos) { return false; } @Override protected float getMaxHorizontalOffset() { return MAX_HORIZONTAL_OFFSET; } @Override public void onBrokenAfterFall(Level level, BlockPos pos, FallingBlockEntity entity) { if (!entity.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 fallPos = pos.mutable(); BlockState fallState = state; while (PointedDripstoneBlock.isStalactite(fallState)) { FallingBlockEntity entity = FallingBlockEntity.fall(level, fallPos, fallState); if (PointedDripstoneBlock.isTip(fallState, true)) { int size = Math.max(1 + pos.getY() - fallPos.getY(), 6); float damagePerFallDistance = 1.0f * (float)size; entity.setHurtsEntities(damagePerFallDistance, 40); break; } fallPos.move(Direction.DOWN); fallState = level.getBlockState(fallPos); } } @VisibleForTesting public static void growStalactiteOrStalagmiteIfPossible(BlockState stalactiteStartState, ServerLevel level, BlockPos stalactiteStartPos, RandomSource random) { BlockState stateAbove; BlockState rootState = level.getBlockState(stalactiteStartPos.above(1)); if (!PointedDripstoneBlock.canGrow(rootState, stateAbove = level.getBlockState(stalactiteStartPos.above(2)))) { return; } BlockPos stalactiteTipPos = PointedDripstoneBlock.findTip(stalactiteStartState, level, stalactiteStartPos, 7, false); if (stalactiteTipPos == null) { return; } BlockState stalactiteTipState = level.getBlockState(stalactiteTipPos); if (!PointedDripstoneBlock.canDrip(stalactiteTipState) || !PointedDripstoneBlock.canTipGrow(stalactiteTipState, level, stalactiteTipPos)) { return; } if (random.nextBoolean()) { PointedDripstoneBlock.grow(level, stalactiteTipPos, Direction.DOWN); } else { PointedDripstoneBlock.growStalagmiteBelow(level, stalactiteTipPos); } } private static void growStalagmiteBelow(ServerLevel level, BlockPos posAboveStalagmite) { BlockPos.MutableBlockPos pos = posAboveStalagmite.mutable(); for (int i = 0; i < 10; ++i) { pos.move(Direction.DOWN); BlockState state = level.getBlockState(pos); if (!state.getFluidState().isEmpty()) { return; } if (PointedDripstoneBlock.isUnmergedTipWithDirection(state, Direction.UP) && PointedDripstoneBlock.canTipGrow(state, level, pos)) { PointedDripstoneBlock.grow(level, pos, Direction.UP); return; } if (PointedDripstoneBlock.isValidPointedDripstonePlacement(level, pos, Direction.UP) && !level.isWaterAt((BlockPos)pos.below())) { PointedDripstoneBlock.grow(level, (BlockPos)pos.below(), Direction.UP); return; } if (PointedDripstoneBlock.canDripThrough(level, pos, state)) continue; return; } } private static void grow(ServerLevel level, BlockPos growFromPos, Direction growToDirection) { BlockPos targetPos = growFromPos.relative(growToDirection); BlockState existingStateAtTargetPos = level.getBlockState(targetPos); if (PointedDripstoneBlock.isUnmergedTipWithDirection(existingStateAtTargetPos, growToDirection.getOpposite())) { PointedDripstoneBlock.createMergedTips(existingStateAtTargetPos, level, targetPos); } else if (existingStateAtTargetPos.isAir() || existingStateAtTargetPos.is(Blocks.WATER)) { PointedDripstoneBlock.createDripstone(level, targetPos, growToDirection, DripstoneThickness.TIP); } } private static void createDripstone(LevelAccessor level, BlockPos pos, Direction direction, DripstoneThickness thickness) { BlockState state = (BlockState)((BlockState)((BlockState)Blocks.POINTED_DRIPSTONE.defaultBlockState().setValue(TIP_DIRECTION, direction)).setValue(THICKNESS, thickness)).setValue(WATERLOGGED, level.getFluidState(pos).getType() == Fluids.WATER); level.setBlock(pos, state, 3); } private static void createMergedTips(BlockState tipState, LevelAccessor level, BlockPos tipPos) { BlockPos stalactitePos; BlockPos stalagmitePos; if (tipState.getValue(TIP_DIRECTION) == Direction.UP) { stalagmitePos = tipPos; stalactitePos = tipPos.above(); } else { stalactitePos = tipPos; stalagmitePos = tipPos.below(); } PointedDripstoneBlock.createDripstone(level, stalactitePos, Direction.DOWN, DripstoneThickness.TIP_MERGE); PointedDripstoneBlock.createDripstone(level, stalagmitePos, Direction.UP, DripstoneThickness.TIP_MERGE); } public static void spawnDripParticle(Level level, BlockPos stalactiteTipPos, BlockState stalactiteTipState) { PointedDripstoneBlock.getFluidAboveStalactite(level, stalactiteTipPos, stalactiteTipState).ifPresent(fluidAbove -> PointedDripstoneBlock.spawnDripParticle(level, stalactiteTipPos, stalactiteTipState, fluidAbove.fluid, fluidAbove.pos)); } private static void spawnDripParticle(Level level, BlockPos stalactiteTipPos, BlockState stalactiteTipState, Fluid fluidAbove, BlockPos posAbove) { Vec3 offset = stalactiteTipState.getOffset(stalactiteTipPos); double PIXEL_SIZE = 0.0625; double x = (double)stalactiteTipPos.getX() + 0.5 + offset.x; double y = (double)stalactiteTipPos.getY() + STALACTITE_DRIP_START_PIXEL - 0.0625; double z = (double)stalactiteTipPos.getZ() + 0.5 + offset.z; ParticleOptions dripParticle = PointedDripstoneBlock.getDripParticle(level, fluidAbove, posAbove); level.addParticle(dripParticle, x, y, z, 0.0, 0.0, 0.0); } private static @Nullable BlockPos findTip(BlockState dripstoneState, LevelAccessor level, BlockPos dripstonePos, int maxSearchLength, boolean includeMergedTip) { if (PointedDripstoneBlock.isTip(dripstoneState, includeMergedTip)) { return dripstonePos; } Direction searchDirection = dripstoneState.getValue(TIP_DIRECTION); BiPredicate pathPredicate = (pos, state) -> state.is(Blocks.POINTED_DRIPSTONE) && state.getValue(TIP_DIRECTION) == searchDirection; return PointedDripstoneBlock.findBlockVertical(level, dripstonePos, searchDirection.getAxisDirection(), pathPredicate, dripstone -> PointedDripstoneBlock.isTip(dripstone, includeMergedTip), maxSearchLength).orElse(null); } private static @Nullable Direction calculateTipDirection(LevelReader level, BlockPos pos, Direction defaultTipDirection) { Direction tipDirection; if (PointedDripstoneBlock.isValidPointedDripstonePlacement(level, pos, defaultTipDirection)) { tipDirection = defaultTipDirection; } else if (PointedDripstoneBlock.isValidPointedDripstonePlacement(level, pos, defaultTipDirection.getOpposite())) { tipDirection = defaultTipDirection.getOpposite(); } else { return null; } return tipDirection; } private static DripstoneThickness calculateDripstoneThickness(LevelReader level, BlockPos pos, Direction tipDirection, boolean mergeOpposingTips) { Direction baseDirection = tipDirection.getOpposite(); BlockState inFrontState = level.getBlockState(pos.relative(tipDirection)); if (PointedDripstoneBlock.isPointedDripstoneWithDirection(inFrontState, baseDirection)) { if (mergeOpposingTips || inFrontState.getValue(THICKNESS) == DripstoneThickness.TIP_MERGE) { return DripstoneThickness.TIP_MERGE; } return DripstoneThickness.TIP; } if (!PointedDripstoneBlock.isPointedDripstoneWithDirection(inFrontState, tipDirection)) { return DripstoneThickness.TIP; } DripstoneThickness inFrontThickness = inFrontState.getValue(THICKNESS); if (inFrontThickness == DripstoneThickness.TIP || inFrontThickness == DripstoneThickness.TIP_MERGE) { return DripstoneThickness.FRUSTUM; } BlockState behindState = level.getBlockState(pos.relative(baseDirection)); if (!PointedDripstoneBlock.isPointedDripstoneWithDirection(behindState, tipDirection)) { return DripstoneThickness.BASE; } return DripstoneThickness.MIDDLE; } public static boolean canDrip(BlockState state) { return PointedDripstoneBlock.isStalactite(state) && state.getValue(THICKNESS) == DripstoneThickness.TIP && state.getValue(WATERLOGGED) == false; } private static boolean canTipGrow(BlockState tipState, ServerLevel level, BlockPos tipPos) { Direction growDirection = tipState.getValue(TIP_DIRECTION); BlockPos growPos = tipPos.relative(growDirection); BlockState stateAtGrowPos = level.getBlockState(growPos); if (!stateAtGrowPos.getFluidState().isEmpty()) { return false; } if (stateAtGrowPos.isAir()) { return true; } return PointedDripstoneBlock.isUnmergedTipWithDirection(stateAtGrowPos, growDirection.getOpposite()); } private static Optional findRootBlock(Level level, BlockPos pos, BlockState dripStoneState, int maxSearchLength) { Direction tipDirection = dripStoneState.getValue(TIP_DIRECTION); BiPredicate pathPredicate = (pathPos, state) -> state.is(Blocks.POINTED_DRIPSTONE) && state.getValue(TIP_DIRECTION) == tipDirection; return PointedDripstoneBlock.findBlockVertical(level, pos, tipDirection.getOpposite().getAxisDirection(), pathPredicate, state -> !state.is(Blocks.POINTED_DRIPSTONE), maxSearchLength); } private static boolean isValidPointedDripstonePlacement(LevelReader level, BlockPos pos, Direction tipDirection) { BlockPos behindPos = pos.relative(tipDirection.getOpposite()); BlockState behindState = level.getBlockState(behindPos); return behindState.isFaceSturdy(level, behindPos, tipDirection) || PointedDripstoneBlock.isPointedDripstoneWithDirection(behindState, tipDirection); } private static boolean isTip(BlockState state, boolean includeMergedTip) { if (!state.is(Blocks.POINTED_DRIPSTONE)) { return false; } DripstoneThickness thickness = state.getValue(THICKNESS); return thickness == DripstoneThickness.TIP || includeMergedTip && thickness == DripstoneThickness.TIP_MERGE; } private static boolean isUnmergedTipWithDirection(BlockState state, Direction tipDirection) { return PointedDripstoneBlock.isTip(state, false) && state.getValue(TIP_DIRECTION) == tipDirection; } private static boolean isStalactite(BlockState state) { return PointedDripstoneBlock.isPointedDripstoneWithDirection(state, Direction.DOWN); } private static boolean isStalagmite(BlockState state) { return PointedDripstoneBlock.isPointedDripstoneWithDirection(state, Direction.UP); } private static boolean isStalactiteStartPos(BlockState state, LevelReader level, BlockPos pos) { return PointedDripstoneBlock.isStalactite(state) && !level.getBlockState(pos.above()).is(Blocks.POINTED_DRIPSTONE); } @Override protected boolean isPathfindable(BlockState state, PathComputationType type) { return false; } private static boolean isPointedDripstoneWithDirection(BlockState blockState, Direction tipDirection) { return blockState.is(Blocks.POINTED_DRIPSTONE) && blockState.getValue(TIP_DIRECTION) == tipDirection; } private static @Nullable BlockPos findFillableCauldronBelowStalactiteTip(Level level, BlockPos stalactiteTipPos, Fluid fluid) { Predicate cauldronPredicate = state -> state.getBlock() instanceof AbstractCauldronBlock && ((AbstractCauldronBlock)state.getBlock()).canReceiveStalactiteDrip(fluid); BiPredicate pathPredicate = (pos, state) -> PointedDripstoneBlock.canDripThrough(level, pos, state); return PointedDripstoneBlock.findBlockVertical(level, stalactiteTipPos, Direction.DOWN.getAxisDirection(), pathPredicate, cauldronPredicate, 11).orElse(null); } public static @Nullable BlockPos findStalactiteTipAboveCauldron(Level level, BlockPos cauldronPos) { BiPredicate pathPredicate = (pos, state) -> PointedDripstoneBlock.canDripThrough(level, pos, state); return PointedDripstoneBlock.findBlockVertical(level, cauldronPos, Direction.UP.getAxisDirection(), pathPredicate, PointedDripstoneBlock::canDrip, 11).orElse(null); } public static Fluid getCauldronFillFluidType(ServerLevel level, BlockPos stalactitePos) { return PointedDripstoneBlock.getFluidAboveStalactite(level, stalactitePos, level.getBlockState(stalactitePos)).map(fluidSource -> fluidSource.fluid).filter(PointedDripstoneBlock::canFillCauldron).orElse(Fluids.EMPTY); } private static Optional getFluidAboveStalactite(Level level, BlockPos stalactitePos, BlockState stalactiteState) { if (!PointedDripstoneBlock.isStalactite(stalactiteState)) { return Optional.empty(); } return PointedDripstoneBlock.findRootBlock(level, stalactitePos, stalactiteState, 11).map(rootPos -> { BlockPos abovePos = rootPos.above(); BlockState aboveState = level.getBlockState(abovePos); Fluid fluid = aboveState.is(Blocks.MUD) && level.environmentAttributes().getValue(EnvironmentAttributes.WATER_EVAPORATES, abovePos) == false ? Fluids.WATER : level.getFluidState(abovePos).getType(); return new FluidInfo(abovePos, fluid, aboveState); }); } private static boolean canFillCauldron(Fluid fluidAbove) { return fluidAbove == Fluids.LAVA || fluidAbove == Fluids.WATER; } private static boolean canGrow(BlockState rootState, BlockState aboveState) { return rootState.is(Blocks.DRIPSTONE_BLOCK) && aboveState.is(Blocks.WATER) && aboveState.getFluidState().isSource(); } private static ParticleOptions getDripParticle(Level level, Fluid fluidAbove, BlockPos posAbove) { if (fluidAbove.isSame(Fluids.EMPTY)) { return level.environmentAttributes().getValue(EnvironmentAttributes.DEFAULT_DRIPSTONE_PARTICLE, posAbove); } return fluidAbove.is(FluidTags.LAVA) ? ParticleTypes.DRIPPING_DRIPSTONE_LAVA : ParticleTypes.DRIPPING_DRIPSTONE_WATER; } private static Optional findBlockVertical(LevelAccessor level, BlockPos pos, Direction.AxisDirection axisDirection, BiPredicate pathPredicate, Predicate targetPredicate, int maxSteps) { Direction direction = Direction.get(axisDirection, Direction.Axis.Y); BlockPos.MutableBlockPos mutablePos = pos.mutable(); for (int i = 1; i < maxSteps; ++i) { mutablePos.move(direction); BlockState state = level.getBlockState(mutablePos); if (targetPredicate.test(state)) { return Optional.of(mutablePos.immutable()); } if (!level.isOutsideBuildHeight(mutablePos.getY()) && pathPredicate.test(mutablePos, state)) continue; return Optional.empty(); } return Optional.empty(); } private static boolean canDripThrough(BlockGetter level, BlockPos pos, BlockState state) { if (state.isAir()) { return true; } if (state.isSolidRender()) { return false; } if (!state.getFluidState().isEmpty()) { return false; } VoxelShape collisionShape = state.getCollisionShape(level, pos); return !Shapes.joinIsNotEmpty(REQUIRED_SPACE_TO_DRIP_THROUGH_NON_SOLID_BLOCK, collisionShape, BooleanOp.AND); } record FluidInfo(BlockPos pos, Fluid fluid, BlockState sourceState) { } }