/** * @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!
Please login to leave a comment.