// ❌ 부동소수로 블록 좌표 계산 → 부정확double x = pos.getX() + 0.5;level.setBlock(new BlockPos((int) x, y, z), state, 11);// ✅ BlockPos 사용 — 정수 블록 좌표BlockPos inner = cornerPos.offset(x, y, 0);level.setBlock(inner, state, 11);
블록 좌표는 항상 BlockPos (정수) 로 계산합니다. 부동소수 변환은 반올림 오차로 인해 블록 위치가 어긋납니다.
// examplemod-master-projects/dimension/src/main/java/com/example/master/dimension/portal/PortalShape.javapackage com.example.master.dimension.portal;import net.minecraft.core.BlockPos;import net.minecraft.world.level.Level;import net.minecraft.world.level.block.Blocks;import net.minecraft.world.level.block.state.BlockState;/** * 포탈 프레임 형태 인식 유틸리티. * * <p>사용 흐름: * 1. findShape(Level, BlockPos) — 주어진 블록 주변에서 유효한 프레임을 찾아 PortalShape 반환 * 2. isValidFrame(Level, BlockPos) — 단순 유효성 검사 (불리언) * 3. fillInterior(Level, BlockState) — 내부 공기 슬롯을 포탈 블록으로 채움 */public class PortalShape { /** 프레임 최소 크기 (내부 포함 전체) */ public static final int MIN_SIZE = 3; /** 프레임 최대 크기 (내부 포함 전체) */ public static final int MAX_SIZE = 21; /** 좌측 하단 코너 좌표 (프레임 블록 위치) */ private final BlockPos cornerPos; /** 프레임 전체 너비 (코너 포함) */ private final int width; /** 프레임 전체 높이 (코너 포함) */ private final int height; private PortalShape(BlockPos cornerPos, int width, int height) { this.cornerPos = cornerPos; this.width = width; this.height = height; } // ── 공개 API ────────────────────────────────────────────────────────────── /** * 주어진 위치 주변에서 유효한 포탈 프레임 형태를 탐색합니다. * * @param level 현재 월드 * @param pos 프레임 블록 또는 내부 블록 위치 * @return 유효한 프레임이면 PortalShape, 없으면 null */ public static PortalShape findShape(Level level, BlockPos pos) { BlockPos corner = findCorner(level, pos); if (corner == null) return null; int w = measureWidth(level, corner); int h = measureHeight(level, corner); if (w < MIN_SIZE || w > MAX_SIZE) return null; if (h < MIN_SIZE || h > MAX_SIZE) return null; if (!isFrameComplete(level, corner, w, h)) return null; return new PortalShape(corner, w, h); } /** * 주어진 위치에 유효한 포탈 프레임이 있는지 빠르게 확인합니다. */ public static boolean isValidFrame(Level level, BlockPos pos) { return findShape(level, pos) != null; } /** * 프레임 내부의 Air 슬롯을 portalState로 채웁니다. */ public void fillInterior(Level level, BlockState portalState) { for (int x = 1; x < width - 1; x++) { for (int y = 1; y < height - 1; y++) { BlockPos inner = cornerPos.offset(x, y, 0); if (level.getBlockState(inner).isAir()) { level.setBlock(inner, portalState, 11); // UPDATE | NOTIFY_CLIENTS } } } } // ── 내부 헬퍼 ───────────────────────────────────────────────────────────── /** 좌측 하단 코너(프레임 블록)를 찾습니다. */ private static BlockPos findCorner(Level level, BlockPos pos) { BlockPos current = pos; for (int i = 0; i < MAX_SIZE; i++) { if (isFrameBlock(level, current)) break; current = current.below(); if (i == MAX_SIZE - 1) return null; } for (int i = 0; i < MAX_SIZE; i++) { if (!isFrameBlock(level, current.west())) break; current = current.west(); } return current; } /** 코너에서 오른쪽(+X)으로 프레임 너비를 측정합니다. */ private static int measureWidth(Level level, BlockPos corner) { int w = 0; for (int x = 0; x <= MAX_SIZE; x++) { if (!isFrameBlock(level, corner.east(x))) break; w++; } return w; } /** 코너에서 위쪽(+Y)으로 프레임 높이를 측정합니다. */ private static int measureHeight(Level level, BlockPos corner) { int h = 0; for (int y = 0; y <= MAX_SIZE; y++) { if (!isFrameBlock(level, corner.above(y))) break; h++; } return h; } /** 프레임 무결성 검사: 4변 + 내부 Air 확인 */ private static boolean isFrameComplete(Level level, BlockPos corner, int w, int h) { for (int x = 0; x < w; x++) { if (!isFrameBlock(level, corner.east(x))) return false; if (!isFrameBlock(level, corner.east(x).above(h - 1))) return false; } for (int y = 0; y < h; y++) { if (!isFrameBlock(level, corner.above(y))) return false; if (!isFrameBlock(level, corner.east(w - 1).above(y))) return false; } for (int x = 1; x < w - 1; x++) { for (int y = 1; y < h - 1; y++) { if (!level.getBlockState(corner.east(x).above(y)).isAir()) return false; } } return true; } /** 흑요석 또는 운명의 흑요석인지 확인합니다. */ private static boolean isFrameBlock(Level level, BlockPos pos) { var block = level.getBlockState(pos).getBlock(); return block == Blocks.OBSIDIAN || block == Blocks.CRYING_OBSIDIAN; } // ── 게터 ───────────────────────────────────────────────────────────────── public BlockPos getCornerPos() { return cornerPos; } public int getWidth() { return width; } public int getHeight() { return height; }}
findShape(level, pos)
│
├─ findCorner(level, pos)
│ ├─ 아래(−Y)로 이동 → 프레임 블록 행 탐색
│ └─ 왼쪽(−X)으로 이동 → 좌측 하단 코너 확정
│
├─ measureWidth() : 코너에서 +X 방향으로 프레임 블록 수 세기
├─ measureHeight() : 코너에서 +Y 방향으로 프레임 블록 수 세기
│
├─ 크기 범위 검사 (3 ≤ w,h ≤ 21)
│
└─ isFrameComplete()
├─ 바닥 행 · 천장 행 프레임 블록 검사
├─ 좌측 열 · 우측 열 프레임 블록 검사
└─ 내부 전체 Air 검사