Actively contain the event horizon so water can never flow out
Scan-time enclosure checking can catch most gaps, but Minecraft's own fluid physics will still spread a water source block into any adjacent open space it finds - a diagonal/stepped ring corner can have a path that isn't a "leak" by the scanner's flood-fill rules (which only ever move orthogonally) but vanilla water flow still takes, since flow is also orthogonal-only and can find edge cases the scanner's enclosure proof didn't need to rule out. Rather than trying to prove every possible ring geometry is leak-proof at scan time, GateManager now tracks every iris block of every locally scanned gate (registered at link time, on restart re-scan, and unregistered on destroy), and a new BlockFromToEvent listener cancels any fluid spread whose source is one of those blocks. This is the same technique real portal-style plugins use: containment enforced by the plugin at the physics level, not by proving the shape is sound in advance. Note: this only stops future leaking. Water that already escaped before this fix needs to be manually cleaned up once.
This commit is contained in:
@@ -65,7 +65,10 @@ material, unsealed interior, too far from the sign, etc.
|
||||
gates on the same network.
|
||||
- **Left-click** the sign: dials the shown destination - chevrons light in sequence,
|
||||
then the interior becomes the event horizon (`gate.iris-open-material`, default
|
||||
water) and travel opens up. It auto-closes after `dialing.open-seconds`.
|
||||
water) and travel opens up. It auto-closes after `dialing.open-seconds`. That water
|
||||
is contained to exactly the iris blocks - the plugin actively cancels any attempt
|
||||
by Minecraft's own fluid physics to spread it further, so it can't leak out through
|
||||
a gap or an odd ring shape and flood the ground around the gate.
|
||||
- **Shift + left-click** the sign: destroys the gate on the spot (same permission
|
||||
check as `/sg destroy` - owner or `stargate.admin`), breaking the sign as if
|
||||
you'd mined it. Quicker than looking at the sign and typing the command.
|
||||
|
||||
@@ -52,6 +52,7 @@ public class StargatePlugin extends JavaPlugin {
|
||||
getServer().getPluginManager().registerEvents(new StructureProtectListener(gateManager), this);
|
||||
getServer().getPluginManager().registerEvents(new dev.skywalker3200.stargate.paper.listener.GateTeleportListener(this, gateManager, crossServerBridge), this);
|
||||
getServer().getPluginManager().registerEvents(new dev.skywalker3200.stargate.paper.listener.GateIrisButtonListener(this, gateManager), this);
|
||||
getServer().getPluginManager().registerEvents(new dev.skywalker3200.stargate.paper.listener.GateWaterContainmentListener(gateManager), this);
|
||||
|
||||
StargateCommand command = new StargateCommand(this, gateManager);
|
||||
getCommand("stargate").setExecutor(command);
|
||||
|
||||
+31
-1
@@ -34,6 +34,11 @@ public class GateManager {
|
||||
|
||||
private final Map<UUID, RuntimeGate> gatesById = new HashMap<>();
|
||||
private final Map<String, RuntimeGate> gatesBySignBlock = new HashMap<>(); // "world,x,y,z" -> gate
|
||||
// Every iris block of every locally-scanned gate, regardless of open/closed - used to stop
|
||||
// Minecraft's own fluid physics from spreading the event horizon water past exactly these
|
||||
// blocks, no matter what shape the ring is. Scan-time enclosure checking can't guarantee
|
||||
// that on its own for every possible geometry, so this is the actual containment.
|
||||
private final Set<String> irisKeys = new HashSet<>();
|
||||
|
||||
public GateManager(StargatePlugin plugin, GateStorage storage) {
|
||||
this.plugin = plugin;
|
||||
@@ -52,6 +57,7 @@ public class GateManager {
|
||||
public void loadAll() {
|
||||
gatesById.clear();
|
||||
gatesBySignBlock.clear();
|
||||
irisKeys.clear();
|
||||
for (Gate gate : storage.loadAll()) {
|
||||
GateStructure structure = null;
|
||||
if (gate.getServerId().equals(plugin.getServerId())) {
|
||||
@@ -71,7 +77,10 @@ public class GateManager {
|
||||
// idle - applyIrisMaterial() still shows the shield if it was left closed, since
|
||||
// that's independent of the wormhole being active
|
||||
RuntimeGate rg = new RuntimeGate(gate, structure);
|
||||
if (structure != null) applyIrisMaterial(rg);
|
||||
if (structure != null) {
|
||||
applyIrisMaterial(rg);
|
||||
registerIrisKeys(structure);
|
||||
}
|
||||
gatesById.put(gate.getId(), rg);
|
||||
if (gate.getServerId().equals(plugin.getServerId())) {
|
||||
gatesBySignBlock.put(signKey(gate.getSignWorld(), gate.getSignX(), gate.getSignY(), gate.getSignZ()), rg);
|
||||
@@ -231,6 +240,7 @@ public class GateManager {
|
||||
|
||||
RuntimeGate rg = new RuntimeGate(gate, structure);
|
||||
applyIrisMaterial(rg);
|
||||
registerIrisKeys(structure);
|
||||
gatesById.put(gate.getId(), rg);
|
||||
gatesBySignBlock.put(signKey(gate.getSignWorld(), gate.getSignX(), gate.getSignY(), gate.getSignZ()), rg);
|
||||
storage.saveGate(gate);
|
||||
@@ -241,11 +251,31 @@ public class GateManager {
|
||||
|
||||
public void destroyGate(RuntimeGate rg) {
|
||||
closeGate(rg);
|
||||
unregisterIrisKeys(rg.getStructure());
|
||||
gatesById.remove(rg.getGate().getId());
|
||||
gatesBySignBlock.remove(signKey(rg.getGate().getSignWorld(), rg.getGate().getSignX(), rg.getGate().getSignY(), rg.getGate().getSignZ()));
|
||||
storage.deleteGate(rg.getGate().getId());
|
||||
}
|
||||
|
||||
private String blockKey(Block b) {
|
||||
return b.getWorld().getName() + "," + b.getX() + "," + b.getY() + "," + b.getZ();
|
||||
}
|
||||
|
||||
private void registerIrisKeys(GateStructure structure) {
|
||||
if (structure == null) return;
|
||||
for (Block b : structure.getIris()) irisKeys.add(blockKey(b));
|
||||
}
|
||||
|
||||
private void unregisterIrisKeys(GateStructure structure) {
|
||||
if (structure == null) return;
|
||||
for (Block b : structure.getIris()) irisKeys.remove(blockKey(b));
|
||||
}
|
||||
|
||||
/** True if this block is a gate's iris - used to stop water from ever flowing out of one. */
|
||||
public boolean isIrisBlock(Block b) {
|
||||
return irisKeys.contains(blockKey(b));
|
||||
}
|
||||
|
||||
public void saveGate(RuntimeGate rg) {
|
||||
storage.saveGate(rg.getGate());
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package dev.skywalker3200.stargate.paper.listener;
|
||||
|
||||
import dev.skywalker3200.stargate.paper.gate.GateManager;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockFromToEvent;
|
||||
|
||||
/**
|
||||
* Stops the event horizon from ever spreading past exactly the blocks the plugin placed.
|
||||
* Scan-time enclosure checking can catch most gaps, but Minecraft's own fluid physics will
|
||||
* still spread water into any adjacent open space it finds - a stepped/diagonal ring corner
|
||||
* can have a path scan-time checks don't consider a "leak" but vanilla water flow still takes.
|
||||
* This is the actual containment: cancel any flow whose source is a registered iris block.
|
||||
*/
|
||||
public class GateWaterContainmentListener implements Listener {
|
||||
|
||||
private final GateManager gateManager;
|
||||
|
||||
public GateWaterContainmentListener(GateManager gateManager) {
|
||||
this.gateManager = gateManager;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onFlow(BlockFromToEvent event) {
|
||||
if (event.getBlock().getType() != Material.WATER) return;
|
||||
if (gateManager.isIrisBlock(event.getBlock())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user