diff --git a/src/main/java/net/coreprotect/consumer/Queue.java b/src/main/java/net/coreprotect/consumer/Queue.java index 76cfd56d..eea5071f 100755 --- a/src/main/java/net/coreprotect/consumer/Queue.java +++ b/src/main/java/net/coreprotect/consumer/Queue.java @@ -248,20 +248,6 @@ public class Queue { }, blockLocation.getLocation(), ticks); } - protected static void queueBlockGravityValidate(final String user, final Location location, final Block block, final Material blockType, int ticks) { - Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { - try { - Block placementBlock = BlockUtil.gravityScan(location, blockType, user); - if (!block.equals(placementBlock)) { - queueBlockPlace(user, placementBlock.getState(), blockType, null, blockType, -1, 0, null); - } - } - catch (Exception e) { - e.printStackTrace(); - } - }, location, ticks); - } - protected static void queueContainerBreak(String user, Location location, Material type, ItemStack[] oldInventory) { int currentConsumer = Consumer.currentConsumer; int consumerId = Consumer.newConsumerId(currentConsumer); diff --git a/src/main/java/net/coreprotect/listener/ListenerHandler.java b/src/main/java/net/coreprotect/listener/ListenerHandler.java index 0811bbeb..8ce8a52e 100644 --- a/src/main/java/net/coreprotect/listener/ListenerHandler.java +++ b/src/main/java/net/coreprotect/listener/ListenerHandler.java @@ -29,6 +29,7 @@ import net.coreprotect.listener.entity.EntityDeathListener; import net.coreprotect.listener.entity.EntityExplodeListener; import net.coreprotect.listener.entity.EntityInteractListener; import net.coreprotect.listener.entity.EntityPickupItemListener; +import net.coreprotect.listener.entity.EntitySpawnListener; import net.coreprotect.listener.entity.HangingBreakByEntityListener; import net.coreprotect.listener.entity.HangingBreakListener; import net.coreprotect.listener.entity.HangingPlaceListener; @@ -131,6 +132,7 @@ public final class ListenerHandler { pluginManager.registerEvents(new EntityExplodeListener(), plugin); pluginManager.registerEvents(new EntityInteractListener(), plugin); pluginManager.registerEvents(new EntityPickupItemListener(), plugin); + pluginManager.registerEvents(new EntitySpawnListener(), plugin); pluginManager.registerEvents(new HangingPlaceListener(), plugin); pluginManager.registerEvents(new HangingBreakListener(), plugin); pluginManager.registerEvents(new HangingBreakByEntityListener(), plugin); diff --git a/src/main/java/net/coreprotect/listener/block/BlockBreakListener.java b/src/main/java/net/coreprotect/listener/block/BlockBreakListener.java index 0734770a..aa66960c 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockBreakListener.java +++ b/src/main/java/net/coreprotect/listener/block/BlockBreakListener.java @@ -1,7 +1,5 @@ package net.coreprotect.listener.block; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; import org.bukkit.GameMode; @@ -38,7 +36,9 @@ import net.coreprotect.consumer.Queue; import net.coreprotect.database.Database; import net.coreprotect.model.BlockGroup; import net.coreprotect.paper.PaperAdapter; +import net.coreprotect.thread.CacheHandler; import net.coreprotect.utility.BlockUtils; +import net.coreprotect.utility.WorldUtils; public final class BlockBreakListener extends Queue implements Listener { @@ -86,7 +86,6 @@ public final class BlockBreakListener extends Queue implements Listener { } protected static void processBlockBreak(Player player, String user, Block block, boolean logBreak, int skipScan) { - List placementMap = new ArrayList<>(); Material type = block.getType(); World world = block.getWorld(); int x = block.getX(); @@ -124,24 +123,9 @@ public final class BlockBreakListener extends Queue implements Listener { Location scanLocation = locationMap[scanMin - 1]; Block scanBlock = world.getBlockAt(scanLocation); Material scanType = scanBlock.getType(); - if (scanMin == 5) { - if (scanType.hasGravity() || BukkitAdapter.ADAPTER.isSuspiciousBlock(scanType)) { - if (Config.getConfig(world).BLOCK_MOVEMENT) { - // log the top-most sand/gravel block as being removed - int scanY = y + 2; - boolean topFound = false; - while (!topFound) { - Block topBlock = world.getBlockAt(x, scanY, z); - Material topMaterial = topBlock.getType(); - if (!topMaterial.hasGravity() && !BukkitAdapter.ADAPTER.isSuspiciousBlock(topMaterial)) { - scanLocation = new Location(world, x, (scanY - 1), z); - topFound = true; - } - scanY++; - } - placementMap.add(scanBlock); - } - } + if (scanMin == 5 && (scanType.hasGravity() || BukkitAdapter.ADAPTER.isSuspiciousBlock(scanType)) && Config.getConfig(world).BLOCK_MOVEMENT) { + // FallingBlock listeners read lookupCache to attribute the chain reaction; overwrite the cache up the gravity stack so falls record under whoever broke this block instead of #gravity or the original placer. + seedGravityFallAttribution(user, world, x, z, y + 1, scanType); } if (!BlockGroup.TRACK_ANY.contains(scanType)) { if (scanMin != 5 && scanMin != 6 && !scanDown) { // side block @@ -208,11 +192,6 @@ public final class BlockBreakListener extends Queue implements Listener { log = true; } } - else if (scanMin == 5) { - if (scanType.hasGravity() || BukkitAdapter.ADAPTER.isSuspiciousBlock(scanType)) { - log = true; - } - } } } else { @@ -323,13 +302,6 @@ public final class BlockBreakListener extends Queue implements Listener { scanMin++; } - - for (Block placementBlock : placementMap) { - Material placementType = placementBlock.getType(); - if (placementType.hasGravity()) { - queueBlockPlace(user, block.getState(), placementType, null, null, -1, 0, placementBlock.getBlockData().getAsString()); - } - } } @EventHandler(priority = EventPriority.MONITOR) @@ -337,7 +309,27 @@ public final class BlockBreakListener extends Queue implements Listener { if (!event.isCancelled()) { String user = event.getPlayer().getName(); Block block = event.getBlock(); - processBlockBreak(event.getPlayer(), user, event.getBlock(), Config.getConfig(block.getWorld()).BLOCK_BREAK, BlockUtil.NONE); + processBlockBreak(event.getPlayer(), user, block, Config.getConfig(block.getWorld()).BLOCK_BREAK, BlockUtil.NONE); + } + } + + private static void seedGravityFallAttribution(String user, World world, int x, int z, int startY, Material startType) { + int worldId = WorldUtils.getWorldId(world.getName()); + int timestamp = (int) (System.currentTimeMillis() / 1000L); + int worldMax = world.getMaxHeight(); + Material currentType = startType; + int scanY = startY; + while (scanY < worldMax) { + String coordKey = x + "." + scanY + "." + z + "." + worldId; + CacheHandler.lookupCache.put(coordKey, new Object[] { timestamp, user, currentType }); + scanY++; + if (scanY >= worldMax) { + break; + } + currentType = world.getBlockAt(x, scanY, z).getType(); + if (!currentType.hasGravity() && !BukkitAdapter.ADAPTER.isSuspiciousBlock(currentType)) { + break; + } } } diff --git a/src/main/java/net/coreprotect/listener/block/BlockExplodeListener.java b/src/main/java/net/coreprotect/listener/block/BlockExplodeListener.java index d8afc572..39f1e6dd 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockExplodeListener.java +++ b/src/main/java/net/coreprotect/listener/block/BlockExplodeListener.java @@ -79,29 +79,6 @@ public final class BlockExplodeListener extends Queue implements Listener { } } } - else if (scanType.hasGravity() && Config.getConfig(world).BLOCK_MOVEMENT) { - // log the top-most sand/gravel block as being removed - int scanY = location.getBlockY() + 1; - boolean topFound = false; - while (!topFound) { - Block topBlock = world.getBlockAt(location.getBlockX(), scanY, location.getBlockZ()); - Material topMaterial = topBlock.getType(); - if (!topMaterial.hasGravity()) { - location = new Location(world, location.getBlockX(), (scanY - 1), location.getBlockZ()); - topFound = true; - - // log block attached to top as being removed - if (BlockGroup.TRACK_ANY.contains(topMaterial) || BlockGroup.TRACK_TOP.contains(topMaterial) || BlockGroup.TRACK_TOP_BOTTOM.contains(topMaterial)) { - blockMap.put(topBlock.getLocation(), topBlock); - } - } - scanY++; - } - - Block gravityBlock = location.getBlock(); - blockMap.put(location, gravityBlock); - Queue.queueBlockGravityValidate(user, location, gravityBlock, scanType, 0); - } } scanMin++; } diff --git a/src/main/java/net/coreprotect/listener/block/BlockPlaceListener.java b/src/main/java/net/coreprotect/listener/block/BlockPlaceListener.java index b53783d2..0cc11afe 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockPlaceListener.java +++ b/src/main/java/net/coreprotect/listener/block/BlockPlaceListener.java @@ -27,8 +27,10 @@ import net.coreprotect.config.Config; import net.coreprotect.consumer.Queue; import net.coreprotect.model.BlockGroup; import net.coreprotect.paper.PaperAdapter; +import net.coreprotect.thread.CacheHandler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.MaterialUtils; +import net.coreprotect.utility.WorldUtils; public final class BlockPlaceListener extends Queue implements Listener { @@ -38,12 +40,9 @@ public final class BlockPlaceListener extends Queue implements Listener { if (!event.isCancelled() && Config.getConfig(world).BLOCK_PLACE) { Player player = event.getPlayer(); Block blockPlaced = event.getBlockPlaced(); - Block blockLogged = blockPlaced; String bBlockData = null; BlockState blockReplaced = event.getBlockReplacedState(); Material blockType = blockPlaced.getType(); - Material forceType = null; - int forceData = -1; boolean abort = false; if (blockType == Material.LECTERN && blockReplaced.getType() == Material.LECTERN) { @@ -57,7 +56,7 @@ public final class BlockPlaceListener extends Queue implements Listener { bBlockData = waterlogged.getAsString(); blockReplaced = null; } - Queue.queueBlockPlaceDelayed(player.getName(), blockLogged.getLocation(), blockLogged.getType(), bBlockData, blockReplaced, 0); + Queue.queueBlockPlaceDelayed(player.getName(), blockPlaced.getLocation(), blockPlaced.getType(), bBlockData, blockReplaced, 0); abort = true; } else if (BlockGroup.FIRE.contains(blockType)) { @@ -78,15 +77,7 @@ public final class BlockPlaceListener extends Queue implements Listener { } if (!abort) { - if (Config.getConfig(world).BLOCK_MOVEMENT) { - blockLogged = BlockUtil.gravityScan(blockLogged.getLocation(), blockLogged.getType(), player.getName()); - if (!blockLogged.equals(blockPlaced)) { - forceType = blockType; - blockReplaced = blockLogged.getState(); - } - } - - BlockData blockData = blockLogged.getBlockData(); + BlockData blockData = blockPlaced.getBlockData(); Waterlogged waterlogged = BlockUtils.checkWaterlogged(blockData, blockReplaced); if (waterlogged != null) { bBlockData = waterlogged.getAsString(); @@ -94,7 +85,7 @@ public final class BlockPlaceListener extends Queue implements Listener { } // fix for placed bisected blocks randomly returning the top or bottom of the block in this event - BlockState blockState = blockLogged.getState(); + BlockState blockState = blockPlaced.getState(); if (blockState.getBlockData() instanceof Bisected && !(blockState.getBlockData() instanceof Stairs || blockState.getBlockData() instanceof TrapDoor)) { if (((Bisected) blockState.getBlockData()).getHalf().equals(Half.TOP)) { if (blockPlaced.getY() > BukkitAdapter.ADAPTER.getMinHeight(world)) { @@ -103,7 +94,15 @@ public final class BlockPlaceListener extends Queue implements Listener { } } - Queue.queueBlockPlace(player.getName(), blockState, blockPlaced.getType(), blockReplaced, forceType, forceData, 0, bBlockData); + if (blockType.hasGravity()) { + // FallingBlock listeners read lookupCache before the async consumer can populate it; record the placer synchronously so the fall and landing get attributed to the player instead of #gravity. + int worldId = WorldUtils.getWorldId(world.getName()); + int timestamp = (int) (System.currentTimeMillis() / 1000L); + String coordKey = blockPlaced.getX() + "." + blockPlaced.getY() + "." + blockPlaced.getZ() + "." + worldId; + CacheHandler.lookupCache.put(coordKey, new Object[] { timestamp, player.getName(), blockType }); + } + + Queue.queueBlockPlace(player.getName(), blockState, blockPlaced.getType(), blockReplaced, null, -1, 0, bBlockData); if (BukkitAdapter.ADAPTER.isSign(blockType)) { if (Config.getConfig(world).SIGN_TEXT) { diff --git a/src/main/java/net/coreprotect/listener/entity/EntityChangeBlockListener.java b/src/main/java/net/coreprotect/listener/entity/EntityChangeBlockListener.java index a1ee060c..730aad6a 100644 --- a/src/main/java/net/coreprotect/listener/entity/EntityChangeBlockListener.java +++ b/src/main/java/net/coreprotect/listener/entity/EntityChangeBlockListener.java @@ -6,6 +6,7 @@ import org.bukkit.block.Block; import org.bukkit.entity.EnderDragon; import org.bukkit.entity.Enderman; import org.bukkit.entity.Entity; +import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Fox; import org.bukkit.entity.Ravager; import org.bukkit.entity.Silverfish; @@ -19,59 +20,115 @@ import org.bukkit.event.entity.EntityChangeBlockEvent; import net.coreprotect.config.Config; import net.coreprotect.consumer.Queue; +import net.coreprotect.thread.CacheHandler; +import net.coreprotect.utility.WorldUtils; public final class EntityChangeBlockListener extends Queue implements Listener { @EventHandler(priority = EventPriority.MONITOR) protected void onEntityChangeBlock(EntityChangeBlockEvent event) { World world = event.getBlock().getWorld(); - if (!event.isCancelled() && Config.getConfig(world).ENTITY_CHANGE) { - Entity entity = event.getEntity(); // Can be sand/gravel - Block block = event.getBlock(); - Material newtype = event.getTo(); - Material type = event.getBlock().getType(); - String e = ""; - if (entity instanceof Enderman) { - e = "#enderman"; + if (event.isCancelled() || !Config.getConfig(world).ENTITY_CHANGE) { + return; + } + + Entity entity = event.getEntity(); + Block block = event.getBlock(); + Material newtype = event.getTo(); + Material type = event.getBlock().getType(); + + if (entity instanceof FallingBlock) { + handleFallingBlock(event, (FallingBlock) entity, block, newtype, type, world); + return; + } + + String e = ""; + if (entity instanceof Enderman) { + e = "#enderman"; + } + else if (entity instanceof EnderDragon) { + e = "#enderdragon"; + } + else if (entity instanceof Fox) { + e = "#fox"; + } + else if (entity instanceof Wither) { + e = "#wither"; + } + else if (entity instanceof Turtle) { + e = "#turtle"; + } + else if (entity instanceof Ravager) { + e = "#ravager"; + } + else if (entity instanceof Zombie) { + e = "#zombie"; + } + else if (entity instanceof Silverfish) { + if (newtype.equals(Material.AIR) || newtype.equals(Material.CAVE_AIR)) { + e = "#silverfish"; } - else if (entity instanceof EnderDragon) { - e = "#enderdragon"; + } + else if (entity.getType().name().equals("WIND_CHARGE")) { + e = "#windcharge"; + } + else if (entity.getType().name().equals("BREEZE_WIND_CHARGE")) { + e = "#breezewindcharge"; + } + if (e.length() > 0) { + if (newtype.equals(Material.AIR) || newtype.equals(Material.CAVE_AIR)) { + Queue.queueBlockBreak(e, block.getState(), type, block.getBlockData().getAsString(), 0); } - else if (entity instanceof Fox) { - e = "#fox"; - } - else if (entity instanceof Wither) { - e = "#wither"; - } - else if (entity instanceof Turtle) { - e = "#turtle"; - } - else if (entity instanceof Ravager) { - e = "#ravager"; - } - else if (entity instanceof Zombie) { - e = "#zombie"; - } - else if (entity instanceof Silverfish) { - if (newtype.equals(Material.AIR) || newtype.equals(Material.CAVE_AIR)) { - e = "#silverfish"; - } - } - else if (entity.getType().name().equals("WIND_CHARGE")) { - e = "#windcharge"; - } - else if (entity.getType().name().equals("BREEZE_WIND_CHARGE")) { - e = "#breezewindcharge"; - } - if (e.length() > 0) { - if (newtype.equals(Material.AIR) || newtype.equals(Material.CAVE_AIR)) { - Queue.queueBlockBreak(e, block.getState(), type, block.getBlockData().getAsString(), 0); - } - else { - queueBlockPlace(e, block.getState(), type, block.getState(), newtype, -1, 0, event.getBlockData().getAsString()); - } + else { + queueBlockPlace(e, block.getState(), type, block.getState(), newtype, -1, 0, event.getBlockData().getAsString()); } } } + private void handleFallingBlock(EntityChangeBlockEvent event, FallingBlock fallingBlock, Block block, Material newtype, Material type, World world) { + int worldId = WorldUtils.getWorldId(world.getName()); + + if (newtype.equals(Material.AIR) || newtype.equals(Material.CAVE_AIR)) { + if (!Config.getConfig(world).BLOCK_BREAK) { + return; + } + + String coordKey = block.getX() + "." + block.getY() + "." + block.getZ() + "." + worldId; + String user = lookupCachedUser(coordKey, type); + if (user == null) { + user = "#gravity"; + } + + Queue.queueBlockBreak(user, block.getState(), type, block.getBlockData().getAsString(), 0); + return; + } + + if (!Config.getConfig(world).BLOCK_PLACE) { + return; + } + + Object[] originData = CacheHandler.fallingBlockSpawnCache.remove(fallingBlock.getUniqueId().toString()); + String user = "#gravity"; + if (originData != null) { + String originKey = (String) originData[1]; + String lookupUser = lookupCachedUser(originKey, fallingBlock.getBlockData().getMaterial()); + if (lookupUser != null) { + user = lookupUser; + } + } + + queueBlockPlace(user, block.getState(), type, block.getState(), newtype, -1, 0, event.getBlockData().getAsString()); + } + + private static String lookupCachedUser(String coordKey, Material expectedType) { + Object[] data = CacheHandler.lookupCache.get(coordKey); + if (data == null) { + return null; + } + if (expectedType != null && data[2] instanceof Material && !data[2].equals(expectedType)) { + return null; + } + return (String) data[1]; + } + } diff --git a/src/main/java/net/coreprotect/listener/entity/EntitySpawnListener.java b/src/main/java/net/coreprotect/listener/entity/EntitySpawnListener.java new file mode 100644 index 00000000..f04989dc --- /dev/null +++ b/src/main/java/net/coreprotect/listener/entity/EntitySpawnListener.java @@ -0,0 +1,33 @@ +package net.coreprotect.listener.entity; + +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntitySpawnEvent; + +import net.coreprotect.thread.CacheHandler; +import net.coreprotect.utility.WorldUtils; + +public final class EntitySpawnListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + protected void onEntitySpawn(EntitySpawnEvent event) { + if (event.getEntityType() != EntityType.FALLING_BLOCK) { + return; + } + + Location location = event.getLocation(); + if (location.getWorld() == null) { + return; + } + + int worldId = WorldUtils.getWorldId(location.getWorld().getName()); + String originKey = location.getBlockX() + "." + location.getBlockY() + "." + location.getBlockZ() + "." + worldId; + int timestamp = (int) (System.currentTimeMillis() / 1000L); + + CacheHandler.fallingBlockSpawnCache.put(event.getEntity().getUniqueId().toString(), new Object[] { timestamp, originKey }); + } + +} diff --git a/src/main/java/net/coreprotect/thread/CacheHandler.java b/src/main/java/net/coreprotect/thread/CacheHandler.java index 5c3aa2cf..a9b7de54 100755 --- a/src/main/java/net/coreprotect/thread/CacheHandler.java +++ b/src/main/java/net/coreprotect/thread/CacheHandler.java @@ -24,6 +24,7 @@ public class CacheHandler implements Runnable { public static ConcurrentHashMap bonemealDuplicateCache = new ConcurrentHashMap<>(16, 0.75f, 2); public static ConcurrentHashMap entityKillDuplicateCache = new ConcurrentHashMap<>(16, 0.75f, 2); public static ConcurrentHashMap redstoneCache = new ConcurrentHashMap<>(16, 0.75f, 2); + public static ConcurrentHashMap fallingBlockSpawnCache = new ConcurrentHashMap<>(16, 0.75f, 2); public static String locationKey(Location location) { if (location == null || location.getWorld() == null) { @@ -63,7 +64,7 @@ public class CacheHandler implements Runnable { public void run() { while (ConfigHandler.serverRunning) { try { - for (int id = 0; id < 13; id++) { + for (int id = 0; id < 14; id++) { Thread.sleep(1000); int scanTime = 30; Map cache = CacheHandler.lookupCache; @@ -116,6 +117,10 @@ public class CacheHandler implements Runnable { cache = CacheHandler.entityKillDuplicateCache; scanTime = 900; // 15 minutes break; + case 13: + cache = CacheHandler.fallingBlockSpawnCache; + scanTime = 30; // 30 seconds + break; } int timestamp = (int) (System.currentTimeMillis() / 1000L) - scanTime;