Improve PistonCalculator
This commit is contained in:
2025-07-29 09:53:17 +02:00
parent 78e176b55e
commit 066f06a6e3
4 changed files with 258 additions and 75 deletions
@@ -21,13 +21,13 @@ package de.steamwar.bausystem.features.util;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.entity.CWireframe;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.entity.RTextDisplay;
import de.steamwar.linkage.Linked;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import net.md_5.bungee.api.ChatMessageType;
import de.steamwar.linkage.MinVersion;
import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -37,101 +37,276 @@ 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.entity.Display;
import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.*;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
@Linked
@MinVersion(20)
public class PistonCalculator implements Listener {
private final Map<Player, Long> DEBOUNCE = new HashMap<>();
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (!event.hasItem() || event.getItem().getType() != Material.SLIME_BALL) return;
if (event.hasItem() && event.getItem().getType() != Material.SLIME_BALL) return;
if (event.getClickedBlock() == null) return;
Block clickedBlock = event.getClickedBlock();
Material blockType = clickedBlock.getType();
if (!(blockType == Material.PISTON || blockType == Material.STICKY_PISTON)) return;
Piston piston = (Piston) clickedBlock.getBlockData();
if (System.currentTimeMillis() - DEBOUNCE.getOrDefault(event.getPlayer(), 0L) <= 200) return;
DEBOUNCE.put(event.getPlayer(), System.currentTimeMillis());
if (blockType == Material.PISTON && piston.isExtended()) {
BauSystem.MESSAGE.sendPrefixless("PISTON_INFO", event.getPlayer(), ChatMessageType.ACTION_BAR, "§a", 0);
Location location = event.getClickedBlock().getLocation();
if (pistOrders.containsKey(location)) {
PistOrder pistOrder = pistOrders.get(location);
if (pistOrder.server.getPlayers().contains(event.getPlayer())) {
pistOrder.server.removePlayer(event.getPlayer());
} else {
pistOrder.server.addPlayer(event.getPlayer());
}
if (pistOrder.server.getPlayers().isEmpty()) {
pistOrders.remove(location);
pistOrder.server.close();
}
return;
}
boolean pulling = blockType == Material.STICKY_PISTON && (clickedBlock.getRelative(piston.getFacing()).getType() == Material.AIR || piston.isExtended());
PistOrder pistOrder = new PistOrder(clickedBlock);
pistOrder.calculate();
pistOrder.server.addPlayer(event.getPlayer());
pistOrders.put(location, pistOrder);
}
CalculationResult result = calc(clickedBlock, piston.getFacing(), (pulling ? piston.getFacing().getOppositeFace() : piston.getFacing()));
result.entityServer.addPlayer(event.getPlayer());
BauSystem.MESSAGE.sendPrefixless("PISTON_INFO", event.getPlayer(), ChatMessageType.ACTION_BAR, result.unmovable ? "§c" : (result.tooMany ? "§e" : "§a"), result.amount);
@EventHandler
public void onBlockPistonExtend(BlockPistonExtendEvent event) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
pistOrders.values().forEach(PistOrder::calculate);
}, 1);
}
@EventHandler
public void onBlockPistonRetract(BlockPistonRetractEvent event) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
pistOrders.values().forEach(PistOrder::calculate);
}, 3);
}
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
pistOrders.values().forEach(PistOrder::calculate);
}, 1);
}
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
if (pistOrders.containsKey(event.getBlock().getLocation())) {
PistOrder pistOrder = pistOrders.remove(event.getBlock().getLocation());
pistOrder.server.close();
}
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
pistOrders.values().forEach(PistOrder::calculate);
}, 1);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
DEBOUNCE.remove(event.getPlayer());
Set<Location> toRemove = new HashSet<>();
pistOrders.forEach((location, pistOrder) -> {
pistOrder.server.removePlayer(event.getPlayer());
if (pistOrder.server.getPlayers().isEmpty()) {
toRemove.add(location);
pistOrder.server.close();
}
});
toRemove.forEach(pistOrders::remove);
}
private final BlockFace[] FACES = new BlockFace[]{BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST};
private CalculationResult calc(Block origin, BlockFace facing, BlockFace direction) {
Set<Block> blockSet = new HashSet<>();
Set<Location> unmovable = new HashSet<>();
private final Map<Location, PistOrder> pistOrders = new HashMap<>();
Block calcOrigin = origin;
if (facing != direction) calcOrigin = origin.getRelative(facing, 3);
@RequiredArgsConstructor
private final class PistOrder {
private final Block piston;
private REntityServer server = new REntityServer();
List<Block> toCalc = new LinkedList<>();
calcDirection(origin, null, calcOrigin, facing != direction ? origin.getRelative(facing) : null, facing, direction, blockSet, toCalc, unmovable);
private boolean pulling = false;
private List<Location> movedBlocks = new ArrayList<>();
private List<Location> brokenBlocks = new ArrayList<>();
private List<Location> immovableBlocks = new ArrayList<>();
while (!toCalc.isEmpty()) {
Block current = toCalc.remove(0);
blockSet.add(current);
public void calculate() {
movedBlocks.clear();
brokenBlocks.clear();
immovableBlocks.clear();
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;
Piston pistonData = (Piston) piston.getBlockData();
if (piston.getType() == Material.PISTON && pistonData.isExtended()) {
return;
}
for (BlockFace face : FACES) {
Block block = current.getRelative(face);
if (block.getType().isAir()) continue;
if (!isPiston(block) && (block.getPistonMoveReaction() == PistonMoveReaction.BLOCK || block.getPistonMoveReaction() == PistonMoveReaction.IGNORE || block.getPistonMoveReaction() == PistonMoveReaction.PUSH_ONLY || block.getState() instanceof TileState || block.getPistonMoveReaction() == PistonMoveReaction.BREAK)) continue;
if (block.getType() != oppositeType) {
if (!blockSet.contains(block)) toCalc.add(block);
calcDirection(null, origin, block, null, facing, direction, blockSet, toCalc, unmovable);
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()));
Collections.reverse(movedBlocks);
Collections.reverse(brokenBlocks);
Collections.reverse(immovableBlocks);
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 (block.getType() != oppositeType) {
if (!movedBlocks.contains(block.getLocation())) toCalc.add(block);
calcDirection(null, origin, block, null, facing, direction, toCalc);
}
}
}
movedBlocks.remove(origin.getLocation());
if (facing != direction) movedBlocks.remove(origin.getRelative(facing, 1).getLocation());
}
blockSet.remove(origin);
if (facing != direction) blockSet.remove(origin.getRelative(facing, 1));
REntityServer entityServer = new REntityServer();
for (Location loc : unmovable) {
RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(entityServer, loc.clone().add(0.5, 0, 0.5), Material.RED_STAINED_GLASS);
rFallingBlockEntity.setGlowing(true);
rFallingBlockEntity.setNoGravity(true);
rFallingBlockEntity.setInvisible(true);
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) {
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 void visualize() {
server.getEntities().forEach(REntity::die);
for (int i = 0; i < movedBlocks.size(); i++) {
Location location = movedBlocks.get(i);
int order = i + 1;
RTextDisplay textDisplay = new RTextDisplay(server, location.clone().add(0.5, 0.4, 0.5));
textDisplay.setText("§e" + order);
textDisplay.setBillboard(Display.Billboard.CENTER);
textDisplay.setAlignment(TextDisplay.TextAlignment.CENTER);
textDisplay.setSeeThrough(true);
textDisplay.setBackgroundColor(0);
textDisplay.setShadowed(false);
textDisplay.setBrightness(new Display.Brightness(15, 15));
}
for (int i = 0; i < brokenBlocks.size(); i++) {
Location location = brokenBlocks.get(i);
int order = i + 1;
RTextDisplay textDisplay = new RTextDisplay(server, location.clone().add(0.5, 0.4, 0.5));
textDisplay.setText("§c" + order);
textDisplay.setBillboard(Display.Billboard.CENTER);
textDisplay.setAlignment(TextDisplay.TextAlignment.CENTER);
textDisplay.setSeeThrough(true);
textDisplay.setBackgroundColor(0);
textDisplay.setShadowed(false);
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()) {
wireframe.setBlock(Material.RED_CONCRETE.createBlockData());
} else if (movedBlocks.size() > 12) {
wireframe.setBlock(Material.YELLOW_CONCRETE.createBlockData());
} else {
wireframe.setBlock(Material.LIME_CONCRETE.createBlockData());
}
wireframe.setWidth(1 / 32f);
RTextDisplay textDisplay = new RTextDisplay(server, piston.getLocation().clone().add(0.5, 0.3, 0.5));
StringBuilder text = new StringBuilder();
if (pulling) {
text.append("§ePull\n");
} else {
text.append("§ePush\n");
}
text.append("§f").append(movedBlocks.size()).append("§eB");
textDisplay.setBillboard(Display.Billboard.CENTER);
textDisplay.setAlignment(TextDisplay.TextAlignment.CENTER);
textDisplay.setSeeThrough(true);
textDisplay.setBackgroundColor(0);
textDisplay.setShadowed(false);
textDisplay.setBrightness(new Display.Brightness(15, 15));
}
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), entityServer::close, 20);
return new CalculationResult(blockSet.size(), blockSet.size() > 12, !unmovable.isEmpty(), entityServer);
}
private void calcDirection(Block origin, Block blockOrigin, Block calcOrigin, Block ignore, BlockFace facing, BlockFace direction, Set<Block> blockSet, List<Block> toCalc, Set<Location> unmovable) {
for (int i = 1; i < 24; i++) {
Block block = calcOrigin.getRelative(direction, i);
if (block.equals(ignore)) return;
if (block.getPistonMoveReaction() == PistonMoveReaction.BREAK) return;
if (!isPiston(block) && (block.getPistonMoveReaction() == PistonMoveReaction.BLOCK || block.getPistonMoveReaction() == PistonMoveReaction.IGNORE || block.getState() instanceof TileState)) {
unmovable.add(block.getLocation());
return;
}
if (block.getType().isAir()) return;
if (facing == direction && block.equals(blockOrigin)) {
unmovable.add(block.getLocation());
return;
}
if (facing != direction && (block.equals(origin) || block.getRelative(facing.getOppositeFace()).equals(origin))) return;
if (!blockSet.contains(block)) toCalc.add(block);
private boolean isImmovable(Block block) {
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;
}
}
@@ -142,13 +317,4 @@ public class PistonCalculator implements Listener {
}
return false;
}
@AllArgsConstructor
@Getter
private static class CalculationResult {
private int amount;
private boolean tooMany;
private boolean unmovable;
private REntityServer entityServer;
}
}
@@ -22,9 +22,11 @@ package de.steamwar.bausystem.features.util;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import org.bukkit.entity.Player;
@Linked
@MinVersion(20)
public class PistonCalculatorCommand extends SWCommand {
public PistonCalculatorCommand() {
@@ -189,6 +189,7 @@ public class REntityServer implements Listener {
private void removeEntityFromChunk(REntity entity) {
long id = entityToId(entity);
HashSet<REntity> entitiesInChunk = entities.get(id);
if (entitiesInChunk == null) return;
entitiesInChunk.remove(entity);
if(entitiesInChunk.isEmpty())
entities.remove(id);
@@ -47,7 +47,7 @@ public class RTextDisplay extends RDisplay {
private boolean seeThrough;
private boolean defaultBackground;
private Integer backgroundColor;
private TextDisplay.TextAlignment alignment;
@@ -58,7 +58,7 @@ public class RTextDisplay extends RDisplay {
this.textOpacity = (byte) -1;
this.shadowed = false;
this.seeThrough = false;
this.defaultBackground = false;
this.backgroundColor = null;
this.alignment = TextDisplay.TextAlignment.CENTER;
server.addEntity(this);
}
@@ -116,8 +116,22 @@ public class RTextDisplay extends RDisplay {
sendPacket(updatePacketSink, this::getTextStatus);
}
public void setBackgroundColor(int color) {
this.backgroundColor = color;
sendPacket(updatePacketSink, this::getTextStatus, this::getBackgroundColor);
}
private static final Object backgroundColorWatcher = BountifulWrapper.impl.getDataWatcherObject(Core.getVersion() >= 21 ? 25 : 24, Integer.class);
private void getBackgroundColor(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || backgroundColor != null) {
packetSink.accept(backgroundColorWatcher, backgroundColor);
}
}
public void setDefaultBackground(boolean defaultBackground) {
this.defaultBackground = defaultBackground;
if (defaultBackground) {
this.backgroundColor = null;
}
sendPacket(updatePacketSink, this::getTextStatus);
}
@@ -136,7 +150,7 @@ public class RTextDisplay extends RDisplay {
if (seeThrough) {
status |= 0x02;
}
if (defaultBackground) {
if (backgroundColor == null) {
status |= 0x04;
}
if (alignment == TextDisplay.TextAlignment.CENTER) {