diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/PistonCalculator.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/PistonCalculator.java index de9ea34a..d6d49f8b 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/PistonCalculator.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/PistonCalculator.java @@ -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 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 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 blockSet = new HashSet<>(); - Set unmovable = new HashSet<>(); + private final Map 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 toCalc = new LinkedList<>(); - calcDirection(origin, null, calcOrigin, facing != direction ? origin.getRelative(facing) : null, facing, direction, blockSet, toCalc, unmovable); + private boolean pulling = false; + private List movedBlocks = new ArrayList<>(); + private List brokenBlocks = new ArrayList<>(); + private List 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 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 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 blockSet, List toCalc, Set 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; - } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/PistonCalculatorCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/PistonCalculatorCommand.java index 71fefaa4..510c6050 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/PistonCalculatorCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/PistonCalculatorCommand.java @@ -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() { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntityServer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntityServer.java index 2a9a2a13..cfa53943 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntityServer.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntityServer.java @@ -189,6 +189,7 @@ public class REntityServer implements Listener { private void removeEntityFromChunk(REntity entity) { long id = entityToId(entity); HashSet entitiesInChunk = entities.get(id); + if (entitiesInChunk == null) return; entitiesInChunk.remove(entity); if(entitiesInChunk.isEmpty()) entities.remove(id); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java index 38a10bd6..f221e351 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java @@ -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 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) {