Use PistonStructureResolver in PistonCalculator

Numbering now comes directly from the vanilla resolver instead of a
custom breadth-first search, so the order matches the real move order
(including backtracking through slime/honey chains). The display of
more than 12 blocks is removed; a blocked piston is shown in red.

Refs: #158
This commit is contained in:
2026-10-18 20:09:10 +02:00
parent 253a975c73
commit 230f8c521a
@@ -23,15 +23,17 @@ import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.entity.*;
import de.steamwar.linkage.Linked;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.piston.PistonStructureResolver;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.PistonMoveReaction;
import org.bukkit.block.TileState;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Piston;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Display;
import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay;
@@ -170,7 +172,7 @@ public class PistonCalculator implements Listener {
private boolean pulling = false;
private List<Location> movedBlocks = new ArrayList<>();
private List<Location> brokenBlocks = new ArrayList<>();
private Set<Location> immovableBlocks = new HashSet<>();
private boolean blocked = false;
public PistOrder(Block piston) {
this.piston = piston;
@@ -179,7 +181,7 @@ public class PistonCalculator implements Listener {
public void calculate() {
movedBlocks.clear();
brokenBlocks.clear();
immovableBlocks.clear();
blocked = false;
if (piston.isEmpty()) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
@@ -199,7 +201,7 @@ public class PistonCalculator implements Listener {
pulling = piston.getType() == Material.STICKY_PISTON && (piston.getRelative(pistonData.getFacing()).getType() == Material.AIR || pistonData.isExtended());
calculate(piston, pistonData.getFacing(), (pulling ? pistonData.getFacing().getOppositeFace() : pistonData.getFacing()));
resolve(pistonData.getFacing());
Collections.reverse(movedBlocks);
Collections.reverse(brokenBlocks);
@@ -207,86 +209,23 @@ public class PistonCalculator implements Listener {
visualize();
}
private void calculate(Block origin, BlockFace facing, BlockFace direction) {
Block calcOrigin = origin;
if (facing != direction) calcOrigin = origin.getRelative(facing, 3);
List<Block> toCalc = new LinkedList<>();
calcDirection(origin, null, calcOrigin, facing != direction ? origin.getRelative(facing) : null, facing, direction, toCalc);
while (!toCalc.isEmpty()) {
Block current = toCalc.remove(0);
if (!movedBlocks.contains(current.getLocation())) {
movedBlocks.add(current.getLocation());
}
Material type = current.getType();
if (type != Material.SLIME_BLOCK && type != Material.HONEY_BLOCK) continue;
Material oppositeType = type == Material.SLIME_BLOCK ? Material.HONEY_BLOCK : Material.SLIME_BLOCK;
for (BlockFace face : FACES) {
Block block = current.getRelative(face);
if (block.getType().isAir()) continue;
if (isImmovable(block) || block.getPistonMoveReaction() == PistonMoveReaction.BREAK) continue;
if (face != direction || face == direction.getOppositeFace()) {
switch (block.getType()) {
case BLACK_GLAZED_TERRACOTTA:
case GRAY_GLAZED_TERRACOTTA:
case BLUE_GLAZED_TERRACOTTA:
case BROWN_GLAZED_TERRACOTTA:
case CYAN_GLAZED_TERRACOTTA:
case GREEN_GLAZED_TERRACOTTA:
case LIGHT_BLUE_GLAZED_TERRACOTTA:
case LIGHT_GRAY_GLAZED_TERRACOTTA:
case LIME_GLAZED_TERRACOTTA:
case MAGENTA_GLAZED_TERRACOTTA:
case ORANGE_GLAZED_TERRACOTTA:
case PINK_GLAZED_TERRACOTTA:
case PURPLE_GLAZED_TERRACOTTA:
case RED_GLAZED_TERRACOTTA:
case WHITE_GLAZED_TERRACOTTA:
case YELLOW_GLAZED_TERRACOTTA:
continue;
default:
break;
}
}
if (block.getType() != oppositeType) {
if (!movedBlocks.contains(block.getLocation())) toCalc.add(block);
calcDirection(null, origin, block, null, facing, direction, toCalc);
}
}
private void resolve(BlockFace facing) {
PistonStructureResolver resolver = new PistonStructureResolver(
((CraftWorld) piston.getWorld()).getHandle(),
new BlockPos(piston.getX(), piston.getY(), piston.getZ()),
Direction.valueOf(facing.name()),
!pulling
);
if (!resolver.resolve()) {
blocked = true;
return;
}
movedBlocks.remove(origin.getLocation());
if (facing != direction) movedBlocks.remove(origin.getRelative(facing, 1).getLocation());
if (pulling) immovableBlocks.remove(origin.getRelative(facing).getLocation());
resolver.getToPush().forEach(pos -> movedBlocks.add(toLocation(pos)));
resolver.getToDestroy().forEach(pos -> brokenBlocks.add(toLocation(pos)));
}
private void calcDirection(Block origin, Block blockOrigin, Block calcOrigin, Block ignore, BlockFace facing, BlockFace direction, List<Block> toCalc) {
for (int i = 1; i < 24; i++) {
Block block = calcOrigin.getRelative(direction, i);
if (block.equals(ignore)) return;
if (block.getPistonMoveReaction() == PistonMoveReaction.BREAK) {
if (!brokenBlocks.contains(block.getLocation())) {
brokenBlocks.add(block.getLocation());
}
return;
}
if (isImmovable(block)) {
immovableBlocks.add(block.getLocation());
return;
}
if (block.getType().isAir()) return;
if (facing == direction && block.equals(blockOrigin)) {
immovableBlocks.add(block.getLocation());
return;
}
if (facing != direction && (block.equals(origin) || block.getRelative(facing.getOppositeFace()).equals(origin))) {
return;
}
if (!movedBlocks.contains(block.getLocation())) toCalc.add(block);
}
private Location toLocation(BlockPos pos) {
return new Location(piston.getWorld(), pos.getX(), pos.getY(), pos.getZ());
}
private void visualize() {
@@ -321,21 +260,11 @@ public class PistonCalculator implements Listener {
textDisplay.setBrightness(new Display.Brightness(15, 15));
}
for (Location location : immovableBlocks) {
CWireframe wireframe = new CWireframe(server);
wireframe.setPos1(location);
wireframe.setPos2(location);
wireframe.setWidth(1 / 32f);
wireframe.setBlock(Material.RED_CONCRETE.createBlockData());
}
CWireframe wireframe = new CWireframe(server);
wireframe.setPos1(piston.getLocation());
wireframe.setPos2(piston.getLocation());
if (!immovableBlocks.isEmpty()) {
if (blocked) {
wireframe.setBlock(Material.RED_CONCRETE.createBlockData());
} else if (movedBlocks.size() > 12) {
wireframe.setBlock(Material.YELLOW_CONCRETE.createBlockData());
} else {
wireframe.setBlock(Material.LIME_CONCRETE.createBlockData());
}
@@ -348,7 +277,11 @@ public class PistonCalculator implements Listener {
} else {
text.append("§ePush\n");
}
text.append("§f").append(movedBlocks.size()).append(" §eBlocks");
if (blocked) {
text.append("§cBlocked");
} else {
text.append("§f").append(movedBlocks.size()).append(" §eBlocks");
}
textDisplay.setText(text.toString());
textDisplay.setBillboard(Display.Billboard.CENTER);
textDisplay.setAlignment(TextDisplay.TextAlignment.CENTER);
@@ -358,26 +291,4 @@ public class PistonCalculator implements Listener {
textDisplay.setBrightness(new Display.Brightness(15, 15));
}
}
private boolean isImmovable(Block block) {
if (block.isEmpty() || block.isLiquid()) {
return false;
}
BlockData blockData = block.getBlockData();
if (blockData instanceof Piston) {
return ((Piston) blockData).isExtended();
}
if (block.getState() instanceof TileState) {
return true;
}
PistonMoveReaction reaction = block.getPistonMoveReaction();
if (reaction == PistonMoveReaction.IGNORE) return true;
if (reaction == PistonMoveReaction.BLOCK) return true;
switch (block.getType()) {
case OBSIDIAN:
return true;
default:
return false;
}
}
}