minecraft-src/net/minecraft/world/level/block/DoorBlock.java
2025-07-04 01:41:11 +03:00

269 lines
12 KiB
Java

package net.minecraft.world.level.block;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.function.BiConsumer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
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.LevelAccessor;
import net.minecraft.world.level.LevelReader;
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.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.DirectionProperty;
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.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
public class DoorBlock extends Block {
public static final MapCodec<DoorBlock> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(BlockSetType.CODEC.fieldOf("block_set_type").forGetter(DoorBlock::type), propertiesCodec()).apply(instance, DoorBlock::new)
);
public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING;
public static final BooleanProperty OPEN = BlockStateProperties.OPEN;
public static final EnumProperty<DoorHingeSide> HINGE = BlockStateProperties.DOOR_HINGE;
public static final BooleanProperty POWERED = BlockStateProperties.POWERED;
public static final EnumProperty<DoubleBlockHalf> HALF = BlockStateProperties.DOUBLE_BLOCK_HALF;
protected static final float AABB_DOOR_THICKNESS = 3.0F;
protected static final VoxelShape SOUTH_AABB = Block.box(0.0, 0.0, 0.0, 16.0, 16.0, 3.0);
protected static final VoxelShape NORTH_AABB = Block.box(0.0, 0.0, 13.0, 16.0, 16.0, 16.0);
protected static final VoxelShape WEST_AABB = Block.box(13.0, 0.0, 0.0, 16.0, 16.0, 16.0);
protected static final VoxelShape EAST_AABB = Block.box(0.0, 0.0, 0.0, 3.0, 16.0, 16.0);
private final BlockSetType type;
@Override
public MapCodec<? extends DoorBlock> 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);
boolean bl = !(Boolean)state.getValue(OPEN);
boolean bl2 = state.getValue(HINGE) == DoorHingeSide.RIGHT;
return switch (direction) {
case SOUTH -> bl ? SOUTH_AABB : (bl2 ? EAST_AABB : WEST_AABB);
case WEST -> bl ? WEST_AABB : (bl2 ? SOUTH_AABB : NORTH_AABB);
case NORTH -> bl ? NORTH_AABB : (bl2 ? WEST_AABB : EAST_AABB);
default -> bl ? EAST_AABB : (bl2 ? NORTH_AABB : SOUTH_AABB);
};
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
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, direction, neighborState, level, pos, neighborPos);
} else {
return neighborState.getBlock() instanceof DoorBlock && neighborState.getValue(HALF) != doubleBlockHalf
? neighborState.setValue(HALF, doubleBlockHalf)
: Blocks.AIR.defaultBlockState();
}
}
@Override
protected void onExplosionHit(BlockState state, Level level, BlockPos pos, Explosion explosion, BiConsumer<ItemStack, BlockPos> 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.isCreative() || !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.getMaxBuildHeight() - 1 && 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.sidedSuccess(level.isClientSide);
}
}
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, BlockPos neighborPos, 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(StateDefinition.Builder<Block, BlockState> 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();
}
}