diff --git a/README.md b/README.md index bba5801..021dd0c 100644 --- a/README.md +++ b/README.md @@ -16,38 +16,21 @@ Build everything with `./gradlew build`. Jars land in each module's `build/libs/ ## Building a gate -A gate's shape is a fixed template in `config.yml` (`gate.template`), matched exactly -against the world - not flood-filled or guessed. The default is an 11x11 ring with 7 -chevrons, laid out top row to bottom row: +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. -``` -00000000000 -0000*1*0000 -00012221000 -00*22222*00 -0*2222222*0 -01222222210 -0*2222222*0 -00*22222*00 -00012221000 -0000***0000 -00000000000 -``` +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 +shape, which is where a real Stargate's chevrons sit (an 11-wide octagonal ring like +the reference design naturally produces 7 of them: the top apex, the four shoulder +elbows, and the two side bumps). Those blocks swap to `gate.chevron-lit-material` +(default gilded blackstone) while dialing/open, and revert to whatever they looked +like at rest when the gate closes - so a chevron can rest as plain obsidian and blend +invisibly into the frame until it lights up. -- `*` — frame block, never touched by the plugin. Must be one of `gate.frame-materials` - (default: obsidian, gold block, gilded blackstone, birch/oak planks). -- `1` — a chevron: a frame block (same material rules as `*`) that swaps to - `gate.chevron-lit-material` (default gilded blackstone) while dialing/open, then - reverts to whatever it looked like at rest when the gate closes. So a chevron can - rest as plain obsidian and blend invisibly into the frame until it lights up. -- `2` — interior ("iris"), filled with `gate.iris-material` (default water) while open. -- `0` — ignored, not part of the gate. - -Build it in a single vertical plane, one block thick, aligned to the world's X or Z -axis (facing north/south/east/west - not built at an angle). Edit `gate.template` in -`config.yml` if you want a different size or chevron count/placement. - -1. Build the frame to match the template. +1. Build the ring. 2. Place a sign anywhere - it doesn't need to touch the frame, so it can act like a DHD console standing apart from the gate - with: - Line 1: `[Stargate]` @@ -55,13 +38,12 @@ axis (facing north/south/east/west - not built at an angle). Edit `gate.template - Line 3: gate name (blank = auto-generated) - Line 4: `hidden` to keep it out of the cycle list, `fixed:GateName` to lock this gate to always dial `GateName` (no right-click cycling), or blank -3. The sign will show "Punch the gate (30s)". Within `gate.link-timeout-seconds`, stand - facing the gate square-on and left-click (punch) any `*` or `1` block, within - `gate.max-link-distance` of the sign. The plugin matches the template against the - world starting from that block and links the sign to it. +3. The sign will show "Punch the gate (30s)". Within `gate.link-timeout-seconds`, + left-click (punch) any frame block, within `gate.max-link-distance` of the sign. + The plugin scans out from that block and links the sign to it. If punching a block doesn't work, the plugin tells you exactly why in chat - wrong -material, no orientation of the template lines up, too far from the sign, etc. +material, unsealed interior, too far from the sign, etc. ## Using a gate 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 bda80fd..6c23e68 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 @@ -4,17 +4,18 @@ import org.bukkit.Material; import org.bukkit.configuration.file.FileConfiguration; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.logging.Logger; /** Typed view over the `gate:` / `dialing:` / `network:` sections of config.yml. */ public class GateConfig { - public final GateTemplate template; public final Set frameMaterials; public final Material chevronLit; public final Material irisMaterial; + public final int maxFrameBlocks; + public final int maxIrisBlocks; + public final int minFrameBlocks; public final int openSeconds; public final int chevronTickDelay; public final boolean playSounds; @@ -23,13 +24,6 @@ public class GateConfig { public final int maxLinkDistance; public GateConfig(FileConfiguration cfg, Logger logger) { - List templateRows = cfg.getStringList("gate.template"); - if (templateRows.isEmpty()) { - logger.warning("[Stargate] gate.template is empty in config.yml - falling back to a built-in default."); - templateRows = DEFAULT_TEMPLATE; - } - this.template = new GateTemplate(templateRows); - Set materials = new HashSet<>(); for (String s : cfg.getStringList("gate.frame-materials")) { Material m = Material.matchMaterial(s); @@ -41,6 +35,9 @@ public class GateConfig { 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.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); this.openSeconds = cfg.getInt("dialing.open-seconds", 10); this.chevronTickDelay = cfg.getInt("dialing.chevron-tick-delay", 4); this.playSounds = cfg.getBoolean("dialing.play-sounds", true); @@ -58,18 +55,4 @@ public class GateConfig { } return m; } - - private static final List DEFAULT_TEMPLATE = List.of( - "00000000000", - "0000*1*0000", - "00012221000", - "00*22222*00", - "0*2222222*0", - "01222222210", - "0*2222222*0", - "00*22222*00", - "00012221000", - "0000***0000", - "00000000000" - ); } 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 2387009..fee370d 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 @@ -26,7 +26,7 @@ public class GateManager { private final StargatePlugin plugin; private final GateStorage storage; private GateConfig config; - private GateTemplateScanner scanner; + private GateStructureScanner scanner; private final Map gatesById = new HashMap<>(); private final Map gatesBySignBlock = new HashMap<>(); // "world,x,y,z" -> gate @@ -39,7 +39,8 @@ public class GateManager { public void reloadConfig() { this.config = new GateConfig(plugin.getConfig(), plugin.getLogger()); - this.scanner = new GateTemplateScanner(config.template, config.frameMaterials); + this.scanner = new GateStructureScanner(config.frameMaterials, config.chevronLit, + config.maxFrameBlocks, config.maxIrisBlocks, config.minFrameBlocks); } public GateConfig getConfig() { return config; } @@ -53,8 +54,7 @@ public class GateManager { World world = Bukkit.getWorld(gate.getWorld()); if (world != null) { Block linked = world.getBlockAt(gate.getLinkX(), gate.getLinkY(), gate.getLinkZ()); - BlockFace facing = parseFacing(gate.getFacing()); - GateTemplateScanner.ScanResult result = scanner.scan(linked, facing); + GateStructureScanner.ScanResult result = scanner.scanWithDiagnostics(linked); if (result.isSuccess()) { structure = result.structure; } else { @@ -164,7 +164,7 @@ public class GateManager { } BlockFace facing = yawToCardinal(player.getLocation().getYaw()); - GateTemplateScanner.ScanResult result = scanner.scan(clickedBlock, facing); + GateStructureScanner.ScanResult result = scanner.scanWithDiagnostics(clickedBlock); if (!result.isSuccess()) { return LinkResult.fail(result.failureReason); } @@ -238,17 +238,6 @@ public class GateManager { }; } - private BlockFace parseFacing(String facing) { - try { - BlockFace face = BlockFace.valueOf(facing); - if (face == BlockFace.NORTH || face == BlockFace.SOUTH || face == BlockFace.EAST || face == BlockFace.WEST) { - return face; - } - } catch (Exception ignored) { - } - return BlockFace.NORTH; - } - // ---- Dialing ---- /** Starts the chevron-lighting animation, then opens the gate and connects it to the destination. */ 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 new file mode 100644 index 0000000..66e4d1f --- /dev/null +++ b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateStructureScanner.java @@ -0,0 +1,274 @@ +package dev.skywalker3200.stargate.paper.gate; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Flood-fill scanner that discovers a gate's physical structure starting from a punched + * frame block: the connected frame ring (any size/shape the builder makes), the enclosed + * interior ("iris"), and - unlike naive even-spacing - the ring's actual protruding "elbow" + * points, picked out geometrically, which is where a real Stargate's chevrons sit. + */ +public class GateStructureScanner { + + private static final BlockFace[] NEIGHBORS = { + BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST + }; + + public static final class ScanResult { + public final GateStructure structure; // null on failure + public final String failureReason; // null on success + + private ScanResult(GateStructure structure, String failureReason) { + this.structure = structure; + this.failureReason = failureReason; + } + + static ScanResult ok(GateStructure s) { return new ScanResult(s, null); } + static ScanResult fail(String reason) { return new ScanResult(null, reason); } + + public boolean isSuccess() { return structure != null; } + } + + private final Set frameMaterials; + private final Material chevronLitMaterial; + private final int maxFrameBlocks; + private final int maxIrisBlocks; + private final int minFrameBlocks; + + public GateStructureScanner(Set frameMaterials, Material chevronLitMaterial, + int maxFrameBlocks, int maxIrisBlocks, int minFrameBlocks) { + this.frameMaterials = frameMaterials; + this.chevronLitMaterial = chevronLitMaterial; + this.maxFrameBlocks = maxFrameBlocks; + this.maxIrisBlocks = maxIrisBlocks; + this.minFrameBlocks = minFrameBlocks; + } + + private boolean isFrameMaterial(Material m) { + // a gate re-scanned mid-dial (e.g. server restart) may have chevrons still lit + return frameMaterials.contains(m) || m == chevronLitMaterial; + } + + public GateStructure scan(Block seed) { + return scanWithDiagnostics(seed).structure; + } + + public ScanResult scanWithDiagnostics(Block seed) { + if (!isFrameMaterial(seed.getType())) { + return ScanResult.fail("That block isn't listed in gate.frame-materials."); + } + + Set frameKeys = new HashSet<>(); + List frameBlocks = new ArrayList<>(); + Deque queue = new ArrayDeque<>(); + queue.add(seed); + frameKeys.add(key(seed)); + + while (!queue.isEmpty()) { + Block cur = queue.poll(); + frameBlocks.add(cur); + if (frameBlocks.size() > maxFrameBlocks) { + return ScanResult.fail("The connected frame has more than gate.max-frame-blocks (" + maxFrameBlocks + + ") blocks. Either it's too big, or a frame material is leaking into a much larger structure. " + + "Raise max-frame-blocks or make the frame materials more specific."); + } + + for (BlockFace face : NEIGHBORS) { + Block next = cur.getRelative(face); + long k = key(next); + if (frameKeys.contains(k)) continue; + if (isFrameMaterial(next.getType())) { + frameKeys.add(k); + queue.add(next); + } + } + } + + if (frameBlocks.size() < minFrameBlocks) { + return ScanResult.fail("Only found " + frameBlocks.size() + " connected frame block(s), need at least " + + minFrameBlocks + " (gate.min-frame-blocks)."); + } + + Set triedSeeds = new HashSet<>(); + boolean triedAnyInterior = false; + for (Block frameBlock : frameBlocks) { + for (BlockFace face : NEIGHBORS) { + Block candidate = frameBlock.getRelative(face); + long ck = key(candidate); + if (frameKeys.contains(ck) || triedSeeds.contains(ck)) continue; + triedSeeds.add(ck); + if (candidate.getType().isSolid()) continue; + + triedAnyInterior = true; + List iris = floodInterior(candidate, frameKeys); + if (iris != null && !iris.isEmpty()) { + return ScanResult.ok(new GateStructure(frameBlocks, pickChevrons(frameBlocks, iris), iris)); + } + } + } + + if (!triedAnyInterior) { + return ScanResult.fail("The frame has no open interior at all (every block touching the ring is solid). " + + "Leave the middle hollow."); + } + return ScanResult.fail("Found a " + frameBlocks.size() + "-block frame, but its interior isn't fully " + + "enclosed - the empty space either leaks out through a gap in the ring, or is bigger than " + + "gate.max-iris-blocks (" + maxIrisBlocks + ")."); + } + + private List floodInterior(Block seed, Set frameKeys) { + Set visited = new HashSet<>(); + List interior = new ArrayList<>(); + Deque queue = new ArrayDeque<>(); + queue.add(seed); + visited.add(key(seed)); + + while (!queue.isEmpty()) { + Block cur = queue.poll(); + interior.add(cur); + if (interior.size() > maxIrisBlocks) return null; + + for (BlockFace face : NEIGHBORS) { + Block next = cur.getRelative(face); + long k = key(next); + if (visited.contains(k) || frameKeys.contains(k)) continue; + visited.add(k); + if (next.getType().isSolid()) continue; // ground/ceiling/wall boundary, not part of the interior + queue.add(next); + } + } + return interior; + } + + /** + * 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 + * order (each frame block should have exactly two ring neighbours) and marking local maxima + * of distance from the interior's centroid, i.e. points that stick out further than their + * immediate neighbours on the ring. + */ + private List pickChevrons(List frameBlocks, List iris) { + // The ring is essentially 2D (one block thick); find which world axis is the thin one. + int minX = Integer.MAX_VALUE, maxX = 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()); + minZ = Math.min(minZ, b.getZ()); maxZ = Math.max(maxZ, b.getZ()); + } + boolean horizontalIsX = (maxX - minX) >= (maxZ - minZ); + + Map plane = new HashMap<>(); + Map coords = new HashMap<>(); + for (Block b : frameBlocks) { + int col = horizontalIsX ? b.getX() : b.getZ(); + int row = b.getY(); + long k = (((long) row) << 32) ^ (col & 0xFFFFFFFFL); + plane.putIfAbsent(k, b); + coords.put(b, new int[]{row, col}); + } + + List ordered = walkRing(frameBlocks, plane, horizontalIsX); + if (ordered == null || ordered.size() < 4) { + return fallbackEvenlySpaced(frameBlocks); + } + + double centroidRow, centroidCol; + if (!iris.isEmpty()) { + long sumRow = 0, sumCol = 0; + for (Block b : iris) { + sumRow += b.getY(); + sumCol += horizontalIsX ? b.getX() : b.getZ(); + } + centroidRow = (double) sumRow / iris.size(); + centroidCol = (double) sumCol / iris.size(); + } else { + long sumRow = 0, sumCol = 0; + for (Block b : frameBlocks) { + sumRow += b.getY(); + sumCol += horizontalIsX ? b.getX() : b.getZ(); + } + centroidRow = (double) sumRow / frameBlocks.size(); + centroidCol = (double) sumCol / frameBlocks.size(); + } + + int n = ordered.size(); + double[] dist = new double[n]; + for (int i = 0; i < n; i++) { + int[] rc = coords.get(ordered.get(i)); + double dr = rc[0] - centroidRow; + double dc = rc[1] - centroidCol; + dist[i] = dr * dr + dc * dc; + } + + List chevrons = new ArrayList<>(); + for (int i = 0; i < n; i++) { + double prev = dist[(i - 1 + n) % n]; + double next = dist[(i + 1) % n]; + if (dist[i] > prev && dist[i] >= next) { + Block b = ordered.get(i); + chevrons.add(new GateStructure.ChevronSlot(b, b.getType())); + } + } + + 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 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}}) { + long k = (((long) (row + d[0])) << 32) ^ ((col + d[1]) & 0xFFFFFFFFL); + Block n = plane.get(k); + if (n != null && n != b) neighbors.add(n); + } + if (neighbors.size() != 2) return null; // not a simple single-thickness loop; bail out to fallback + adjacency.put(b, neighbors); + } + + Block start = frameBlocks.get(0); + List ordered = new ArrayList<>(); + Block prev = null; + Block current = start; + int guard = frameBlocks.size() + 2; + while (guard-- > 0) { + ordered.add(current); + List neighbors = adjacency.get(current); + Block next = neighbors.get(0).equals(prev) ? neighbors.get(1) : neighbors.get(0); + prev = current; + current = next; + if (current.equals(start)) break; + } + return ordered.size() == frameBlocks.size() ? ordered : null; + } + + private List fallbackEvenlySpaced(List frameBlocks) { + int count = Math.min(6, frameBlocks.size()); + List slots = new ArrayList<>(count); + if (count == 0) return slots; + double step = frameBlocks.size() / (double) count; + for (int i = 0; i < count; i++) { + Block b = frameBlocks.get((int) Math.round(i * step) % frameBlocks.size()); + slots.add(new GateStructure.ChevronSlot(b, b.getType())); + } + return slots; + } + + private long key(Block b) { + return (((long) b.getX() & 0x3FFFFFF) << 38) | (((long) (b.getY() + 512) & 0xFFF) << 26) | ((long) b.getZ() & 0x3FFFFFF); + } +} diff --git a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateTemplate.java b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateTemplate.java deleted file mode 100644 index d41422d..0000000 --- a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateTemplate.java +++ /dev/null @@ -1,64 +0,0 @@ -package dev.skywalker3200.stargate.paper.gate; - -import java.util.ArrayList; -import java.util.List; - -/** - * A fixed 2D blueprint for a gate, read top-to-bottom / left-to-right, e.g.: - *
- * 0000*1*0000
- * 00012221000
- * 00*22222*00
- * ...
- * 
- * '*' = frame, '1' = chevron (a frame block the plugin lights up while dialing), '2' = interior - * ("iris") filled while open, '0' = ignored - not part of the structure at all. - */ -public class GateTemplate { - - public static final char FRAME = '*'; - public static final char CHEVRON = '1'; - public static final char IRIS = '2'; - public static final char IGNORE = '0'; - - private final char[][] grid; // [row][col] - private final int height; - private final int width; - - public GateTemplate(List rows) { - this.height = rows.size(); - int w = 0; - for (String row : rows) w = Math.max(w, row.length()); - this.width = w; - this.grid = new char[height][width]; - for (int r = 0; r < height; r++) { - String row = rows.get(r); - for (int c = 0; c < width; c++) { - grid[r][c] = c < row.length() ? row.charAt(c) : IGNORE; - } - } - } - - public int getHeight() { return height; } - public int getWidth() { return width; } - - public char symbolAt(int row, int col) { - if (row < 0 || row >= height || col < 0 || col >= width) return IGNORE; - return grid[row][col]; - } - - public boolean isFrameSymbol(char c) { - return c == FRAME || c == CHEVRON; - } - - /** All (row, col) cells matching the given symbol, in row-major order. */ - public List cellsOf(char symbol) { - List cells = new ArrayList<>(); - for (int r = 0; r < height; r++) { - for (int c = 0; c < width; c++) { - if (grid[r][c] == symbol) cells.add(new int[]{r, c}); - } - } - return cells; - } -} diff --git a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateTemplateScanner.java b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateTemplateScanner.java deleted file mode 100644 index d7d657e..0000000 --- a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateTemplateScanner.java +++ /dev/null @@ -1,112 +0,0 @@ -package dev.skywalker3200.stargate.paper.gate; - -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * Matches a {@link GateTemplate} against the world: given a block the player punched and the - * horizontal direction they were facing, tries every plausible way the punched block could be - * one of the template's frame/chevron cells, and checks the rest of the template lines up. - * - * The template is built in a vertical plane facing the player - columns run along the world's - * X axis if the player faced north/south, or along Z if they faced east/west. Row 0 is the top - * of the gate; increasing row means decreasing Y. - */ -public class GateTemplateScanner { - - private final GateTemplate template; - private final Set frameMaterials; - - public GateTemplateScanner(GateTemplate template, Set frameMaterials) { - this.template = template; - this.frameMaterials = frameMaterials; - } - - public static final class ScanResult { - public final GateStructure structure; // null on failure - public final String failureReason; // null on success - - private ScanResult(GateStructure structure, String failureReason) { - this.structure = structure; - this.failureReason = failureReason; - } - - static ScanResult ok(GateStructure s) { return new ScanResult(s, null); } - static ScanResult fail(String reason) { return new ScanResult(null, reason); } - - public boolean isSuccess() { return structure != null; } - } - - /** True for NORTH/SOUTH, false for EAST/WEST; anything else gets snapped to the nearer of the two axes. */ - private boolean columnsRunAlongX(BlockFace facing) { - return facing == BlockFace.NORTH || facing == BlockFace.SOUTH; - } - - public ScanResult scan(Block punchedBlock, BlockFace facing) { - if (!frameMaterials.contains(punchedBlock.getType())) { - return ScanResult.fail("That block isn't listed in gate.frame-materials."); - } - - boolean alongX = columnsRunAlongX(facing); - - List candidates = new ArrayList<>(); - candidates.addAll(template.cellsOf(GateTemplate.FRAME)); - candidates.addAll(template.cellsOf(GateTemplate.CHEVRON)); - - for (int[] anchor : candidates) { - int anchorRow = anchor[0]; - int anchorCol = anchor[1]; - - List frame = new ArrayList<>(); - List chevrons = new ArrayList<>(); - List iris = new ArrayList<>(); - boolean matched = true; - - outer: - for (int r = 0; r < template.getHeight() && matched; r++) { - for (int c = 0; c < template.getWidth(); c++) { - char symbol = template.symbolAt(r, c); - if (symbol == GateTemplate.IGNORE) continue; - - Block world = worldBlockFor(punchedBlock, alongX, anchorRow, anchorCol, r, c); - - if (template.isFrameSymbol(symbol)) { - if (!frameMaterials.contains(world.getType())) { - matched = false; - break outer; - } - frame.add(world); - if (symbol == GateTemplate.CHEVRON) { - chevrons.add(new GateStructure.ChevronSlot(world, world.getType())); - } - } else if (symbol == GateTemplate.IRIS) { - iris.add(world); - } - } - } - - if (matched) { - return ScanResult.ok(new GateStructure(frame, chevrons, iris)); - } - } - - return ScanResult.fail("Found a frame block, but no orientation of the configured template lines up " - + "starting there. Make sure you're facing the gate square-on (not at an angle) when you punch it, " - + "and that the build matches gate.template exactly."); - } - - private Block worldBlockFor(Block punchedBlock, boolean alongX, int anchorRow, int anchorCol, int r, int c) { - int dy = anchorRow - r; // row increases downward, Y decreases as row increases - int dCol = c - anchorCol; - if (alongX) { - return punchedBlock.getRelative(dCol, dy, 0); - } else { - return punchedBlock.getRelative(0, dy, dCol); - } - } -} diff --git a/stargate-paper/src/main/resources/config.yml b/stargate-paper/src/main/resources/config.yml index e644e32..6306dbc 100644 --- a/stargate-paper/src/main/resources/config.yml +++ b/stargate-paper/src/main/resources/config.yml @@ -26,38 +26,29 @@ cross-server: enabled: false gate: - # The exact shape of a gate, read top row to bottom row. This is matched precisely - # against the world - no flood-fill guessing. Symbols: - # * = frame block (never touched by the plugin) - # 1 = chevron - a frame block that lights up to chevron-lit-material while - # dialing/open, then reverts to whatever it looked like at rest when closed - # 2 = interior ("iris") - filled with iris-material while open, air while closed - # 0 = ignored - not part of the gate at all - # Must be built in a single vertical plane, one block thick, aligned to the world's - # X or Z axis (i.e. facing north/south/east/west, not built at an angle). - template: - - "00000000000" - - "0000*1*0000" - - "00012221000" - - "00*22222*00" - - "0*2222222*0" - - "01222222210" - - "0*2222222*0" - - "00*22222*00" - - "00012221000" - - "0000***0000" - - "00000000000" - # Any of these materials satisfy a '*' or '1' cell above. Mix and match to match your - # build (the default matches an obsidian/gilded-blackstone ring). + # Any of these materials count as the ring's frame (and count as a valid chevron + # material too, since chevrons are just frame blocks the plugin temporarily swaps). + # Mix and match to match your build (the default matches an obsidian/gilded-blackstone + # ring). The ring can be any size or shape, as long as it's a single connected loop + # made only of these materials with a fully sealed hollow interior - no fixed template + # or size is required. frame-materials: - OBSIDIAN - GOLD_BLOCK - GILDED_BLACKSTONE - BIRCH_PLANKS - OAK_PLANKS + # Chevrons aren't a separate material you place. The plugin flood-fills the ring, + # walks it in geometric order, and picks out the blocks that stick further outward + # than their ring-neighbours - the "elbow" points of the shape, same as a real + # 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 + max-frame-blocks: 300 + max-iris-blocks: 200 + min-frame-blocks: 8 # After placing a "[Stargate]" sign, the owner has this long to left-click (punch) a # block of the actual gate frame to link it - the sign itself can sit anywhere, like a # DHD console away from the gate, instead of being mounted directly on the frame.