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.BauSystem;
import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.Permission;
import de.steamwar.entity.CWireframe;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer; import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity; import de.steamwar.entity.RTextDisplay;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import lombok.AllArgsConstructor; import de.steamwar.linkage.MinVersion;
import lombok.Getter; import lombok.RequiredArgsConstructor;
import lombok.ToString;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
@@ -37,101 +37,276 @@ import org.bukkit.block.PistonMoveReaction;
import org.bukkit.block.TileState; import org.bukkit.block.TileState;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Piston; 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.EventHandler;
import org.bukkit.event.Listener; 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.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
@Linked @Linked
@MinVersion(20)
public class PistonCalculator implements Listener { public class PistonCalculator implements Listener {
private final Map<Player, Long> DEBOUNCE = new HashMap<>();
@EventHandler @EventHandler
public void onPlayerInteract(PlayerInteractEvent event) { public void onPlayerInteract(PlayerInteractEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return; if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) 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; if (event.getClickedBlock() == null) return;
Block clickedBlock = event.getClickedBlock(); Block clickedBlock = event.getClickedBlock();
Material blockType = clickedBlock.getType(); Material blockType = clickedBlock.getType();
if (!(blockType == Material.PISTON || blockType == Material.STICKY_PISTON)) return; 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()) { Location location = event.getClickedBlock().getLocation();
BauSystem.MESSAGE.sendPrefixless("PISTON_INFO", event.getPlayer(), ChatMessageType.ACTION_BAR, "§a", 0); 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; 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())); @EventHandler
result.entityServer.addPlayer(event.getPlayer()); public void onBlockPistonExtend(BlockPistonExtendEvent event) {
BauSystem.MESSAGE.sendPrefixless("PISTON_INFO", event.getPlayer(), ChatMessageType.ACTION_BAR, result.unmovable ? "§c" : (result.tooMany ? "§e" : "§a"), result.amount); 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 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) { private final Map<Location, PistOrder> pistOrders = new HashMap<>();
Set<Block> blockSet = new HashSet<>();
Set<Location> unmovable = new HashSet<>();
Block calcOrigin = origin; @RequiredArgsConstructor
if (facing != direction) calcOrigin = origin.getRelative(facing, 3); private final class PistOrder {
private final Block piston;
private REntityServer server = new REntityServer();
List<Block> toCalc = new LinkedList<>(); private boolean pulling = false;
calcDirection(origin, null, calcOrigin, facing != direction ? origin.getRelative(facing) : null, facing, direction, blockSet, toCalc, unmovable); private List<Location> movedBlocks = new ArrayList<>();
private List<Location> brokenBlocks = new ArrayList<>();
private List<Location> immovableBlocks = new ArrayList<>();
while (!toCalc.isEmpty()) { public void calculate() {
Block current = toCalc.remove(0); movedBlocks.clear();
blockSet.add(current); brokenBlocks.clear();
immovableBlocks.clear();
Material type = current.getType(); Piston pistonData = (Piston) piston.getBlockData();
if (type != Material.SLIME_BLOCK && type != Material.HONEY_BLOCK) continue; if (piston.getType() == Material.PISTON && pistonData.isExtended()) {
Material oppositeType = type == Material.SLIME_BLOCK ? Material.HONEY_BLOCK : Material.SLIME_BLOCK; return;
}
for (BlockFace face : FACES) { pulling = piston.getType() == Material.STICKY_PISTON && (piston.getRelative(pistonData.getFacing()).getType() == Material.AIR || pistonData.isExtended());
Block block = current.getRelative(face);
if (block.getType().isAir()) continue; calculate(piston, pistonData.getFacing(), (pulling ? pistonData.getFacing().getOppositeFace() : pistonData.getFacing()));
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) { Collections.reverse(movedBlocks);
if (!blockSet.contains(block)) toCalc.add(block); Collections.reverse(brokenBlocks);
calcDirection(null, origin, block, null, facing, direction, blockSet, toCalc, unmovable); 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); private void calcDirection(Block origin, Block blockOrigin, Block calcOrigin, Block ignore, BlockFace facing, BlockFace direction, List<Block> toCalc) {
if (facing != direction) blockSet.remove(origin.getRelative(facing, 1)); for (int i = 1; i < 24; i++) {
Block block = calcOrigin.getRelative(direction, i);
REntityServer entityServer = new REntityServer(); if (block.equals(ignore)) return;
for (Location loc : unmovable) { if (block.getPistonMoveReaction() == PistonMoveReaction.BREAK) {
RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(entityServer, loc.clone().add(0.5, 0, 0.5), Material.RED_STAINED_GLASS); brokenBlocks.add(block.getLocation());
rFallingBlockEntity.setGlowing(true); return;
rFallingBlockEntity.setNoGravity(true); }
rFallingBlockEntity.setInvisible(true); 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) { private boolean isImmovable(Block block) {
for (int i = 1; i < 24; i++) { BlockData blockData = block.getBlockData();
Block block = calcOrigin.getRelative(direction, i); if (blockData instanceof Piston) {
if (block.equals(ignore)) return; return ((Piston) blockData).isExtended();
if (block.getPistonMoveReaction() == PistonMoveReaction.BREAK) return; }
if (!isPiston(block) && (block.getPistonMoveReaction() == PistonMoveReaction.BLOCK || block.getPistonMoveReaction() == PistonMoveReaction.IGNORE || block.getState() instanceof TileState)) { if (block.getState() instanceof TileState) {
unmovable.add(block.getLocation()); return true;
return; }
} PistonMoveReaction reaction = block.getPistonMoveReaction();
if (block.getType().isAir()) return; if (reaction == PistonMoveReaction.IGNORE) return true;
if (facing == direction && block.equals(blockOrigin)) { if (reaction == PistonMoveReaction.BLOCK) return true;
unmovable.add(block.getLocation()); switch (block.getType()) {
return; case OBSIDIAN:
} return true;
if (facing != direction && (block.equals(origin) || block.getRelative(facing.getOppositeFace()).equals(origin))) return; default:
if (!blockSet.contains(block)) toCalc.add(block); return false;
} }
} }
@@ -142,13 +317,4 @@ public class PistonCalculator implements Listener {
} }
return false; 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.bausystem.BauSystem;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@Linked @Linked
@MinVersion(20)
public class PistonCalculatorCommand extends SWCommand { public class PistonCalculatorCommand extends SWCommand {
public PistonCalculatorCommand() { public PistonCalculatorCommand() {
@@ -189,6 +189,7 @@ public class REntityServer implements Listener {
private void removeEntityFromChunk(REntity entity) { private void removeEntityFromChunk(REntity entity) {
long id = entityToId(entity); long id = entityToId(entity);
HashSet<REntity> entitiesInChunk = entities.get(id); HashSet<REntity> entitiesInChunk = entities.get(id);
if (entitiesInChunk == null) return;
entitiesInChunk.remove(entity); entitiesInChunk.remove(entity);
if(entitiesInChunk.isEmpty()) if(entitiesInChunk.isEmpty())
entities.remove(id); entities.remove(id);
@@ -47,7 +47,7 @@ public class RTextDisplay extends RDisplay {
private boolean seeThrough; private boolean seeThrough;
private boolean defaultBackground; private Integer backgroundColor;
private TextDisplay.TextAlignment alignment; private TextDisplay.TextAlignment alignment;
@@ -58,7 +58,7 @@ public class RTextDisplay extends RDisplay {
this.textOpacity = (byte) -1; this.textOpacity = (byte) -1;
this.shadowed = false; this.shadowed = false;
this.seeThrough = false; this.seeThrough = false;
this.defaultBackground = false; this.backgroundColor = null;
this.alignment = TextDisplay.TextAlignment.CENTER; this.alignment = TextDisplay.TextAlignment.CENTER;
server.addEntity(this); server.addEntity(this);
} }
@@ -116,8 +116,22 @@ public class RTextDisplay extends RDisplay {
sendPacket(updatePacketSink, this::getTextStatus); 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) { public void setDefaultBackground(boolean defaultBackground) {
this.defaultBackground = defaultBackground; if (defaultBackground) {
this.backgroundColor = null;
}
sendPacket(updatePacketSink, this::getTextStatus); sendPacket(updatePacketSink, this::getTextStatus);
} }
@@ -136,7 +150,7 @@ public class RTextDisplay extends RDisplay {
if (seeThrough) { if (seeThrough) {
status |= 0x02; status |= 0x02;
} }
if (defaultBackground) { if (backgroundColor == null) {
status |= 0x04; status |= 0x04;
} }
if (alignment == TextDisplay.TextAlignment.CENTER) { if (alignment == TextDisplay.TextAlignment.CENTER) {