Auto-select chevron slots instead of requiring a distinct unlit material
Chevrons no longer need to be built out of a special block - the plugin picks gate.chevron-count frame blocks evenly spaced around the scanned ring and remembers their resting material, so a chevron can rest as plain obsidian (matching the rest of the frame) and only reveal itself by swapping to gate.chevron-lit-material while dialing/open. Restoring on close now puts back the original block instead of a fixed material, so alternating decorative frames (e.g. obsidian/gilded blackstone) are preserved exactly.
This commit is contained in:
@@ -11,8 +11,8 @@ import java.util.logging.Logger;
|
||||
public class GateConfig {
|
||||
|
||||
public final Set<Material> 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);
|
||||
|
||||
@@ -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<Block> chevrons = from.getStructure() != null ? from.getStructure().getChevrons() : List.of();
|
||||
List<GateStructure.ChevronSlot> 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()) {
|
||||
|
||||
+21
-3
@@ -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<Block> frame;
|
||||
private final List<Block> chevrons;
|
||||
private final List<ChevronSlot> chevrons;
|
||||
private final List<Block> iris;
|
||||
|
||||
public GateStructure(List<Block> frame, List<Block> chevrons, List<Block> iris) {
|
||||
public GateStructure(List<Block> frame, List<ChevronSlot> chevrons, List<Block> iris) {
|
||||
this.frame = frame;
|
||||
this.chevrons = chevrons;
|
||||
this.iris = iris;
|
||||
}
|
||||
|
||||
public List<Block> getFrame() { return frame; }
|
||||
public List<Block> getChevrons() { return chevrons; }
|
||||
public List<ChevronSlot> getChevrons() { return chevrons; }
|
||||
public List<Block> getIris() { return iris; }
|
||||
}
|
||||
|
||||
+36
-18
@@ -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<Material> 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<Material> frameMaterials, Material chevronUnlit, Material chevronLit,
|
||||
public GateStructureScanner(Set<Material> 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<Long> frameKeys = new HashSet<>();
|
||||
List<Block> frameBlocks = new ArrayList<>();
|
||||
List<Block> chevronBlocks = new ArrayList<>();
|
||||
Deque<Block> 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<Block> 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<GateStructure.ChevronSlot> pickChevrons(List<Block> frameBlocks) {
|
||||
int count = Math.max(0, Math.min(chevronCount, frameBlocks.size()));
|
||||
List<GateStructure.ChevronSlot> slots = new ArrayList<>(count);
|
||||
if (count == 0) return slots;
|
||||
double step = frameBlocks.size() / (double) count;
|
||||
Set<Integer> 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<Block> floodInterior(Block seed, Set<Long> frameKeys) {
|
||||
Set<Long> visited = new HashSet<>();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user