diff --git a/README.md b/README.md index fa28659..3f9a7b6 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,11 @@ planks) with a fully sealed hollow interior. No gaps; the interior must be enclo 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. +Build it free-standing, one block thick, in a single flat plane (a vertical ring +facing a direction, like all the examples above) - the plugin figures out which axis +is the ring's "thickness" and only looks for the interior within that plane, so it +doesn't try to flood-fill out through the open air in front of and behind the gate. + 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 further outward than their immediate ring-neighbours - the "elbow" points of the @@ -53,8 +58,10 @@ material, unsealed interior, too far from the sign, etc. - **Right-click** the sign: cycles the destination shown on line 3 among the other gates on the same network. - **Left-click** the sign: dials the shown destination — chevrons light in sequence, - then the interior fills with `gate.iris-material` (default water). Walk into it to - teleport. It auto-closes after `dialing.open-seconds`. + then the interior switches from `gate.iris-closed-material` (default bedrock - solid, + nothing can pass through a closed/idle gate) to `gate.iris-open-material` (default + water, the "event horizon"). Walk into it to teleport. It auto-closes after + `dialing.open-seconds`, switching the interior back to bedrock. ## Multi-world diff --git a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateConfig.java b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateConfig.java index 6c23e68..773e0c7 100644 --- a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateConfig.java +++ b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateConfig.java @@ -12,7 +12,8 @@ public class GateConfig { public final Set frameMaterials; public final Material chevronLit; - public final Material irisMaterial; + public final Material irisOpenMaterial; + public final Material irisClosedMaterial; public final int maxFrameBlocks; public final int maxIrisBlocks; public final int minFrameBlocks; @@ -34,7 +35,8 @@ public class GateConfig { this.frameMaterials = materials; this.chevronLit = matOr(cfg.getString("gate.chevron-lit-material"), Material.GILDED_BLACKSTONE, logger); - this.irisMaterial = matOr(cfg.getString("gate.iris-material"), Material.WATER, logger); + this.irisOpenMaterial = matOr(cfg.getString("gate.iris-open-material"), Material.WATER, logger); + this.irisClosedMaterial = matOr(cfg.getString("gate.iris-closed-material"), Material.BEDROCK, logger); this.maxFrameBlocks = cfg.getInt("gate.max-frame-blocks", 300); this.maxIrisBlocks = cfg.getInt("gate.max-iris-blocks", 200); this.minFrameBlocks = cfg.getInt("gate.min-frame-blocks", 8); diff --git a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateManager.java b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateManager.java index fee370d..65cea14 100644 --- a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateManager.java +++ b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateManager.java @@ -57,6 +57,11 @@ public class GateManager { GateStructureScanner.ScanResult result = scanner.scanWithDiagnostics(linked); if (result.isSuccess()) { structure = result.structure; + // a restart can't preserve "open" runtime state, so every gate comes + // back closed - make sure the iris actually reflects that + for (Block b : structure.getIris()) { + b.setType(config.irisClosedMaterial); + } } else { plugin.getLogger().warning("[Stargate] Could not re-scan structure for gate '" + gate.getName() + "' - it may have been damaged (" + result.failureReason + ")"); @@ -169,6 +174,9 @@ public class GateManager { return LinkResult.fail(result.failureReason); } GateStructure structure = result.structure; + for (Block b : structure.getIris()) { + b.setType(config.irisClosedMaterial); + } Location exit = computeExitLocation(structure, clickedBlock.getWorld(), facing); Gate gate = new Gate(UUID.randomUUID(), pending.name, pending.network, plugin.getServerId(), @@ -281,7 +289,7 @@ public class GateManager { gate.setConnectedTo(connectedTo); if (gate.getStructure() != null) { for (Block b : gate.getStructure().getIris()) { - b.setType(config.irisMaterial); + b.setType(config.irisOpenMaterial); if (b.getBlockData() instanceof Levelled lvl) { lvl.setLevel(0); b.setBlockData(lvl); @@ -311,7 +319,7 @@ public class GateManager { gate.setConnectedTo(null); if (gate.getStructure() != null) { for (Block b : gate.getStructure().getIris()) { - b.setType(org.bukkit.Material.AIR); + b.setType(config.irisClosedMaterial); } for (GateStructure.ChevronSlot c : gate.getStructure().getChevrons()) { c.getBlock().setType(c.getRestingMaterial()); 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 f3032c0..0039067 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 @@ -21,10 +21,6 @@ import java.util.Set; */ public class GateStructureScanner { - private static final BlockFace[] NEIGHBORS = { - 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 @@ -119,10 +115,16 @@ public class GateStructureScanner { + minFrameBlocks + " (gate.min-frame-blocks)."); } + // A free-standing, one-block-thick ring has nothing behind or in front of it, so + // interior candidates must stay confined to the ring's own plane - otherwise "inside" + // immediately leaks out the front/back into the open world. Find the thin axis (the + // one frame blocks barely vary along) and only search/flood the other two. + BlockFace[] inPlane = inPlaneDirections(frameBlocks); + Set triedSeeds = new HashSet<>(); boolean triedAnyInterior = false; for (Block frameBlock : frameBlocks) { - for (BlockFace face : NEIGHBORS) { + for (BlockFace face : inPlane) { Block candidate = frameBlock.getRelative(face); long ck = key(candidate); if (frameKeys.contains(ck) || triedSeeds.contains(ck)) continue; @@ -130,7 +132,7 @@ public class GateStructureScanner { if (candidate.getType().isSolid()) continue; triedAnyInterior = true; - List iris = floodInterior(candidate, frameKeys); + List iris = floodInterior(candidate, frameKeys, inPlane); if (iris != null && !iris.isEmpty()) { return ScanResult.ok(new GateStructure(frameBlocks, pickChevrons(frameBlocks, iris), iris)); } @@ -146,7 +148,7 @@ public class GateStructureScanner { + "gate.max-iris-blocks (" + maxIrisBlocks + ")."); } - private List floodInterior(Block seed, Set frameKeys) { + private List floodInterior(Block seed, Set frameKeys, BlockFace[] inPlane) { Set visited = new HashSet<>(); List interior = new ArrayList<>(); Deque queue = new ArrayDeque<>(); @@ -158,7 +160,7 @@ public class GateStructureScanner { interior.add(cur); if (interior.size() > maxIrisBlocks) return null; - for (BlockFace face : NEIGHBORS) { + for (BlockFace face : inPlane) { Block next = cur.getRelative(face); long k = key(next); if (visited.contains(k) || frameKeys.contains(k)) continue; @@ -170,6 +172,27 @@ public class GateStructureScanner { return interior; } + /** The 4 directions that stay within the ring's own plane, excluding the axis the ring is thinnest along. */ + private BlockFace[] inPlaneDirections(List frameBlocks) { + int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE; + int minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE; + int minZ = Integer.MAX_VALUE, maxZ = Integer.MIN_VALUE; + for (Block b : frameBlocks) { + minX = Math.min(minX, b.getX()); maxX = Math.max(maxX, b.getX()); + minY = Math.min(minY, b.getY()); maxY = Math.max(maxY, b.getY()); + minZ = Math.min(minZ, b.getZ()); maxZ = Math.max(maxZ, b.getZ()); + } + int rangeX = maxX - minX, rangeY = maxY - minY, rangeZ = maxZ - minZ; + + if (rangeX <= rangeY && rangeX <= rangeZ) { + return new BlockFace[]{BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH}; + } else if (rangeZ <= rangeY && rangeZ <= rangeX) { + return new BlockFace[]{BlockFace.UP, BlockFace.DOWN, BlockFace.EAST, BlockFace.WEST}; + } else { + return new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}; + } + } + /** * Picks chevrons at the ring's actual protruding "elbow" points, the way a real Stargate's * chevrons sit - not evenly spaced by scan order. Works by walking the ring in geometric diff --git a/stargate-paper/src/main/resources/config.yml b/stargate-paper/src/main/resources/config.yml index 6306dbc..12d4ccf 100644 --- a/stargate-paper/src/main/resources/config.yml +++ b/stargate-paper/src/main/resources/config.yml @@ -44,8 +44,11 @@ gate: # Stargate's chevron placement - then swaps just those to chevron-lit-material while # dialing/open, restoring whatever they looked like at rest when the gate closes. chevron-lit-material: GILDED_BLACKSTONE - # Material poured into the interior (the "event horizon") while the gate is open. - iris-material: WATER + # The interior ("iris") reflects whether the gate is usable. Closed/idle, it's solid + # bedrock - nothing can walk or fall through it. Only while open does it become + # iris-open-material (the "event horizon") and let players/items pass. + iris-open-material: WATER + iris-closed-material: BEDROCK max-frame-blocks: 300 max-iris-blocks: 200 min-frame-blocks: 8