Code

/** * @see <a href="https://mcsrc.dev/#1/1.21.11_unobfuscated/net/minecraft/world/level/block/DoorBlock">Minecraft source code</a> */public final class DoorPlacementRule extends BlockPlacementRule {    public static final Key KEY = Key.key("minecraft:doors");    public DoorPlacementRule(@NotNull Block block) {        super(block);    }    @Override    public @Nullable Block blockPlace(@NotNull PlacementState placementState) {        var instance = (Instance) placementState.instance();        var playerPosition = placementState.playerPosition();        var facing = getFacingDirection(playerPosition);        var placePosition = placementState.placePosition();        var dimension = MinecraftServer.getDimensionTypeRegistry().get(instance.getDimensionType());        assert dimension != null;        var baseX = placePosition.blockX();        var baseY = placePosition.blockY();        var baseZ = placePosition.blockZ();        // check if there's space for both door halves within the world bounds        if (baseY <= dimension.minY() || baseY + 1 >= dimension.maxY()) {            return null;        }        var targetBlock = instance.getBlock(baseX, baseY, baseZ);        if (!isReplaceable(targetBlock)) {            return null;        }        var upperBlock = instance.getBlock(baseX, baseY + 1, baseZ);        if (!isReplaceable(upperBlock)) {            return null;        }        var supportBlock = instance.getBlock(baseX, baseY - 1, baseZ);        if (!isSupporting(supportBlock)) {            return null;        }        var hinge = getHinge(instance, facing, placementState, baseX, baseY, baseZ);        var configured = placementState.block()                .withProperty("facing", facing.name().toLowerCase())                .withProperty("open", "false")                .withProperty("hinge", hinge.name().toLowerCase())                .withProperty("powered", "false");        var upperPosition = placePosition.add(0, 1, 0);        instance.setBlock(upperPosition, configured.withProperty("half", "upper"));        return configured.withProperty("half", "lower");    }    @Override    public @NotNull Block blockUpdate(@NotNull UpdateState updateState) {        var currentBlock = updateState.currentBlock();        var half = currentBlock.getProperty("half");        if (half == null) {            return currentBlock;        }        var otherHalfY = "lower".equals(half) ? 1 : -1;        var blockPosition = updateState.blockPosition();        var otherHalfBlock = updateState.instance().getBlock(                blockPosition.blockX(),                blockPosition.blockY() + otherHalfY,                blockPosition.blockZ());        if (!isDoorHalf(otherHalfBlock, getOppositeHalf(half))) {            return Block.AIR;        }        return currentBlock;    }    @Override    public int maxUpdateDistance() {        return 1;    }    private static boolean isDoorHalf(Block block, String expectedHalf) {        if (!Utility.hasTag(block, KEY)) {            return false;        }        var half = block.getProperty("half");        return expectedHalf.equals(half);    }    private static String getOppositeHalf(String half) {        return "lower".equals(half) ? "upper" : "lower";    }    private static Direction getFacingDirection(@Nullable Pos position) {        if (position == null) {            return Direction.NORTH;        }        // convert yaw to horizontal direction        var yaw = (position.yaw() % 360.0F + 360.0F) % 360.0F;        if (yaw < 45.0F || yaw >= 315.0F) {            return Direction.SOUTH;        } else if (yaw < 135.0F) {            return Direction.WEST;        } else if (yaw < 225.0F) {            return Direction.NORTH;        } else {            return Direction.EAST;        }    }    @SuppressWarnings("BooleanMethodIsAlwaysInverted")    private static boolean isReplaceable(Block block) {        return block.isAir() || block.registry().isReplaceable();    }    private static boolean isSupporting(Block block) {        return !block.isAir() && block.registry().isSolid();    }    private static Hinge getHinge(Instance instance, Direction facing, PlacementState placementState, int baseX, int baseY, int baseZ) {        var leftDirection = rotateCounterClockwise(facing);        var rightDirection = rotateClockwise(facing);        var leftLower = instance.getBlock(baseX + leftDirection.normalX(), baseY, baseZ + leftDirection.normalZ());        var leftUpper = instance.getBlock(baseX + leftDirection.normalX(), baseY + 1, baseZ + leftDirection.normalZ());        var rightLower = instance.getBlock(baseX + rightDirection.normalX(), baseY, baseZ + rightDirection.normalZ());        var rightUpper = instance.getBlock(baseX + rightDirection.normalX(), baseY + 1, baseZ + rightDirection.normalZ());        // calculate solidity score: negative favors left hinge, positive favors right hinge        var solidityScore = (isFullBlock(leftLower) ? -1 : 0)                + (isFullBlock(leftUpper) ? -1 : 0)                + (isFullBlock(rightLower) ? 1 : 0)                + (isFullBlock(rightUpper) ? 1 : 0);        boolean leftDoor = isLowerDoor(leftLower);        boolean rightDoor = isLowerDoor(rightLower);        // determine hinge based on neighboring doors and solidity        if ((!leftDoor || rightDoor) && solidityScore <= 0) {            if ((!rightDoor || leftDoor) && solidityScore == 0) {                // no clear preference, use cursor position to decide                var relativeCursor = getRelativeCursor(placementState);                var stepX = facing.normalX();                var stepZ = facing.normalZ();                var placeLeft = (stepX >= 0 || !(relativeCursor.z() < 0.5D))                        && (stepX <= 0 || !(relativeCursor.z() > 0.5D))                        && (stepZ >= 0 || !(relativeCursor.x() > 0.5D))                        && (stepZ <= 0 || !(relativeCursor.x() < 0.5D));                return placeLeft ? Hinge.LEFT : Hinge.RIGHT;            }            return Hinge.LEFT;        }        return Hinge.RIGHT;    }    private static boolean isFullBlock(Block block) {        return !block.isAir() && block.registry().occludes() && block.registry().isSolid();    }    private static boolean isLowerDoor(Block block) {        if (!Utility.hasTag(block, KEY)) {            return false;        }        return "lower".equals(block.getProperty("half"));    }    private static Direction rotateClockwise(Direction direction) {        return switch (direction) {            case NORTH -> Direction.EAST;            case EAST -> Direction.SOUTH;            case SOUTH -> Direction.WEST;            case WEST -> Direction.NORTH;            default -> direction;        };    }    private static Direction rotateCounterClockwise(Direction direction) {        return switch (direction) {            case NORTH -> Direction.WEST;            case WEST -> Direction.SOUTH;            case SOUTH -> Direction.EAST;            case EAST -> Direction.NORTH;            default -> direction;        };    }    private static Cursor getRelativeCursor(PlacementState placementState) {        var cursorPosition = placementState.cursorPosition();        var localX = 0.5D;        var localZ = 0.5D;        if (cursorPosition != null) {            localX = cursorPosition.x();            localZ = cursorPosition.z();        }        var offsetX = 0;        var offsetZ = 0;        var blockFace = placementState.blockFace();        if (blockFace != null) {            var direction = blockFace.toDirection();            offsetX = direction.normalX();            offsetZ = direction.normalZ();        }        return new Cursor(localX - offsetX, localZ - offsetZ);    }    private record Cursor(double x, double z) {    }    public enum Hinge {        LEFT,        RIGHT    }}

Comments

No comments yet. Be the first!