Add fixed-destination gates and /sg networks command

Sign line 4 now accepts fixed:GateName to lock a gate to one destination
without cycling, and /sg networks lists every known network.
This commit is contained in:
Michael Burgess
2026-08-09 09:56:12 -04:00
parent 83a0392ee9
commit d651f6586f
6 changed files with 31 additions and 8 deletions
+2 -1
View File
@@ -26,7 +26,8 @@ Build everything with `./gradlew build`. Jars land in each module's `build/libs/
- Line 1: `[Stargate]` - Line 1: `[Stargate]`
- Line 2: network name (blank = default network) - Line 2: network name (blank = default network)
- Line 3: gate name (blank = auto-generated) - Line 3: gate name (blank = auto-generated)
- Line 4: `hidden` to keep it out of the cycle list, otherwise blank - 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
The plugin flood-fills the frame from the block behind the sign, confirms it's a The plugin flood-fills the frame from the block behind the sign, confirms it's a
fully enclosed ring, and registers the gate. If nothing happens, the ring isn't fully enclosed ring, and registers the gate. If nothing happens, the ring isn't
@@ -13,8 +13,8 @@ import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.TreeSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class StargateCommand implements CommandExecutor, TabCompleter { public class StargateCommand implements CommandExecutor, TabCompleter {
@@ -30,11 +30,18 @@ public class StargateCommand implements CommandExecutor, TabCompleter {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0) { if (args.length == 0) {
sender.sendMessage(Component.text("Usage: /sg <list|destroy|reload>", NamedTextColor.YELLOW)); sender.sendMessage(Component.text("Usage: /sg <list|networks|destroy|reload>", NamedTextColor.YELLOW));
return true; return true;
} }
switch (args[0].toLowerCase()) { switch (args[0].toLowerCase()) {
case "networks" -> {
TreeSet<String> networks = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
for (RuntimeGate rg : gateManager.all()) {
networks.add(rg.getGate().getNetwork());
}
sender.sendMessage(Component.text("Networks (" + networks.size() + "): " + String.join(", ", networks), NamedTextColor.AQUA));
}
case "reload" -> { case "reload" -> {
if (!sender.hasPermission("stargate.admin")) { if (!sender.hasPermission("stargate.admin")) {
sender.sendMessage(Component.text("No permission.", NamedTextColor.RED)); sender.sendMessage(Component.text("No permission.", NamedTextColor.RED));
@@ -82,7 +89,7 @@ public class StargateCommand implements CommandExecutor, TabCompleter {
@Override @Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) { if (args.length == 1) {
return List.of("list", "destroy", "reload").stream() return List.of("list", "networks", "destroy", "reload").stream()
.filter(s -> s.startsWith(args[0].toLowerCase())) .filter(s -> s.startsWith(args[0].toLowerCase()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@@ -109,6 +109,10 @@ public class GateManager {
} }
public RuntimeGate createGate(Block signBlock, String network, String name, UUID owner, EnumSet<Gate.Flag> flags) { public RuntimeGate createGate(Block signBlock, String network, String name, UUID owner, EnumSet<Gate.Flag> flags) {
return createGate(signBlock, network, name, owner, flags, null);
}
public RuntimeGate createGate(Block signBlock, String network, String name, UUID owner, EnumSet<Gate.Flag> flags, String fixedDestination) {
Block attached = attachedFrameBlock(signBlock); Block attached = attachedFrameBlock(signBlock);
if (attached == null) return null; if (attached == null) return null;
GateStructure structure = scanner.scan(attached); GateStructure structure = scanner.scan(attached);
@@ -118,7 +122,7 @@ public class GateManager {
Gate gate = new Gate(UUID.randomUUID(), name, network, plugin.getServerId(), Gate gate = new Gate(UUID.randomUUID(), name, network, plugin.getServerId(),
exit.getWorld().getName(), exit.getBlockX(), exit.getBlockY(), exit.getBlockZ(), exit.getYaw(), exit.getWorld().getName(), exit.getBlockX(), exit.getBlockY(), exit.getBlockZ(), exit.getYaw(),
signBlock.getX(), signBlock.getY(), signBlock.getZ(), signBlock.getWorld().getName(), signBlock.getX(), signBlock.getY(), signBlock.getZ(), signBlock.getWorld().getName(),
signBlockFacing(signBlock), owner, flags, null); signBlockFacing(signBlock), owner, flags, fixedDestination);
RuntimeGate rg = new RuntimeGate(gate, structure); RuntimeGate rg = new RuntimeGate(gate, structure);
gatesById.put(gate.getId(), rg); gatesById.put(gate.getId(), rg);
@@ -46,12 +46,19 @@ public class SignCreateListener implements Listener {
if (name == null || name.isBlank()) name = "Gate-" + Integer.toHexString((int) (Math.random() * 0xFFFF)); if (name == null || name.isBlank()) name = "Gate-" + Integer.toHexString((int) (Math.random() * 0xFFFF));
EnumSet<Gate.Flag> flags = EnumSet.of(Gate.Flag.PUBLIC); EnumSet<Gate.Flag> flags = EnumSet.of(Gate.Flag.PUBLIC);
String fixedDestination = null;
String flagLine = stripped(event.line(3)); String flagLine = stripped(event.line(3));
if (flagLine != null) { if (flagLine != null) {
if (flagLine.equalsIgnoreCase("hidden")) { flags.clear(); flags.add(Gate.Flag.HIDDEN); } if (flagLine.equalsIgnoreCase("hidden")) {
flags.clear();
flags.add(Gate.Flag.HIDDEN);
} else if (flagLine.toLowerCase().startsWith("fixed:")) {
fixedDestination = flagLine.substring("fixed:".length()).trim();
flags.add(Gate.Flag.FIXED);
}
} }
RuntimeGate rg = gateManager.createGate(event.getBlock(), network, name, player.getUniqueId(), flags); RuntimeGate rg = gateManager.createGate(event.getBlock(), network, name, player.getUniqueId(), flags, fixedDestination);
if (rg == null) { if (rg == null) {
player.sendMessage(Component.text("No valid gate structure found. Build the frame first, then place the sign.", NamedTextColor.RED)); player.sendMessage(Component.text("No valid gate structure found. Build the frame first, then place the sign.", NamedTextColor.RED));
resetLine(event); resetLine(event);
@@ -35,6 +35,10 @@ public final class SignRenderer {
} }
private static Component destinationLine(RuntimeGate rg, GateManager gateManager) { private static Component destinationLine(RuntimeGate rg, GateManager gateManager) {
if (rg.getGate().isFixed()) {
String dest = rg.getGate().getFixedDestination();
return Component.text("=> " + (dest == null || dest.isBlank() ? "?" : dest), NamedTextColor.DARK_PURPLE);
}
List<RuntimeGate> destinations = gateManager.destinationsFor(rg); List<RuntimeGate> destinations = gateManager.destinationsFor(rg);
if (destinations.isEmpty()) { if (destinations.isEmpty()) {
return Component.text("no destinations", NamedTextColor.DARK_GRAY); return Component.text("no destinations", NamedTextColor.DARK_GRAY);
+1 -1
View File
@@ -10,7 +10,7 @@ commands:
stargate: stargate:
description: Manage Stargate networks and gates. description: Manage Stargate networks and gates.
aliases: [sg] aliases: [sg]
usage: /sg <create|destroy|network|reload|list> ... usage: /sg <list|networks|destroy|reload> ...
permissions: permissions:
stargate.use: stargate.use: