2025-11-24 22:52:51 +03:00

581 lines
30 KiB
Java

/*
* 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<PointedDripstoneBlock> CODEC = PointedDripstoneBlock.simpleCodec(PointedDripstoneBlock::new);
public static final EnumProperty<Direction> 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.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<PointedDripstoneBlock> 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<Block, BlockState> 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> 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<BlockPos, BlockState> 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<BlockPos> findRootBlock(Level level, BlockPos pos, BlockState dripStoneState, int maxSearchLength) {
Direction tipDirection = dripStoneState.getValue(TIP_DIRECTION);
BiPredicate<BlockPos, BlockState> 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<BlockState> cauldronPredicate = state -> state.getBlock() instanceof AbstractCauldronBlock && ((AbstractCauldronBlock)state.getBlock()).canReceiveStalactiteDrip(fluid);
BiPredicate<BlockPos, BlockState> 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<BlockPos, BlockState> 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<FluidInfo> 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<BlockPos> findBlockVertical(LevelAccessor level, BlockPos pos, Direction.AxisDirection axisDirection, BiPredicate<BlockPos, BlockState> pathPredicate, Predicate<BlockState> 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) {
}
}