package ru.bclib.blocks; import java.util.Map; import java.util.Optional; import org.jetbrains.annotations.Nullable; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.resources.model.BlockModelRotation; import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos.MutableBlockPos; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.ItemStack; 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.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.LiquidBlockContainer; import net.minecraft.world.level.block.SimpleWaterloggedBlock; 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.IntegerProperty; 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.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.interfaces.IRenderTyped; public class StalactiteBlock extends BaseBlockNotFull implements SimpleWaterloggedBlock, LiquidBlockContainer, IRenderTyped { public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final BooleanProperty IS_FLOOR = BlockProperties.IS_FLOOR; public static final IntegerProperty SIZE = BlockProperties.SIZE; private static final VoxelShape[] SHAPES; public StalactiteBlock(Block source) { super(FabricBlockSettings.copy(source).noOcclusion()); this.registerDefaultState(getStateDefinition().any().setValue(SIZE, 0).setValue(IS_FLOOR, true).setValue(WATERLOGGED, false)); } @Override protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { stateManager.add(WATERLOGGED, IS_FLOOR, SIZE); } @Override public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { return SHAPES[state.getValue(SIZE)]; } @Override public BlockState getStateForPlacement(BlockPlaceContext ctx) { LevelReader world = ctx.getLevel(); BlockPos pos = ctx.getClickedPos(); Direction dir = ctx.getClickedFace(); boolean water = world.getFluidState(pos).getType() == Fluids.WATER; if (dir == Direction.DOWN) { if (isThis(world, pos.above()) || canSupportCenter(world, pos.above(), Direction.DOWN)) { return defaultBlockState().setValue(IS_FLOOR, false).setValue(WATERLOGGED, water); } else if (isThis(world, pos.below()) || canSupportCenter(world, pos.below(), Direction.UP)) { return defaultBlockState().setValue(IS_FLOOR, true).setValue(WATERLOGGED, water); } else { return null; } } else { if (isThis(world, pos.below()) || canSupportCenter(world, pos.below(), Direction.UP)) { return defaultBlockState().setValue(IS_FLOOR, true).setValue(WATERLOGGED, water); } else if (isThis(world, pos.above()) || canSupportCenter(world, pos.above(), Direction.DOWN)) { return defaultBlockState().setValue(IS_FLOOR, false).setValue(WATERLOGGED, water); } else { return null; } } } @Override public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) { boolean hasUp = isThis(world, pos.above()); boolean hasDown = isThis(world, pos.below()); MutableBlockPos mut = new MutableBlockPos(); if (hasUp && hasDown) { boolean floor = state.getValue(IS_FLOOR); BlockPos second = floor ? pos.above() : pos.below(); BlockState bState = world.getBlockState(second); world.setBlockAndUpdate(pos, state.setValue(SIZE, 1).setValue(IS_FLOOR, floor)); world.setBlockAndUpdate(second, bState.setValue(SIZE, 1).setValue(IS_FLOOR, !floor)); bState = state; int startSize = floor ? 1 : 2; mut.set(pos.getX(), pos.getY() + 1, pos.getZ()); for (int i = 0; i < 8 && isThis(bState); i++) { world.setBlockAndUpdate(mut, bState.setValue(SIZE, startSize++).setValue(IS_FLOOR, false)); mut.setY(mut.getY() + 1); bState = world.getBlockState(mut); } bState = state; startSize = floor ? 2 : 1; mut.set(pos.getX(), pos.getY() - 1, pos.getZ()); for (int i = 0; i < 8 && isThis(bState); i++) { world.setBlockAndUpdate(mut, bState.setValue(SIZE, startSize++).setValue(IS_FLOOR, true)); mut.setY(mut.getY() - 1); bState = world.getBlockState(mut); } } else if (hasDown) { mut.setX(pos.getX()); mut.setZ(pos.getZ()); for (int i = 1; i < 8; i++) { mut.setY(pos.getY() - i); if (isThis(world, mut)) { BlockState state2 = world.getBlockState(mut); int size = state2.getValue(SIZE); if (size < i) { world.setBlockAndUpdate(mut, state2.setValue(SIZE, i).setValue(IS_FLOOR, true)); } else { break; } } else { break; } } } else if (hasUp) { mut.setX(pos.getX()); mut.setZ(pos.getZ()); for (int i = 1; i < 8; i++) { mut.setY(pos.getY() + i); if (isThis(world, mut)) { BlockState state2 = world.getBlockState(mut); int size = state2.getValue(SIZE); if (size < i) { world.setBlockAndUpdate(mut, state2.setValue(SIZE, i).setValue(IS_FLOOR, false)); } else { break; } } else { break; } } } } private boolean isThis(LevelReader world, BlockPos pos) { return isThis(world.getBlockState(pos)); } private boolean isThis(BlockState state) { return state.getBlock() instanceof StalactiteBlock; } @Override public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { if (!canSurvive(state, world, pos)) { return Blocks.AIR.defaultBlockState(); } return state; } @Override public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { int size = state.getValue(SIZE); return checkUp(world, pos, size) || checkDown(world, pos, size); } private boolean checkUp(BlockGetter world, BlockPos pos, int size) { BlockPos p = pos.above(); BlockState state = world.getBlockState(p); return (isThis(state) && state.getValue(SIZE) >= size) || state.isCollisionShapeFullBlock(world, p); } private boolean checkDown(BlockGetter world, BlockPos pos, int size) { BlockPos p = pos.below(); BlockState state = world.getBlockState(p); return (isThis(state) && state.getValue(SIZE) >= size) || state.isCollisionShapeFullBlock(world, p); } @Override @Environment(EnvType.CLIENT) public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CROSS_SHADED, resourceLocation); return ModelsHelper.fromPattern(pattern); } @Override @Environment(EnvType.CLIENT) public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { BlockModelRotation rotation = blockState.getValue(IS_FLOOR) ? BlockModelRotation.X0_Y0 : BlockModelRotation.X180_Y0; ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), stateId.getPath() + "_" + blockState.getValue(SIZE)); registerBlockModel(modelId, modelId, blockState, modelCache); return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); } @Override public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { return false; } @Override public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { return false; } @Override public FluidState getFluidState(BlockState state) { return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : Fluids.EMPTY.defaultFluidState(); } @Override public BCLRenderLayer getRenderLayer() { return BCLRenderLayer.CUTOUT; } static { float end = 2F / 8F; float start = 5F / 8F; SHAPES = new VoxelShape[8]; for (int i = 0; i < 8; i++) { int side = Mth.floor(Mth.lerp(i / 7F, start, end) * 8F + 0.5F); SHAPES[i] = Block.box(side, 0, side, 16 - side, 16, 16 - side); } } }