diff --git a/README.md b/README.md index 5ecaf40..95fea30 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,15 @@ Build everything with `./gradlew build`. Jars land in each module's `build/libs/ ## Building a gate -1. Build a ring out of any block in `gate.frame-materials` (default: obsidian, gold - block, birch/oak planks — matches an obsidian+gold arch design). Leave the middle - hollow. -2. Embed a few blocks of `gate.chevron-unlit-material` (default black stained glass) - into the ring — these light up to `gate.chevron-lit-material` (default glowstone) - as the gate dials. +1. Build a fully-enclosed ring out of any block(s) in `gate.frame-materials` (default: + obsidian, gold block, gilded blackstone, birch/oak planks). Leave the middle hollow. + No gaps - the ring must be a sealed loop. +2. Nothing else to build for chevrons. On creation, the plugin automatically picks + `gate.chevron-count` (default 6) frame blocks, evenly spaced around the ring, and + remembers what they looked like at rest. While dialing/open those blocks swap to + `gate.chevron-lit-material` (default gilded blackstone); closing the gate restores + them to their original block, so a chevron can rest as plain obsidian and blend + invisibly into the frame until it lights up. 3. Place a sign on the outside of the frame with: - Line 1: `[Stargate]` - Line 2: network name (blank = default network) 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 0f17ced..950f2f2 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 @@ -11,8 +11,8 @@ import java.util.logging.Logger; public class GateConfig { public final Set frameMaterials; - public final Material chevronUnlit; public final Material chevronLit; + public final int chevronCount; public final Material irisMaterial; public final int maxFrameBlocks; public final int maxIrisBlocks; @@ -32,8 +32,8 @@ public class GateConfig { if (materials.isEmpty()) materials.add(Material.OBSIDIAN); this.frameMaterials = materials; - this.chevronUnlit = matOr(cfg.getString("gate.chevron-unlit-material"), Material.BLACK_STAINED_GLASS, logger); - this.chevronLit = matOr(cfg.getString("gate.chevron-lit-material"), Material.GLOWSTONE, logger); + this.chevronLit = matOr(cfg.getString("gate.chevron-lit-material"), Material.GILDED_BLACKSTONE, logger); + this.chevronCount = cfg.getInt("gate.chevron-count", 6); 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); 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 d7d56f0..25c7cc7 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 @@ -38,7 +38,7 @@ public class GateManager { public void reloadConfig() { this.config = new GateConfig(plugin.getConfig(), plugin.getLogger()); - this.scanner = new GateStructureScanner(config.frameMaterials, config.chevronUnlit, config.chevronLit, + this.scanner = new GateStructureScanner(config.frameMaterials, config.chevronLit, config.chevronCount, config.maxFrameBlocks, config.maxIrisBlocks, config.minFrameBlocks); } @@ -206,7 +206,7 @@ public class GateManager { if (from.isOpen()) closeGate(from); if (to.isOpen()) closeGate(to); - List chevrons = from.getStructure() != null ? from.getStructure().getChevrons() : List.of(); + List chevrons = from.getStructure() != null ? from.getStructure().getChevrons() : List.of(); int delay = Math.max(1, config.chevronTickDelay); Runnable finish = () -> { @@ -227,7 +227,7 @@ public class GateManager { finish.run(); return; } - Block chevron = chevrons.get(i[0]); + Block chevron = chevrons.get(i[0]).getBlock(); chevron.setType(config.chevronLit); if (config.playSounds) { chevron.getWorld().playSound(chevron.getLocation(), Sound.BLOCK_STONE_STEP, 1f, 1.4f); @@ -248,8 +248,8 @@ public class GateManager { b.setBlockData(lvl); } } - for (Block c : gate.getStructure().getChevrons()) { - c.setType(config.chevronLit); + for (GateStructure.ChevronSlot c : gate.getStructure().getChevrons()) { + c.getBlock().setType(config.chevronLit); } } if (config.playSounds) { @@ -274,8 +274,8 @@ public class GateManager { for (Block b : gate.getStructure().getIris()) { b.setType(org.bukkit.Material.AIR); } - for (Block c : gate.getStructure().getChevrons()) { - c.setType(config.chevronUnlit); + for (GateStructure.ChevronSlot c : gate.getStructure().getChevrons()) { + c.getBlock().setType(c.getRestingMaterial()); } } if (other != null && other.isOpen()) { diff --git a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateStructure.java b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateStructure.java index 61ba634..9941e29 100644 --- a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateStructure.java +++ b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateStructure.java @@ -1,22 +1,40 @@ package dev.skywalker3200.stargate.paper.gate; +import org.bukkit.Material; import org.bukkit.block.Block; import java.util.List; /** The physical blocks that make up a scanned gate: frame ring, chevrons, and interior iris. */ public class GateStructure { + + /** A chevron is just a frame block the plugin swaps materials on; we remember what it looked + * like before dialing so closing the gate restores it exactly, even if the frame is decorative + * and mixes several materials (e.g. alternating obsidian / gilded blackstone). */ + public static final class ChevronSlot { + private final Block block; + private final Material restingMaterial; + + public ChevronSlot(Block block, Material restingMaterial) { + this.block = block; + this.restingMaterial = restingMaterial; + } + + public Block getBlock() { return block; } + public Material getRestingMaterial() { return restingMaterial; } + } + private final List frame; - private final List chevrons; + private final List chevrons; private final List iris; - public GateStructure(List frame, List chevrons, List iris) { + public GateStructure(List frame, List chevrons, List iris) { this.frame = frame; this.chevrons = chevrons; this.iris = iris; } public List getFrame() { return frame; } - public List getChevrons() { return chevrons; } + public List getChevrons() { return chevrons; } public List getIris() { return iris; } } 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 23cdecd..4d4314c 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 @@ -13,8 +13,14 @@ import java.util.Set; /** * Flood-fill scanner that discovers a gate's physical structure starting from the block - * a control sign is attached to: the frame ring, the chevron blocks embedded in it, and - * the enclosed interior ("iris") that gets filled with water while the gate is open. + * a control sign is attached to: the frame ring, N evenly-spaced chevron slots picked out + * of that ring, and the enclosed interior ("iris") that gets filled with water while the + * gate is open. + * + * Chevrons are not identified by a special "unlit" material - they're ordinary frame blocks + * that the plugin temporarily swaps to {@code chevronLitMaterial} while dialing/open, then + * restores to whatever they originally looked like. That way a chevron can rest as plain + * obsidian, indistinguishable from the rest of the ring, exactly like the reference build. */ public class GateStructureScanner { @@ -39,28 +45,25 @@ public class GateStructureScanner { } private final Set frameMaterials; - private final Material chevronUnlit; - private final Material chevronLit; + private final Material chevronLitMaterial; + private final int chevronCount; private final int maxFrameBlocks; private final int maxIrisBlocks; private final int minFrameBlocks; - public GateStructureScanner(Set frameMaterials, Material chevronUnlit, Material chevronLit, + public GateStructureScanner(Set frameMaterials, Material chevronLitMaterial, int chevronCount, int maxFrameBlocks, int maxIrisBlocks, int minFrameBlocks) { this.frameMaterials = frameMaterials; - this.chevronUnlit = chevronUnlit; - this.chevronLit = chevronLit; + this.chevronLitMaterial = chevronLitMaterial; + this.chevronCount = chevronCount; this.maxFrameBlocks = maxFrameBlocks; this.maxIrisBlocks = maxIrisBlocks; this.minFrameBlocks = minFrameBlocks; } private boolean isFrameMaterial(Material m) { - return frameMaterials.contains(m) || m == chevronUnlit || m == chevronLit; - } - - private boolean isChevronMaterial(Material m) { - return m == chevronUnlit || m == chevronLit; + // a gate re-scanned mid-dial (e.g. server restart) may have chevrons still lit + return frameMaterials.contains(m) || m == chevronLitMaterial; } /** Convenience wrapper for callers that only care about success/failure, not why. */ @@ -84,14 +87,13 @@ public class GateStructureScanner { } } if (!isFrameMaterial(frameSeed.getType())) { - return ScanResult.fail("The sign isn't touching a block listed in gate.frame-materials " - + "(nor is it touching the chevron material). Check the sign is mounted directly on the frame, " - + "and that every block type in your ring is listed in config.yml."); + return ScanResult.fail("The sign isn't touching a block listed in gate.frame-materials. " + + "Check the sign is mounted directly on the frame, and that every block type in your ring " + + "is listed in config.yml."); } Set frameKeys = new HashSet<>(); List frameBlocks = new ArrayList<>(); - List chevronBlocks = new ArrayList<>(); Deque queue = new ArrayDeque<>(); queue.add(frameSeed); frameKeys.add(key(frameSeed)); @@ -99,7 +101,6 @@ public class GateStructureScanner { while (!queue.isEmpty()) { Block cur = queue.poll(); frameBlocks.add(cur); - if (isChevronMaterial(cur.getType())) chevronBlocks.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 " @@ -140,7 +141,7 @@ public class GateStructureScanner { triedAnyInterior = true; List iris = floodInterior(candidate, frameKeys); if (iris != null && !iris.isEmpty()) { - return ScanResult.ok(new GateStructure(frameBlocks, chevronBlocks, iris)); + return ScanResult.ok(new GateStructure(frameBlocks, pickChevrons(frameBlocks), iris)); } } } @@ -155,6 +156,23 @@ public class GateStructureScanner { + "(including the floor/ceiling of the arch)."); } + /** Picks N frame blocks spaced evenly across the flood-fill order as chevron slots, remembering their resting look. */ + private List pickChevrons(List frameBlocks) { + int count = Math.max(0, Math.min(chevronCount, frameBlocks.size())); + List slots = new ArrayList<>(count); + if (count == 0) return slots; + double step = frameBlocks.size() / (double) count; + Set chosen = new HashSet<>(); + for (int i = 0; i < count; i++) { + int idx = (int) Math.round(i * step) % frameBlocks.size(); + while (chosen.contains(idx)) idx = (idx + 1) % frameBlocks.size(); + chosen.add(idx); + Block block = frameBlocks.get(idx); + slots.add(new GateStructure.ChevronSlot(block, block.getType())); + } + return slots; + } + /** Flood-fills non-frame blocks starting at seed; fails (returns null) if it escapes the frame boundary. */ private List floodInterior(Block seed, Set frameKeys) { Set visited = new HashSet<>(); diff --git a/stargate-paper/src/main/resources/config.yml b/stargate-paper/src/main/resources/config.yml index 223b6c7..5b89169 100644 --- a/stargate-paper/src/main/resources/config.yml +++ b/stargate-paper/src/main/resources/config.yml @@ -34,11 +34,13 @@ gate: - GILDED_BLACKSTONE - BIRCH_PLANKS - OAK_PLANKS - # Chevron blocks embedded in the frame. Build them as chevron-unlit-material; the - # plugin swaps them to chevron-lit-material as the gate dials/opens, and swaps them - # back when the gate closes/deactivates. - chevron-unlit-material: BLACK_STAINED_GLASS - chevron-lit-material: GLOWSTONE + # Chevrons are NOT a separate material you have to place - the plugin automatically + # picks this many frame blocks, evenly spaced around the ring, and swaps them to + # chevron-lit-material while dialing/open, then restores whatever they looked like + # at rest when the gate closes. So a chevron can rest as plain obsidian and only + # stand out once it lights up. + chevron-lit-material: GILDED_BLACKSTONE + chevron-count: 6 # Material poured into the interior (the "event horizon") while the gate is open. iris-material: WATER max-frame-blocks: 300