forked from github-mirror/CoreProtect
Extended de-dupe logic to include droppers/dispensers/fluids/kills
This commit is contained in:
1 parent
c706cf5b18
commit
bd449d1b89
10 files changed
+270
-53
No files matched your search
@@ -46,6 +46,7 @@ public class Config extends Language {
|
|||||||
public boolean DATABASE_LOCK;
|
public boolean DATABASE_LOCK;
|
||||||
public boolean LOG_CANCELLED_CHAT;
|
public boolean LOG_CANCELLED_CHAT;
|
||||||
public boolean HOPPER_FILTER_META;
|
public boolean HOPPER_FILTER_META;
|
||||||
|
public boolean DUPLICATE_SUPPRESSION;
|
||||||
public boolean EXCLUDE_TNT;
|
public boolean EXCLUDE_TNT;
|
||||||
public boolean NETWORK_DEBUG;
|
public boolean NETWORK_DEBUG;
|
||||||
public boolean MYSQL;
|
public boolean MYSQL;
|
||||||
@@ -193,6 +194,7 @@ public class Config extends Language {
|
|||||||
this.DATABASE_LOCK = this.getBoolean("database-lock", true);
|
this.DATABASE_LOCK = this.getBoolean("database-lock", true);
|
||||||
this.LOG_CANCELLED_CHAT = this.getBoolean("log-cancelled-chat", true);
|
this.LOG_CANCELLED_CHAT = this.getBoolean("log-cancelled-chat", true);
|
||||||
this.HOPPER_FILTER_META = this.getBoolean("hopper-filter-meta", false);
|
this.HOPPER_FILTER_META = this.getBoolean("hopper-filter-meta", false);
|
||||||
|
this.DUPLICATE_SUPPRESSION = this.getBoolean("duplicate-suppression", true);
|
||||||
this.EXCLUDE_TNT = this.getBoolean("exclude-tnt", false);
|
this.EXCLUDE_TNT = this.getBoolean("exclude-tnt", false);
|
||||||
this.NETWORK_DEBUG = this.getBoolean("network-debug", false);
|
this.NETWORK_DEBUG = this.getBoolean("network-debug", false);
|
||||||
this.UNKNOWN_LOGGING = this.getBoolean("unknown-logging", false);
|
this.UNKNOWN_LOGGING = this.getBoolean("unknown-logging", false);
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package net.coreprotect.database.logger;
|
|||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@@ -22,6 +24,7 @@ import net.coreprotect.consumer.Queue;
|
|||||||
import net.coreprotect.database.statement.ContainerStatement;
|
import net.coreprotect.database.statement.ContainerStatement;
|
||||||
import net.coreprotect.database.statement.UserStatement;
|
import net.coreprotect.database.statement.UserStatement;
|
||||||
import net.coreprotect.event.CoreProtectPreLogEvent;
|
import net.coreprotect.event.CoreProtectPreLogEvent;
|
||||||
|
import net.coreprotect.thread.CacheHandler;
|
||||||
import net.coreprotect.utility.BlockUtils;
|
import net.coreprotect.utility.BlockUtils;
|
||||||
import net.coreprotect.utility.ItemUtils;
|
import net.coreprotect.utility.ItemUtils;
|
||||||
import net.coreprotect.utility.MaterialUtils;
|
import net.coreprotect.utility.MaterialUtils;
|
||||||
@@ -30,6 +33,10 @@ import net.coreprotect.utility.serialize.ItemMetaHandler;
|
|||||||
|
|
||||||
public class ContainerLogger extends Queue {
|
public class ContainerLogger extends Queue {
|
||||||
|
|
||||||
|
private static final int CONTAINER_DUPLICATE_THRESHOLD_DROPPER = 512;
|
||||||
|
private static final int CONTAINER_DUPLICATE_THRESHOLD_DISPENSER = 256;
|
||||||
|
private static final int CONTAINER_DUPLICATE_WINDOW_SECONDS = 1200;
|
||||||
|
|
||||||
private ContainerLogger() {
|
private ContainerLogger() {
|
||||||
throw new IllegalStateException("Database class");
|
throw new IllegalStateException("Database class");
|
||||||
}
|
}
|
||||||
@@ -65,9 +72,10 @@ public class ContainerLogger extends Queue {
|
|||||||
if (oldInventory == null || newInventory == null) {
|
if (oldInventory == null || newInventory == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
boolean duplicateSuppression = Config.getConfig(location.getWorld()).DUPLICATE_SUPPRESSION;
|
||||||
|
|
||||||
// Check if this is a dispenser with no actual changes
|
// Check if this is a dispenser with no actual changes
|
||||||
if ("#dispenser".equals(player) && ItemUtils.compareContainers(oldInventory, newInventory)) {
|
if (duplicateSuppression && "#dispenser".equals(player) && ItemUtils.compareContainers(oldInventory, newInventory)) {
|
||||||
// No changes detected, mark this dispenser in the dispenserNoChange map
|
// No changes detected, mark this dispenser in the dispenserNoChange map
|
||||||
// Extract the location key from the loggingContainerId
|
// Extract the location key from the loggingContainerId
|
||||||
// Format: #dispenser.x.y.z
|
// Format: #dispenser.x.y.z
|
||||||
@@ -95,7 +103,7 @@ public class ContainerLogger extends Queue {
|
|||||||
|
|
||||||
// If we reach here, the dispenser event resulted in changes
|
// If we reach here, the dispenser event resulted in changes
|
||||||
// Remove any pending event for this dispenser
|
// Remove any pending event for this dispenser
|
||||||
if ("#dispenser".equals(player)) {
|
if (duplicateSuppression && "#dispenser".equals(player)) {
|
||||||
String[] parts = loggingContainerId.split("\\.");
|
String[] parts = loggingContainerId.split("\\.");
|
||||||
if (parts.length >= 4) {
|
if (parts.length >= 4) {
|
||||||
int x = Integer.parseInt(parts[1]);
|
int x = Integer.parseInt(parts[1]);
|
||||||
@@ -166,6 +174,17 @@ public class ContainerLogger extends Queue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (duplicateSuppression && shouldSuppressContainerDuplicate(player, location, oldInventory, newInventory)) {
|
||||||
|
oldList.remove(0);
|
||||||
|
ConfigHandler.oldContainer.put(loggingContainerId, oldList);
|
||||||
|
if ("#hopper".equals(player)) {
|
||||||
|
String hopperPush = "#hopper-push." + location.getBlockX() + "." + location.getBlockY() + "." + location.getBlockZ();
|
||||||
|
ConfigHandler.hopperSuccess.remove(hopperPush);
|
||||||
|
ConfigHandler.hopperAbort.remove(hopperPush);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (ItemStack oldi : oldInventory) {
|
for (ItemStack oldi : oldInventory) {
|
||||||
for (ItemStack newi : newInventory) {
|
for (ItemStack newi : newInventory) {
|
||||||
if (oldi != null && newi != null) {
|
if (oldi != null && newi != null) {
|
||||||
@@ -265,4 +284,65 @@ public class ContainerLogger extends Queue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldSuppressContainerDuplicate(String user, Location location, ItemStack[] oldInventory, ItemStack[] newInventory) {
|
||||||
|
if (!"#dropper".equals(user) && !"#dispenser".equals(user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String oldSignature = getContainerStateSignature(oldInventory);
|
||||||
|
String newSignature = getContainerStateSignature(newInventory);
|
||||||
|
String transitionSignature = oldSignature + ">" + newSignature;
|
||||||
|
if ("#dispenser".equals(user)) {
|
||||||
|
if (oldSignature.compareTo(newSignature) <= 0) {
|
||||||
|
transitionSignature = oldSignature + "<>" + newSignature;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
transitionSignature = newSignature + "<>" + oldSignature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int threshold = getContainerDuplicateThreshold(user);
|
||||||
|
String signature = location.getWorld().getUID().toString() + "." + location.getBlockX() + "." + location.getBlockY() + "." + location.getBlockZ() + "." + user + "." + transitionSignature;
|
||||||
|
return CacheHandler.shouldSuppressRepeat(CacheHandler.containerDuplicateCache, signature, threshold, CONTAINER_DUPLICATE_WINDOW_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getContainerDuplicateThreshold(String user) {
|
||||||
|
if ("#dispenser".equals(user)) {
|
||||||
|
return CONTAINER_DUPLICATE_THRESHOLD_DISPENSER;
|
||||||
|
}
|
||||||
|
return CONTAINER_DUPLICATE_THRESHOLD_DROPPER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getContainerStateSignature(ItemStack[] inventory) {
|
||||||
|
if (inventory == null || inventory.length == 0) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Integer> itemCounts = new HashMap<>();
|
||||||
|
for (ItemStack itemStack : inventory) {
|
||||||
|
if (itemStack == null || itemStack.getAmount() <= 0 || BlockUtils.isAir(itemStack.getType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack normalized = itemStack.clone();
|
||||||
|
normalized.setAmount(1);
|
||||||
|
String key = normalized.toString();
|
||||||
|
itemCounts.merge(key, itemStack.getAmount(), Integer::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemCounts.isEmpty()) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (Map.Entry<String, Integer> entry : new TreeMap<>(itemCounts).entrySet()) {
|
||||||
|
if (builder.length() > 0) {
|
||||||
|
builder.append('|');
|
||||||
|
}
|
||||||
|
builder.append(entry.getKey()).append('*').append(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.toHexString(builder.toString().hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -28,11 +28,15 @@ import net.coreprotect.thread.CacheHandler;
|
|||||||
|
|
||||||
public final class BlockDispenseListener extends Queue implements Listener {
|
public final class BlockDispenseListener extends Queue implements Listener {
|
||||||
|
|
||||||
|
private static final int DISPENSER_LIQUID_DUPLICATE_THRESHOLD = 256;
|
||||||
|
private static final int DISPENSER_LIQUID_DUPLICATE_WINDOW_SECONDS = 1200;
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
protected void onBlockDispense(BlockDispenseEvent event) {
|
protected void onBlockDispense(BlockDispenseEvent event) {
|
||||||
Block block = event.getBlock();
|
Block block = event.getBlock();
|
||||||
World world = block.getWorld();
|
World world = block.getWorld();
|
||||||
if (!event.isCancelled() && Config.getConfig(world).BLOCK_PLACE) {
|
Config config = Config.getConfig(world);
|
||||||
|
if (!event.isCancelled() && config.BLOCK_PLACE) {
|
||||||
BlockData blockData = block.getBlockData();
|
BlockData blockData = block.getBlockData();
|
||||||
ItemStack item = event.getItem();
|
ItemStack item = event.getItem();
|
||||||
if (item != null && blockData instanceof Dispenser) {
|
if (item != null && blockData instanceof Dispenser) {
|
||||||
@@ -67,11 +71,9 @@ public final class BlockDispenseListener extends Queue implements Listener {
|
|||||||
|
|
||||||
if (material.equals(Material.WATER_BUCKET)) {
|
if (material.equals(Material.WATER_BUCKET)) {
|
||||||
type = Material.WATER;
|
type = Material.WATER;
|
||||||
user = "#water";
|
|
||||||
}
|
}
|
||||||
else if (material.equals(Material.LAVA_BUCKET)) {
|
else if (material.equals(Material.LAVA_BUCKET)) {
|
||||||
type = Material.LAVA;
|
type = Material.LAVA;
|
||||||
user = "#lava";
|
|
||||||
}
|
}
|
||||||
else if (material.equals(Material.FLINT_AND_STEEL)) {
|
else if (material.equals(Material.FLINT_AND_STEEL)) {
|
||||||
type = Material.FIRE;
|
type = Material.FIRE;
|
||||||
@@ -85,10 +87,10 @@ public final class BlockDispenseListener extends Queue implements Listener {
|
|||||||
CacheHandler.redstoneCache.put(newBlock.getLocation(), new Object[] { System.currentTimeMillis(), user });
|
CacheHandler.redstoneCache.put(newBlock.getLocation(), new Object[] { System.currentTimeMillis(), user });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == Material.FIRE && (!Config.getConfig(world).BLOCK_IGNITE || !(newBlockData instanceof Lightable))) {
|
if (type == Material.FIRE && (!config.BLOCK_IGNITE || !(newBlockData instanceof Lightable))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (type != Material.FIRE && (!Config.getConfig(world).BUCKETS || (!Config.getConfig(world).WATER_FLOW && type.equals(Material.WATER)) || (!Config.getConfig(world).LAVA_FLOW && type.equals(Material.LAVA)))) {
|
else if (type != Material.FIRE && (!config.BUCKETS || (!config.WATER_FLOW && type.equals(Material.WATER)) || (!config.LAVA_FLOW && type.equals(Material.LAVA)))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +106,9 @@ public final class BlockDispenseListener extends Queue implements Listener {
|
|||||||
}
|
}
|
||||||
else if (dispenseRelative) {
|
else if (dispenseRelative) {
|
||||||
BlockState blockState = newBlock.getState();
|
BlockState blockState = newBlock.getState();
|
||||||
|
if (config.DUPLICATE_SUPPRESSION && shouldSuppressDispenseLiquidDuplicate(user, newBlock, type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!type.equals(Material.AIR)) {
|
if (!type.equals(Material.AIR)) {
|
||||||
queueBlockPlaceValidate(user, blockState, newBlock, blockState, type, 1, 1, null, 0);
|
queueBlockPlaceValidate(user, blockState, newBlock, blockState, type, 1, 1, null, 0);
|
||||||
}
|
}
|
||||||
@@ -116,4 +121,32 @@ public final class BlockDispenseListener extends Queue implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldSuppressDispenseLiquidDuplicate(String user, Block targetBlock, Material newType) {
|
||||||
|
Material oldType = targetBlock.getType();
|
||||||
|
boolean placeLiquid = ("#water".equals(user) || "#lava".equals(user) || "#dispenser".equals(user)) && (newType == Material.WATER || newType == Material.LAVA);
|
||||||
|
boolean removeLiquid = "#dispenser".equals(user) && newType == Material.AIR && (oldType == Material.WATER || oldType == Material.LAVA);
|
||||||
|
if (!placeLiquid && !removeLiquid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLiquidToggleType(oldType) || !isLiquidToggleType(newType) || oldType == newType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String left = oldType.name();
|
||||||
|
String right = newType.name();
|
||||||
|
if (left.compareTo(right) > 0) {
|
||||||
|
String swap = left;
|
||||||
|
left = right;
|
||||||
|
right = swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
String signature = targetBlock.getWorld().getUID().toString() + "." + targetBlock.getX() + "." + targetBlock.getY() + "." + targetBlock.getZ() + ".#dispense-liquid." + left + "<>" + right;
|
||||||
|
return CacheHandler.shouldSuppressRepeat(CacheHandler.flowDuplicateCache, signature, DISPENSER_LIQUID_DUPLICATE_THRESHOLD, DISPENSER_LIQUID_DUPLICATE_WINDOW_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLiquidToggleType(Material type) {
|
||||||
|
return type == Material.AIR || type == Material.WATER || type == Material.LAVA;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package net.coreprotect.listener.block;
|
package net.coreprotect.listener.block;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@@ -20,6 +22,9 @@ import net.coreprotect.thread.CacheHandler;
|
|||||||
|
|
||||||
public final class BlockFertilizeListener extends Queue implements Listener {
|
public final class BlockFertilizeListener extends Queue implements Listener {
|
||||||
|
|
||||||
|
private static final int BONEMEAL_DUPLICATE_THRESHOLD = 256;
|
||||||
|
private static final int BONEMEAL_DUPLICATE_WINDOW_SECONDS = 900;
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
protected void onBlockFertilize(BlockFertilizeEvent event) {
|
protected void onBlockFertilize(BlockFertilizeEvent event) {
|
||||||
if (event.isCancelled()) {
|
if (event.isCancelled()) {
|
||||||
@@ -66,6 +71,10 @@ public final class BlockFertilizeListener extends Queue implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.DUPLICATE_SUPPRESSION && "#dispenser".equals(user) && shouldSuppressBonemealDuplicate(location, blocks)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (BlockState newBlock : blocks) {
|
for (BlockState newBlock : blocks) {
|
||||||
Queue.queueBlockPlace(user, newBlock, newBlock.getType(), newBlock.getBlock().getState(), newBlock.getType(), -1, 0, newBlock.getBlockData().getAsString());
|
Queue.queueBlockPlace(user, newBlock, newBlock.getType(), newBlock.getBlock().getState(), newBlock.getType(), -1, 0, newBlock.getBlockData().getAsString());
|
||||||
}
|
}
|
||||||
@@ -75,4 +84,19 @@ public final class BlockFertilizeListener extends Queue implements Listener {
|
|||||||
return blockType == Material.CRIMSON_FUNGUS || blockType == Material.WARPED_FUNGUS || blockType.name().toLowerCase(Locale.ROOT).contains("mushroom");
|
return blockType == Material.CRIMSON_FUNGUS || blockType == Material.WARPED_FUNGUS || blockType.name().toLowerCase(Locale.ROOT).contains("mushroom");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldSuppressBonemealDuplicate(Location location, List<BlockState> blocks) {
|
||||||
|
if (blocks == null || blocks.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> states = new ArrayList<>();
|
||||||
|
for (BlockState newBlock : blocks) {
|
||||||
|
Location newLocation = newBlock.getLocation();
|
||||||
|
states.add(newLocation.getBlockX() + "." + newLocation.getBlockY() + "." + newLocation.getBlockZ() + "." + newBlock.getType().name() + "." + newBlock.getBlockData().getAsString());
|
||||||
|
}
|
||||||
|
Collections.sort(states);
|
||||||
|
String signature = location.getWorld().getUID().toString() + "." + location.getBlockX() + "." + location.getBlockY() + "." + location.getBlockZ() + "." + Integer.toHexString(String.join("|", states).hashCode());
|
||||||
|
return CacheHandler.shouldSuppressRepeat(CacheHandler.bonemealDuplicateCache, signature, BONEMEAL_DUPLICATE_THRESHOLD, BONEMEAL_DUPLICATE_WINDOW_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,9 @@ import net.coreprotect.utility.WorldUtils;
|
|||||||
|
|
||||||
public final class BlockFromToListener extends Queue implements Listener {
|
public final class BlockFromToListener extends Queue implements Listener {
|
||||||
|
|
||||||
|
private static final int FLOW_DUPLICATE_THRESHOLD = 512;
|
||||||
|
private static final int FLOW_DUPLICATE_WINDOW_SECONDS = 900;
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
protected void onBlockFromTo(BlockFromToEvent event) {
|
protected void onBlockFromTo(BlockFromToEvent event) {
|
||||||
Block block = event.getBlock();
|
Block block = event.getBlock();
|
||||||
@@ -36,7 +39,8 @@ public final class BlockFromToListener extends Queue implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
World world = event.getBlock().getWorld();
|
World world = event.getBlock().getWorld();
|
||||||
if ((Config.getConfig(world).WATER_FLOW && type.equals(Material.WATER)) || (Config.getConfig(world).LAVA_FLOW && type.equals(Material.LAVA))) {
|
Config config = Config.getConfig(world);
|
||||||
|
if ((config.WATER_FLOW && type.equals(Material.WATER)) || (config.LAVA_FLOW && type.equals(Material.LAVA))) {
|
||||||
Block toBlock = event.getToBlock();
|
Block toBlock = event.getToBlock();
|
||||||
BlockState toBlockState = toBlock.getState();
|
BlockState toBlockState = toBlock.getState();
|
||||||
|
|
||||||
@@ -67,14 +71,14 @@ public final class BlockFromToListener extends Queue implements Listener {
|
|||||||
int y = toBlock.getY();
|
int y = toBlock.getY();
|
||||||
int z = toBlock.getZ();
|
int z = toBlock.getZ();
|
||||||
int wid = WorldUtils.getWorldId(block.getWorld().getName());
|
int wid = WorldUtils.getWorldId(block.getWorld().getName());
|
||||||
if (Config.getConfig(world).LIQUID_TRACKING) {
|
if (config.LIQUID_TRACKING) {
|
||||||
String p = Lookup.whoPlacedCache(block);
|
String p = Lookup.whoPlacedCache(block);
|
||||||
if (p.length() > 0) {
|
if (p.length() > 0) {
|
||||||
f = p;
|
f = p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f.startsWith("#")) {
|
if (config.DUPLICATE_SUPPRESSION && f.startsWith("#")) {
|
||||||
String cacheId = toBlock.getX() + "." + toBlock.getY() + "." + toBlock.getZ() + "." + WorldUtils.getWorldId(toBlock.getWorld().getName());
|
String cacheId = toBlock.getX() + "." + toBlock.getY() + "." + toBlock.getZ() + "." + WorldUtils.getWorldId(toBlock.getWorld().getName());
|
||||||
int timestamp = (int) (System.currentTimeMillis() / 1000L);
|
int timestamp = (int) (System.currentTimeMillis() / 1000L);
|
||||||
Object[] cacheData = CacheHandler.spreadCache.get(cacheId);
|
Object[] cacheData = CacheHandler.spreadCache.get(cacheId);
|
||||||
@@ -84,6 +88,10 @@ public final class BlockFromToListener extends Queue implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.DUPLICATE_SUPPRESSION && shouldSuppressFlowDuplicate(f, block, toBlock, type, blockData, toBlockState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CacheHandler.lookupCache.put("" + x + "." + y + "." + z + "." + wid + "", new Object[] { unixtimestamp, f, type });
|
CacheHandler.lookupCache.put("" + x + "." + y + "." + z + "." + wid + "", new Object[] { unixtimestamp, f, type });
|
||||||
Queue.queueBlockPlace(f, toBlock.getState(), block.getType(), toBlockState, type, -1, 0, blockData.getAsString());
|
Queue.queueBlockPlace(f, toBlock.getState(), block.getType(), toBlockState, type, -1, 0, blockData.getAsString());
|
||||||
}
|
}
|
||||||
@@ -123,4 +131,14 @@ public final class BlockFromToListener extends Queue implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldSuppressFlowDuplicate(String user, Block sourceBlock, Block targetBlock, Material liquidType, BlockData blockData, BlockState replacedState) {
|
||||||
|
String replacedSignature = "-";
|
||||||
|
if (replacedState != null) {
|
||||||
|
replacedSignature = replacedState.getType().name() + ":" + replacedState.getBlockData().getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String signature = sourceBlock.getWorld().getUID().toString() + "." + sourceBlock.getX() + "." + sourceBlock.getY() + "." + sourceBlock.getZ() + ">" + targetBlock.getX() + "." + targetBlock.getY() + "." + targetBlock.getZ() + "." + user + "." + liquidType.name() + "." + blockData.getAsString() + "." + replacedSignature;
|
||||||
|
return CacheHandler.shouldSuppressRepeat(CacheHandler.flowDuplicateCache, signature, FLOW_DUPLICATE_THRESHOLD, FLOW_DUPLICATE_WINDOW_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,8 @@ public final class BlockPistonListener extends Queue implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
World world = event.getBlock().getWorld();
|
World world = event.getBlock().getWorld();
|
||||||
if (Config.getConfig(world).PISTONS && !event.isCancelled()) {
|
Config config = Config.getConfig(world);
|
||||||
|
if (config.PISTONS && !event.isCancelled()) {
|
||||||
List<Block> nblocks = new ArrayList<>();
|
List<Block> nblocks = new ArrayList<>();
|
||||||
List<Block> blocks = new ArrayList<>();
|
List<Block> blocks = new ArrayList<>();
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ public final class BlockPistonListener extends Queue implements Listener {
|
|||||||
int wid = WorldUtils.getWorldId(bm.getWorld().getName());
|
int wid = WorldUtils.getWorldId(bm.getWorld().getName());
|
||||||
|
|
||||||
int unixtimestamp = (int) (System.currentTimeMillis() / 1000L);
|
int unixtimestamp = (int) (System.currentTimeMillis() / 1000L);
|
||||||
|
boolean duplicateSuppression = config.DUPLICATE_SUPPRESSION;
|
||||||
int log = 0;
|
int log = 0;
|
||||||
int l = 0;
|
int l = 0;
|
||||||
while (l <= nblocks.size()) {
|
while (l <= nblocks.size()) {
|
||||||
@@ -65,10 +67,12 @@ public final class BlockPistonListener extends Queue implements Listener {
|
|||||||
int z = n.getZ();
|
int z = n.getZ();
|
||||||
Material t = n.getType();
|
Material t = n.getType();
|
||||||
String cords = "" + x + "." + y + "." + z + "." + wid + "." + t.name() + "";
|
String cords = "" + x + "." + y + "." + z + "." + wid + "." + t.name() + "";
|
||||||
if (CacheHandler.pistonCache.get(cords) == null) {
|
if (!duplicateSuppression || CacheHandler.pistonCache.get(cords) == null) {
|
||||||
log = 1;
|
log = 1;
|
||||||
}
|
}
|
||||||
CacheHandler.pistonCache.put(cords, new Object[] { unixtimestamp });
|
if (duplicateSuppression) {
|
||||||
|
CacheHandler.pistonCache.put(cords, new Object[] { unixtimestamp });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
l++;
|
l++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,11 @@ public final class BlockSpreadListener extends Queue implements Listener {
|
|||||||
|
|
||||||
BlockState blockstate = event.getNewState();
|
BlockState blockstate = event.getNewState();
|
||||||
Material type = blockstate.getType();
|
Material type = blockstate.getType();
|
||||||
|
Config config = Config.getConfig(event.getBlock().getWorld());
|
||||||
|
|
||||||
if (Config.getConfig(event.getBlock().getWorld()).VINE_GROWTH && (BlockGroup.VINES.contains(type) || BlockGroup.AMETHYST.contains(type) || type == Material.CHORUS_FLOWER || type == Material.BAMBOO)) {
|
if (config.VINE_GROWTH && (BlockGroup.VINES.contains(type) || BlockGroup.AMETHYST.contains(type) || type == Material.CHORUS_FLOWER || type == Material.BAMBOO)) {
|
||||||
Block block = event.getBlock();
|
Block block = event.getBlock();
|
||||||
if (checkCacheData(block, type)) {
|
if (config.DUPLICATE_SUPPRESSION && checkCacheData(block, type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,9 +73,9 @@ public final class BlockSpreadListener extends Queue implements Listener {
|
|||||||
Queue.queueBlockPlaceDelayed("#bamboo", block.getLocation(), type, null, block.getState(), 0);
|
Queue.queueBlockPlaceDelayed("#bamboo", block.getLocation(), type, null, block.getState(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Config.getConfig(event.getBlock().getWorld()).SCULK_SPREAD && BlockGroup.SCULK.contains(type)) {
|
else if (config.SCULK_SPREAD && BlockGroup.SCULK.contains(type)) {
|
||||||
Block block = event.getBlock();
|
Block block = event.getBlock();
|
||||||
if (checkCacheData(block, type)) {
|
if (config.DUPLICATE_SUPPRESSION && checkCacheData(block, type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,11 +73,15 @@ import net.coreprotect.CoreProtect;
|
|||||||
import net.coreprotect.bukkit.BukkitAdapter;
|
import net.coreprotect.bukkit.BukkitAdapter;
|
||||||
import net.coreprotect.config.Config;
|
import net.coreprotect.config.Config;
|
||||||
import net.coreprotect.consumer.Queue;
|
import net.coreprotect.consumer.Queue;
|
||||||
|
import net.coreprotect.thread.CacheHandler;
|
||||||
import net.coreprotect.thread.Scheduler;
|
import net.coreprotect.thread.Scheduler;
|
||||||
import net.coreprotect.utility.serialize.ItemMetaHandler;
|
import net.coreprotect.utility.serialize.ItemMetaHandler;
|
||||||
|
|
||||||
public final class EntityDeathListener extends Queue implements Listener {
|
public final class EntityDeathListener extends Queue implements Listener {
|
||||||
|
|
||||||
|
private static final int ENTITY_KILL_DUPLICATE_THRESHOLD = 256;
|
||||||
|
private static final int ENTITY_KILL_DUPLICATE_WINDOW_SECONDS = 900;
|
||||||
|
|
||||||
public static void parseEntityKills(String message) {
|
public static void parseEntityKills(String message) {
|
||||||
message = message.trim().toLowerCase(Locale.ROOT);
|
message = message.trim().toLowerCase(Locale.ROOT);
|
||||||
if (!message.contains(" ")) {
|
if (!message.contains(" ")) {
|
||||||
@@ -258,6 +262,10 @@ public final class EntityDeathListener extends Queue implements Listener {
|
|||||||
e = "#lightning";
|
e = "#lightning";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Config.getConfig(entity.getWorld()).DUPLICATE_SUPPRESSION && shouldSuppressEntityKill(e, entity, cause)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.length() > 0) {
|
if (e.length() > 0) {
|
||||||
List<Object> data = new ArrayList<>();
|
List<Object> data = new ArrayList<>();
|
||||||
List<Object> age = new ArrayList<>();
|
List<Object> age = new ArrayList<>();
|
||||||
@@ -552,6 +560,18 @@ public final class EntityDeathListener extends Queue implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldSuppressEntityKill(String user, LivingEntity entity, DamageCause cause) {
|
||||||
|
if (user == null || !user.startsWith("#") || "#command".equals(user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location location = entity.getLocation();
|
||||||
|
String causeName = cause == null ? "UNKNOWN" : cause.name();
|
||||||
|
String customName = entity.getCustomName();
|
||||||
|
String signature = location.getWorld().getUID().toString() + "." + location.getBlockX() + "." + location.getBlockY() + "." + location.getBlockZ() + "." + user + "." + entity.getType().name() + "." + causeName + "." + (customName == null ? "-" : customName);
|
||||||
|
return CacheHandler.shouldSuppressRepeat(CacheHandler.entityKillDuplicateCache, signature, ENTITY_KILL_DUPLICATE_THRESHOLD, ENTITY_KILL_DUPLICATE_WINDOW_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onEntityDeath(EntityDeathEvent event) {
|
public void onEntityDeath(EntityDeathEvent event) {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ public final class BlockPreDispenseListener extends Queue implements Listener {
|
|||||||
public void onBlockPreDispense(BlockPreDispenseEvent event) {
|
public void onBlockPreDispense(BlockPreDispenseEvent event) {
|
||||||
Block block = event.getBlock();
|
Block block = event.getBlock();
|
||||||
World world = block.getWorld();
|
World world = block.getWorld();
|
||||||
if (!Config.getConfig(world).BLOCK_PLACE) {
|
Config config = Config.getConfig(world);
|
||||||
|
if (!config.BLOCK_PLACE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,47 +48,35 @@ public final class BlockPreDispenseListener extends Queue implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a basic location key for this dispenser
|
|
||||||
String locationKey = block.getWorld().getUID().toString() + "." + block.getX() + "." + block.getY() + "." + block.getZ();
|
String locationKey = block.getWorld().getUID().toString() + "." + block.getX() + "." + block.getY() + "." + block.getZ();
|
||||||
|
if (config.DUPLICATE_SUPPRESSION) {
|
||||||
|
String eventKey = event.getSlot() + "." + item.getType().name() + ":" + item.getAmount();
|
||||||
|
|
||||||
// Create a detailed event key that includes item details
|
if (item.hasItemMeta()) {
|
||||||
String eventKey = event.getSlot() + "." + item.getType().name() + ":" + item.getAmount();
|
try {
|
||||||
|
eventKey += ":" + item.getItemMeta().hashCode();
|
||||||
// Add metadata hash if available
|
}
|
||||||
if (item.hasItemMeta()) {
|
catch (Exception e) {
|
||||||
try {
|
}
|
||||||
eventKey += ":" + item.getItemMeta().hashCode();
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
|
||||||
// If we can't get metadata hash, just use the basic key
|
ConcurrentHashMap<String, Long> locationMap = ConfigHandler.dispenserNoChange.computeIfAbsent(locationKey, k -> new ConcurrentHashMap<>());
|
||||||
|
Long lastNoChangeTime = locationMap.get(eventKey);
|
||||||
|
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
if (lastNoChangeTime != null && (currentTime - lastNoChangeTime) < CACHE_EXPIRY_TIME) {
|
||||||
|
locationMap.put(eventKey, currentTime);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConfigHandler.dispenserNoChange.remove(locationKey);
|
||||||
|
ConfigHandler.dispenserPending.put(locationKey, new Object[] { eventKey, currentTime, event.getSlot(), item.clone() });
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
// Get or create the inner map for this location
|
ConfigHandler.dispenserNoChange.remove(locationKey);
|
||||||
ConcurrentHashMap<String, Long> locationMap = ConfigHandler.dispenserNoChange.computeIfAbsent(locationKey, k -> new ConcurrentHashMap<>());
|
ConfigHandler.dispenserPending.remove(locationKey);
|
||||||
|
|
||||||
// Check if this specific dispenser event has been marked as having no changes recently
|
|
||||||
Long lastNoChangeTime = locationMap.get(eventKey);
|
|
||||||
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
if (lastNoChangeTime != null && (currentTime - lastNoChangeTime) < CACHE_EXPIRY_TIME) {
|
|
||||||
// This specific dispenser event was recently processed and had no changes
|
|
||||||
// Update the timestamp to extend the skip period
|
|
||||||
locationMap.put(eventKey, currentTime);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a new or changed event
|
|
||||||
// Clear any existing dispenserNoChange entries for this location
|
|
||||||
ConfigHandler.dispenserNoChange.remove(locationKey);
|
|
||||||
|
|
||||||
// Store the event details for ContainerLogger to use
|
|
||||||
ConfigHandler.dispenserPending.put(locationKey, new Object[] { eventKey, // The detailed event key
|
|
||||||
currentTime, // Timestamp
|
|
||||||
event.getSlot(), // Slot
|
|
||||||
item.clone() // Item (cloned to prevent modification)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process the inventory transaction
|
// Process the inventory transaction
|
||||||
String user = "#dispenser";
|
String user = "#dispenser";
|
||||||
ItemStack[] inventory = ((InventoryHolder) block.getState()).getInventory().getStorageContents();
|
ItemStack[] inventory = ((InventoryHolder) block.getState()).getInventory().getStorageContents();
|
||||||
|
|||||||
@@ -19,14 +19,44 @@ public class CacheHandler implements Runnable {
|
|||||||
public static Map<String, Object[]> entityCache = Collections.synchronizedMap(new HashMap<>());
|
public static Map<String, Object[]> entityCache = Collections.synchronizedMap(new HashMap<>());
|
||||||
public static ConcurrentHashMap<String, Object[]> pistonCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
public static ConcurrentHashMap<String, Object[]> pistonCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
||||||
public static ConcurrentHashMap<String, Object[]> spreadCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
public static ConcurrentHashMap<String, Object[]> spreadCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
||||||
|
public static ConcurrentHashMap<String, Object[]> containerDuplicateCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
||||||
|
public static ConcurrentHashMap<String, Object[]> flowDuplicateCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
||||||
|
public static ConcurrentHashMap<String, Object[]> bonemealDuplicateCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
||||||
|
public static ConcurrentHashMap<String, Object[]> entityKillDuplicateCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
||||||
public static ConcurrentHashMap<Location, Object[]> redstoneCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
public static ConcurrentHashMap<Location, Object[]> redstoneCache = new ConcurrentHashMap<>(16, 0.75f, 2);
|
||||||
|
|
||||||
|
public static boolean shouldSuppressRepeat(ConcurrentHashMap<String, Object[]> cache, String cacheId, int threshold, int resetWindowSeconds) {
|
||||||
|
if (cache == null || cacheId == null || cacheId.isEmpty() || threshold <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int timestamp = (int) (System.currentTimeMillis() / 1000L);
|
||||||
|
Object[] result = cache.compute(cacheId, (key, value) -> {
|
||||||
|
int count = 1;
|
||||||
|
if (value != null) {
|
||||||
|
int previousTime = (int) value[0];
|
||||||
|
int previousCount = (int) value[1];
|
||||||
|
if (timestamp - previousTime <= resetWindowSeconds) {
|
||||||
|
count = previousCount + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > (threshold + 1)) {
|
||||||
|
count = threshold + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Object[] { timestamp, count };
|
||||||
|
});
|
||||||
|
|
||||||
|
return ((int) result[1]) > threshold;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
while (ConfigHandler.serverRunning) {
|
while (ConfigHandler.serverRunning) {
|
||||||
try {
|
try {
|
||||||
for (int id = 0; id < 9; id++) {
|
for (int id = 0; id < 13; id++) {
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
int scanTime = 30;
|
int scanTime = 30;
|
||||||
Map cache = CacheHandler.lookupCache;
|
Map cache = CacheHandler.lookupCache;
|
||||||
@@ -63,6 +93,22 @@ public class CacheHandler implements Runnable {
|
|||||||
// Clean up dispenserNoChange cache
|
// Clean up dispenserNoChange cache
|
||||||
cleanupDispenserCache();
|
cleanupDispenserCache();
|
||||||
continue;
|
continue;
|
||||||
|
case 9:
|
||||||
|
cache = CacheHandler.containerDuplicateCache;
|
||||||
|
scanTime = 1800; // 30 minutes
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
cache = CacheHandler.flowDuplicateCache;
|
||||||
|
scanTime = 900; // 15 minutes
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
cache = CacheHandler.bonemealDuplicateCache;
|
||||||
|
scanTime = 900; // 15 minutes
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
cache = CacheHandler.entityKillDuplicateCache;
|
||||||
|
scanTime = 900; // 15 minutes
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int timestamp = (int) (System.currentTimeMillis() / 1000L) - scanTime;
|
int timestamp = (int) (System.currentTimeMillis() / 1000L) - scanTime;
|
||||||
|
|||||||
Reference in new issue
Block a user