581 lines
30 KiB
Java
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) {
|
|
}
|
|
}
|
|
|