From 282c8cfdbb4f9fe9647703f57b750196c39d4387 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Sun, 9 Aug 2026 11:04:20 -0400 Subject: [PATCH] Fix frame flood-fill to follow diagonal (stepped) ring connections Round/octagonal rings built the normal Minecraft way step diagonally at the corners - two frame blocks touching only edge-to-edge, not face-to-face. The scanner only checked the 6 face-adjacent neighbours, so those blocks were treated as disconnected fragments and the whole ring failed to register (or only a fraction of it did). Frame discovery now flood-fills using all 26 surrounding offsets instead of just the 6 faces. The geometric chevron walk needed the same fix - its in-plane adjacency now checks 8 directions (orthogonal + diagonal) instead of 4, so stepped rings still produce a clean single-loop walk instead of falling back to evenly-spaced chevrons. Interior/iris flood-fill is intentionally left orthogonal-only: since it only ever moves face-to-face, a diagonal-only frame corner already blocks it from slipping past, so enclosure detection didn't need to change. --- README.md | 3 ++ .../paper/gate/GateStructureScanner.java | 31 ++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 021dd0c..fa28659 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ Build everything with `./gradlew build`. Jars land in each module's `build/libs/ There's no fixed size or shape - build any single connected ring out of `gate.frame-materials` (default: obsidian, gold block, gilded blackstone, birch/oak planks) with a fully sealed hollow interior. No gaps; the interior must be enclosed. +"Connected" includes the usual diagonal stepping a round/octagonal ring needs at its +corners (blocks touching only edge-to-edge or corner-to-corner still count), so a +circular build doesn't need every block to share a full face with the next. Chevrons aren't a separate material you place. When the gate is linked, the plugin flood-fills the ring, walks it in geometric order, and picks out the blocks that stick diff --git a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateStructureScanner.java b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateStructureScanner.java index 66e4d1f..f3032c0 100644 --- a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateStructureScanner.java +++ b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateStructureScanner.java @@ -25,6 +25,25 @@ public class GateStructureScanner { BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST }; + // Round/octagonal rings built the usual Minecraft way step diagonally at the corners - two + // frame blocks touching only edge-to-edge (or even just corner-to-corner), not face-to-face. + // The frame search has to follow those too, or a stepped ring gets treated as several + // disconnected fragments instead of one structure. + private static final int[][] ALL_26_OFFSETS = buildAll26Offsets(); + + private static int[][] buildAll26Offsets() { + List offsets = new ArrayList<>(); + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dy == 0 && dz == 0) continue; + offsets.add(new int[]{dx, dy, dz}); + } + } + } + return offsets.toArray(new int[0][]); + } + public static final class ScanResult { public final GateStructure structure; // null on failure public final String failureReason; // null on success @@ -84,8 +103,8 @@ public class GateStructureScanner { + "Raise max-frame-blocks or make the frame materials more specific."); } - for (BlockFace face : NEIGHBORS) { - Block next = cur.getRelative(face); + for (int[] d : ALL_26_OFFSETS) { + Block next = cur.getRelative(d[0], d[1], d[2]); long k = key(next); if (frameKeys.contains(k)) continue; if (isFrameMaterial(next.getType())) { @@ -224,14 +243,18 @@ public class GateStructureScanner { return chevrons.isEmpty() ? fallbackEvenlySpaced(frameBlocks) : chevrons; } - /** Walks the ring in geometric order assuming each frame block has exactly two in-plane neighbours; null if that assumption fails. */ + private static final int[][] PLANE_8_OFFSETS = { + {1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1} + }; + + /** Walks the ring in geometric order assuming each frame block has exactly two in-plane neighbours (orthogonal or diagonal, for stepped rings); null if that assumption fails. */ private List walkRing(List frameBlocks, Map plane, boolean horizontalIsX) { Map> adjacency = new HashMap<>(); for (Block b : frameBlocks) { int col = horizontalIsX ? b.getX() : b.getZ(); int row = b.getY(); List neighbors = new ArrayList<>(); - for (int[] d : new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}) { + for (int[] d : PLANE_8_OFFSETS) { long k = (((long) (row + d[0])) << 32) ^ ((col + d[1]) & 0xFFFFFFFFL); Block n = plane.get(k); if (n != null && n != b) neighbors.add(n);