package net.minecraft.world.level.block; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.Map; import java.util.function.BiConsumer; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.ScheduledTickAccess; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition.Builder; import net.minecraft.world.level.block.state.properties.BlockSetType; 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.DoorHingeSide; import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.level.redstone.Orientation; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.jetbrains.annotations.Nullable; public class DoorBlock extends Block { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group(BlockSetType.CODEC.fieldOf("block_set_type").forGetter(DoorBlock::type), propertiesCodec()).apply(instance, DoorBlock::new) ); public static final EnumProperty FACING = HorizontalDirectionalBlock.FACING; public static final EnumProperty HALF = BlockStateProperties.DOUBLE_BLOCK_HALF; public static final EnumProperty HINGE = BlockStateProperties.DOOR_HINGE; public static final BooleanProperty OPEN = BlockStateProperties.OPEN; public static final BooleanProperty POWERED = BlockStateProperties.POWERED; private static final Map SHAPES = Shapes.rotateHorizontal(Block.boxZ(16.0, 13.0, 16.0)); private final BlockSetType type; @Override public MapCodec codec() { return CODEC; } protected DoorBlock(BlockSetType type, BlockBehaviour.Properties properties) { super(properties.sound(type.soundType())); this.type = type; this.registerDefaultState( this.stateDefinition .any() .setValue(FACING, Direction.NORTH) .setValue(OPEN, false) .setValue(HINGE, DoorHingeSide.LEFT) .setValue(POWERED, false) .setValue(HALF, DoubleBlockHalf.LOWER) ); } public BlockSetType type() { return this.type; } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { Direction direction = state.getValue(FACING); Direction direction2 = state.getValue(OPEN) ? (state.getValue(HINGE) == DoorHingeSide.RIGHT ? direction.getCounterClockWise() : direction.getClockWise()) : direction; return (VoxelShape)SHAPES.get(direction2); } @Override protected BlockState updateShape( BlockState state, LevelReader level, ScheduledTickAccess scheduledTickAccess, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random ) { DoubleBlockHalf doubleBlockHalf = state.getValue(HALF); if (direction.getAxis() != Direction.Axis.Y || doubleBlockHalf == DoubleBlockHalf.LOWER != (direction == Direction.UP)) { return doubleBlockHalf == DoubleBlockHalf.LOWER && direction == Direction.DOWN && !state.canSurvive(level, pos) ? Blocks.AIR.defaultBlockState() : super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random); } else { return neighborState.getBlock() instanceof DoorBlock && neighborState.getValue(HALF) != doubleBlockHalf ? neighborState.setValue(HALF, doubleBlockHalf) : Blocks.AIR.defaultBlockState(); } } @Override protected void onExplosionHit(BlockState state, ServerLevel level, BlockPos pos, Explosion explosion, BiConsumer dropConsumer) { if (explosion.canTriggerBlocks() && state.getValue(HALF) == DoubleBlockHalf.LOWER && this.type.canOpenByWindCharge() && !(Boolean)state.getValue(POWERED)) { this.setOpen(null, level, state, pos, !this.isOpen(state)); } super.onExplosionHit(state, level, pos, explosion, dropConsumer); } @Override public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { if (!level.isClientSide && (player.preventsBlockDrops() || !player.hasCorrectToolForDrops(state))) { DoublePlantBlock.preventDropFromBottomPart(level, pos, state, player); } return super.playerWillDestroy(level, pos, state, player); } @Override protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { return switch (pathComputationType) { case LAND, AIR -> state.getValue(OPEN); case WATER -> false; }; } @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext context) { BlockPos blockPos = context.getClickedPos(); Level level = context.getLevel(); if (blockPos.getY() < level.getMaxY() && level.getBlockState(blockPos.above()).canBeReplaced(context)) { boolean bl = level.hasNeighborSignal(blockPos) || level.hasNeighborSignal(blockPos.above()); return this.defaultBlockState() .setValue(FACING, context.getHorizontalDirection()) .setValue(HINGE, this.getHinge(context)) .setValue(POWERED, bl) .setValue(OPEN, bl) .setValue(HALF, DoubleBlockHalf.LOWER); } else { return null; } } @Override public void setPlacedBy(Level level, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack) { level.setBlock(pos.above(), state.setValue(HALF, DoubleBlockHalf.UPPER), 3); } private DoorHingeSide getHinge(BlockPlaceContext context) { BlockGetter blockGetter = context.getLevel(); BlockPos blockPos = context.getClickedPos(); Direction direction = context.getHorizontalDirection(); BlockPos blockPos2 = blockPos.above(); Direction direction2 = direction.getCounterClockWise(); BlockPos blockPos3 = blockPos.relative(direction2); BlockState blockState = blockGetter.getBlockState(blockPos3); BlockPos blockPos4 = blockPos2.relative(direction2); BlockState blockState2 = blockGetter.getBlockState(blockPos4); Direction direction3 = direction.getClockWise(); BlockPos blockPos5 = blockPos.relative(direction3); BlockState blockState3 = blockGetter.getBlockState(blockPos5); BlockPos blockPos6 = blockPos2.relative(direction3); BlockState blockState4 = blockGetter.getBlockState(blockPos6); int i = (blockState.isCollisionShapeFullBlock(blockGetter, blockPos3) ? -1 : 0) + (blockState2.isCollisionShapeFullBlock(blockGetter, blockPos4) ? -1 : 0) + (blockState3.isCollisionShapeFullBlock(blockGetter, blockPos5) ? 1 : 0) + (blockState4.isCollisionShapeFullBlock(blockGetter, blockPos6) ? 1 : 0); boolean bl = blockState.getBlock() instanceof DoorBlock && blockState.getValue(HALF) == DoubleBlockHalf.LOWER; boolean bl2 = blockState3.getBlock() instanceof DoorBlock && blockState3.getValue(HALF) == DoubleBlockHalf.LOWER; if ((!bl || bl2) && i <= 0) { if ((!bl2 || bl) && i >= 0) { int j = direction.getStepX(); int k = direction.getStepZ(); Vec3 vec3 = context.getClickLocation(); double d = vec3.x - blockPos.getX(); double e = vec3.z - blockPos.getZ(); return (j >= 0 || !(e < 0.5)) && (j <= 0 || !(e > 0.5)) && (k >= 0 || !(d > 0.5)) && (k <= 0 || !(d < 0.5)) ? DoorHingeSide.LEFT : DoorHingeSide.RIGHT; } else { return DoorHingeSide.LEFT; } } else { return DoorHingeSide.RIGHT; } } @Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { if (!this.type.canOpenByHand()) { return InteractionResult.PASS; } else { state = state.cycle(OPEN); level.setBlock(pos, state, 10); this.playSound(player, level, pos, (Boolean)state.getValue(OPEN)); level.gameEvent(player, this.isOpen(state) ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos); return InteractionResult.SUCCESS; } } public boolean isOpen(BlockState state) { return (Boolean)state.getValue(OPEN); } public void setOpen(@Nullable Entity entity, Level level, BlockState state, BlockPos pos, boolean open) { if (state.is(this) && (Boolean)state.getValue(OPEN) != open) { level.setBlock(pos, state.setValue(OPEN, open), 10); this.playSound(entity, level, pos, open); level.gameEvent(entity, open ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos); } } @Override protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) { boolean bl = level.hasNeighborSignal(pos) || level.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN)); if (!this.defaultBlockState().is(neighborBlock) && bl != (Boolean)state.getValue(POWERED)) { if (bl != (Boolean)state.getValue(OPEN)) { this.playSound(null, level, pos, bl); level.gameEvent(null, bl ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos); } level.setBlock(pos, state.setValue(POWERED, bl).setValue(OPEN, bl), 2); } } @Override protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { BlockPos blockPos = pos.below(); BlockState blockState = level.getBlockState(blockPos); return state.getValue(HALF) == DoubleBlockHalf.LOWER ? blockState.isFaceSturdy(level, blockPos, Direction.UP) : blockState.is(this); } private void playSound(@Nullable Entity source, Level level, BlockPos pos, boolean isOpening) { level.playSound(source, pos, isOpening ? this.type.doorOpen() : this.type.doorClose(), SoundSource.BLOCKS, 1.0F, level.getRandom().nextFloat() * 0.1F + 0.9F); } @Override protected BlockState rotate(BlockState state, Rotation rotation) { return state.setValue(FACING, rotation.rotate(state.getValue(FACING))); } @Override protected BlockState mirror(BlockState state, Mirror mirror) { return mirror == Mirror.NONE ? state : state.rotate(mirror.getRotation(state.getValue(FACING))).cycle(HINGE); } @Override protected long getSeed(BlockState state, BlockPos pos) { return Mth.getSeed(pos.getX(), pos.below(state.getValue(HALF) == DoubleBlockHalf.LOWER ? 0 : 1).getY(), pos.getZ()); } @Override protected void createBlockStateDefinition(Builder builder) { builder.add(HALF, FACING, OPEN, HINGE, POWERED); } public static boolean isWoodenDoor(Level level, BlockPos pos) { return isWoodenDoor(level.getBlockState(pos)); } public static boolean isWoodenDoor(BlockState state) { return state.getBlock() instanceof DoorBlock doorBlock && doorBlock.type().canOpenByHand(); } }