package ru.betterend.blocks; import java.util.EnumMap; import java.util.List; import java.util.Random; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos.MutableBlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Direction.Axis; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; 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.WorldGenLevel; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Mirror; import net.minecraft.world.level.block.Rotation; 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.DirectionProperty; import net.minecraft.world.level.block.state.properties.EnumProperty; 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.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import ru.bclib.api.TagAPI; import ru.bclib.util.BlocksHelper; import ru.bclib.util.MHelper; import ru.betterend.blocks.BlockProperties.CactusBottom; import ru.betterend.blocks.BlockProperties.TripleShape; import ru.betterend.blocks.basis.BlockBaseNotFull; import ru.betterend.client.render.ERenderLayer; import ru.betterend.interfaces.IRenderTypeable; import ru.betterend.registry.EndBlocks; public class NeonCactusPlantBlock extends BlockBaseNotFull implements SimpleWaterloggedBlock, IRenderTypeable { public static final EnumProperty SHAPE = BlockProperties.TRIPLE_SHAPE; public static final EnumProperty CACTUS_BOTTOM = BlockProperties.CACTUS_BOTTOM; public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final DirectionProperty FACING = BlockStateProperties.FACING; private static final EnumMap BIG_SHAPES_OPEN = Maps.newEnumMap(Direction.class); private static final EnumMap MEDIUM_SHAPES_OPEN = Maps.newEnumMap(Direction.class); private static final EnumMap SMALL_SHAPES_OPEN = Maps.newEnumMap(Direction.class); private static final EnumMap BIG_SHAPES = Maps.newEnumMap(Axis.class); private static final EnumMap MEDIUM_SHAPES = Maps.newEnumMap(Axis.class); private static final EnumMap SMALL_SHAPES = Maps.newEnumMap(Axis.class); private static final int MAX_LENGTH = 12; public NeonCactusPlantBlock() { super(FabricBlockSettings.copyOf(Blocks.CACTUS).luminance(15).randomTicks()); registerDefaultState(defaultBlockState().setValue(WATERLOGGED, false).setValue(FACING, Direction.UP).setValue(SHAPE, TripleShape.TOP)); } @Override protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { stateManager.add(SHAPE, CACTUS_BOTTOM, WATERLOGGED, FACING); } @Override public BlockState getStateForPlacement(BlockPlaceContext ctx) { LevelAccessor world = ctx.getLevel(); BlockPos pos = ctx.getClickedPos(); Direction dir = ctx.getClickedFace(); BlockState down = world.getBlockState(pos.relative(dir.getOpposite())); BlockState state = this.defaultBlockState().setValue(WATERLOGGED, world.getFluidState(pos).getType() == Fluids.WATER).setValue(FACING, ctx.getClickedFace()); if (down.is(Blocks.END_STONE) || down.is(EndBlocks.ENDSTONE_DUST)) { state = state.setValue(CACTUS_BOTTOM, CactusBottom.SAND); } else if (down.is(EndBlocks.END_MOSS)) { state = state.setValue(CACTUS_BOTTOM, CactusBottom.MOSS); } else { state = state.setValue(CACTUS_BOTTOM, CactusBottom.EMPTY); } return state; } @Override public BlockState rotate(BlockState state, Rotation rotation) { return BlocksHelper.rotateHorizontal(state, rotation, FACING); } @Override public BlockState mirror(BlockState state, Mirror mirror) { return BlocksHelper.mirrorHorizontal(state, mirror, FACING); } @Override public FluidState getFluidState(BlockState state) { return (Boolean) state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); } @Override public BlockState updateShape(BlockState state, Direction direction, BlockState newState, LevelAccessor world, BlockPos pos, BlockPos posFrom) { world.getBlockTicks().scheduleTick(pos, this, 2); if ((Boolean) state.getValue(WATERLOGGED)) { world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); } Direction dir = state.getValue(FACING); BlockState downState = world.getBlockState(pos.relative(dir.getOpposite())); if (downState.is(Blocks.END_STONE) || downState.is(EndBlocks.ENDSTONE_DUST)) { state = state.setValue(CACTUS_BOTTOM, CactusBottom.SAND); } else if (downState.is(EndBlocks.END_MOSS)) { state = state.setValue(CACTUS_BOTTOM, CactusBottom.MOSS); } else { state = state.setValue(CACTUS_BOTTOM, CactusBottom.EMPTY); } return state; } @Override public void tick(BlockState blockState, ServerLevel serverLevel, BlockPos blockPos, Random random) { if (!blockState.canSurvive(serverLevel, blockPos)) { serverLevel.destroyBlock(blockPos, true, null, 1); } } @Override public ERenderLayer getRenderLayer() { return ERenderLayer.CUTOUT; } @Override public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { TripleShape shape = state.getValue(SHAPE); Direction dir = state.getValue(FACING); BlockState next = view.getBlockState(pos.relative(dir)); if (next.is(this)) { Axis axis = dir.getAxis(); if (shape == TripleShape.BOTTOM) { return BIG_SHAPES.get(axis); } return shape == TripleShape.MIDDLE ? MEDIUM_SHAPES.get(axis) : SMALL_SHAPES.get(axis); } else { if (shape == TripleShape.BOTTOM) { return BIG_SHAPES_OPEN.get(dir); } return shape == TripleShape.MIDDLE ? MEDIUM_SHAPES_OPEN.get(dir) : SMALL_SHAPES_OPEN.get(dir); } } @Override public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { Direction dir = state.getValue(FACING); BlockPos supportPos = pos.relative(dir.getOpposite()); BlockState support = level.getBlockState(supportPos); return support.is(this) || support.isFaceSturdy(level, supportPos, dir); } @Override public void randomTick(BlockState state, ServerLevel world, BlockPos pos, Random random) { if (!this.canSurvive(state, world, pos) || random.nextInt(8) > 0) { return; } Direction dir = state.getValue(FACING); if (!world.isEmptyBlock(pos.relative(dir))) { return; } int length = getLength(state, world, pos, MAX_LENGTH); if (length < 0 || length > MAX_LENGTH - 1) { return; } if (dir.getAxis().isHorizontal()) { int horizontal = getHorizontal(state, world, pos, 2); if (horizontal > random.nextInt(2)) { dir = Direction.UP; if (!world.getBlockState(pos.above()).isAir()) { return; } } } else if (length > 1 && world.getBlockState(pos.relative(dir.getOpposite())).is(this)) { Direction side = getSideDirection(world, pos, state, dir, random); BlockPos sidePos = pos.relative(side); if (world.isEmptyBlock(sidePos)) { BlockState placement = state.setValue(SHAPE, TripleShape.TOP).setValue(CACTUS_BOTTOM, CactusBottom.EMPTY).setValue(WATERLOGGED, false).setValue(FACING, side); BlocksHelper.setWithoutUpdate(world, sidePos, placement); } } BlockState placement = state.setValue(SHAPE, TripleShape.TOP).setValue(CACTUS_BOTTOM, CactusBottom.EMPTY).setValue(WATERLOGGED, false).setValue(FACING, dir); BlocksHelper.setWithoutUpdate(world, pos.relative(dir), placement); mutateStem(placement, world, pos, MAX_LENGTH); } public void growPlant(WorldGenLevel world, BlockPos pos, Random random) { growPlant(world, pos, random, MHelper.randRange(MAX_LENGTH >> 1, MAX_LENGTH, random)); } public void growPlant(WorldGenLevel world, BlockPos pos, Random random, int iterations) { BlockState state = defaultBlockState(); BlockState downState = world.getBlockState(pos.below()); if (downState.is(Blocks.END_STONE) || downState.is(EndBlocks.ENDSTONE_DUST)) { state = state.setValue(CACTUS_BOTTOM, CactusBottom.SAND); } else if (downState.is(EndBlocks.END_MOSS)) { state = state.setValue(CACTUS_BOTTOM, CactusBottom.MOSS); } else { state = state.setValue(CACTUS_BOTTOM, CactusBottom.EMPTY); } BlocksHelper.setWithoutUpdate(world, pos, state); List ends = Lists.newArrayList(pos.mutable()); for (int i = 0; i < iterations; i++) { int count = ends.size(); for (int n = 0; n < count; n++) { if (!growIteration(world, ends.get(n), random, ends, i)) { ends.remove(n); count--; n--; } } } } private boolean growIteration(WorldGenLevel world, MutableBlockPos pos, Random random, List ends, int length) { BlockState state = world.getBlockState(pos); if (!state.is(this)) { return false; } Direction dir = state.getValue(FACING); if (!world.isEmptyBlock(pos.relative(dir))) { return false; } if (dir.getAxis().isHorizontal()) { int horizontal = getHorizontal(state, world, pos, 2); if (horizontal > random.nextInt(2)) { dir = Direction.UP; if (!world.getBlockState(pos.above()).isAir()) { return false; } } } else if (length > 1 && world.getBlockState(pos.below()).is(this)) { Direction side = getSideDirection(world, pos, state, dir, random); BlockPos sidePos = pos.relative(side); if (world.isEmptyBlock(sidePos)) { BlockState placement = state.setValue(SHAPE, TripleShape.TOP).setValue(CACTUS_BOTTOM, CactusBottom.EMPTY).setValue(WATERLOGGED, false).setValue(FACING, side); BlocksHelper.setWithoutUpdate(world, sidePos, placement); ends.add(sidePos.mutable()); } } BlockState placement = state.setValue(SHAPE, TripleShape.TOP).setValue(CACTUS_BOTTOM, CactusBottom.EMPTY).setValue(WATERLOGGED, false).setValue(FACING, dir); BlocksHelper.setWithoutUpdate(world, pos.relative(dir), placement); mutateStem(placement, world, pos, MAX_LENGTH); pos.move(dir); return true; } private Direction getSideDirection(WorldGenLevel world, BlockPos pos, BlockState iterState, Direction dir, Random random) { MutableBlockPos iterPos = pos.mutable(); Direction startDir = dir; Direction lastDir = null; while (iterState.is(this) && startDir.getAxis().isVertical()) { startDir = iterState.getValue(FACING); if (lastDir == null) { for (Direction side: BlocksHelper.HORIZONTAL) { BlockState sideState = world.getBlockState(iterPos.relative(side)); if (sideState.is(this)) { Direction sideDir = sideState.getValue(FACING); if (sideDir != side) { continue; } lastDir = sideDir; } } } iterPos.move(dir); iterState = world.getBlockState(iterPos); } Direction side = lastDir == null ? BlocksHelper.randomHorizontal(random) : lastDir.getClockWise(); if (side.getOpposite() == startDir) { side = side.getOpposite(); } return side; } @Override public boolean isPathfindable(BlockState blockState, BlockGetter blockGetter, BlockPos blockPos, PathComputationType pathComputationType) { return false; } @Override public void entityInside(BlockState blockState, Level level, BlockPos blockPos, Entity entity) { entity.hurt(DamageSource.CACTUS, 1.0F); } private int getLength(BlockState state, ServerLevel world, BlockPos pos, int max) { int length = 0; Direction dir = state.getValue(FACING).getOpposite(); MutableBlockPos mut = new MutableBlockPos().set(pos); for (int i = 0; i < max; i++) { mut.move(dir); state = world.getBlockState(mut); if (!state.is(this)) { if (!state.is(TagAPI.END_GROUND)) { length = -1; } break; } dir = state.getValue(FACING).getOpposite(); length ++; } return length; } private int getHorizontal(BlockState state, WorldGenLevel world, BlockPos pos, int max) { int count = 0; Direction dir = state.getValue(FACING).getOpposite(); MutableBlockPos mut = new MutableBlockPos().set(pos); for (int i = 0; i < max; i++) { mut.move(dir); state = world.getBlockState(mut); if (!state.is(this)) { break; } dir = state.getValue(FACING).getOpposite(); if (dir.getStepY() != 0) { break; } count ++; } return count; } private void mutateStem(BlockState state, WorldGenLevel world, BlockPos pos, int max) { Direction dir = state.getValue(FACING).getOpposite(); MutableBlockPos mut = new MutableBlockPos().set(pos); for (int i = 0; i < max; i++) { mut.move(dir); state = world.getBlockState(mut); if (!state.is(this)) { return; } int size = (i + 2) * 3 / max; int src = state.getValue(SHAPE).getIndex(); dir = state.getValue(FACING).getOpposite(); if (src < size) { TripleShape shape = TripleShape.fromIndex(size); BlocksHelper.setWithoutUpdate(world, mut, state.setValue(SHAPE, shape)); } } } static { BIG_SHAPES.put(Axis.X, Block.box(0, 2, 2, 16, 14, 14)); BIG_SHAPES.put(Axis.Y, Block.box(2, 0, 2, 14, 16, 14)); BIG_SHAPES.put(Axis.Z, Block.box(2, 2, 0, 14, 14, 16)); MEDIUM_SHAPES.put(Axis.X, Block.box(0, 3, 3, 16, 13, 13)); MEDIUM_SHAPES.put(Axis.Y, Block.box(3, 0, 3, 13, 16, 13)); MEDIUM_SHAPES.put(Axis.Z, Block.box(3, 3, 0, 13, 13, 16)); SMALL_SHAPES.put(Axis.X, Block.box(0, 4, 4, 16, 12, 12)); SMALL_SHAPES.put(Axis.Y, Block.box(4, 0, 4, 12, 16, 12)); SMALL_SHAPES.put(Axis.Z, Block.box(4, 4, 0, 12, 12, 16)); BIG_SHAPES_OPEN.put(Direction.UP, Block.box(2, 0, 2, 14, 14, 14)); BIG_SHAPES_OPEN.put(Direction.DOWN, Block.box(2, 2, 2, 14, 16, 14)); BIG_SHAPES_OPEN.put(Direction.NORTH, Block.box(2, 2, 2, 14, 14, 16)); BIG_SHAPES_OPEN.put(Direction.SOUTH, Block.box(2, 2, 0, 14, 14, 14)); BIG_SHAPES_OPEN.put(Direction.WEST, Block.box(2, 2, 2, 16, 14, 14)); BIG_SHAPES_OPEN.put(Direction.EAST, Block.box(0, 2, 2, 14, 14, 14)); MEDIUM_SHAPES_OPEN.put(Direction.UP, Block.box(3, 0, 3, 13, 13, 13)); MEDIUM_SHAPES_OPEN.put(Direction.DOWN, Block.box(3, 3, 3, 13, 16, 13)); MEDIUM_SHAPES_OPEN.put(Direction.NORTH, Block.box(3, 3, 3, 13, 13, 16)); MEDIUM_SHAPES_OPEN.put(Direction.SOUTH, Block.box(3, 3, 0, 13, 13, 13)); MEDIUM_SHAPES_OPEN.put(Direction.WEST, Block.box(3, 3, 3, 16, 13, 13)); MEDIUM_SHAPES_OPEN.put(Direction.EAST, Block.box(0, 3, 3, 13, 13, 13)); SMALL_SHAPES_OPEN.put(Direction.UP, Block.box(4, 0, 4, 12, 12, 12)); SMALL_SHAPES_OPEN.put(Direction.DOWN, Block.box(4, 4, 4, 12, 16, 12)); SMALL_SHAPES_OPEN.put(Direction.NORTH, Block.box(4, 4, 4, 12, 12, 16)); SMALL_SHAPES_OPEN.put(Direction.SOUTH, Block.box(4, 4, 0, 12, 12, 12)); SMALL_SHAPES_OPEN.put(Direction.WEST, Block.box(4, 4, 4, 16, 12, 12)); SMALL_SHAPES_OPEN.put(Direction.EAST, Block.box(0, 4, 4, 12, 12, 12)); } }