diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java index 2f84732a7..1cfc897d4 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java @@ -266,7 +266,7 @@ public class ChunkListener implements Listener { long start = System.currentTimeMillis(); int i = 0; while (System.currentTimeMillis() - start < 250) { - if (i >= tiles.length) { + if (i >= tiles.length - Settings.Chunk_Processor.MAX_TILES) { Bukkit.getScheduler().cancelTask(TaskManager.tasks.get(currentIndex)); TaskManager.tasks.remove(currentIndex); PlotSquared.debug("Successfully processed and unloaded chunk!"); @@ -287,11 +287,16 @@ public class ChunkListener implements Listener { Entity[] entities = chunk.getEntities(); BlockState[] tiles = chunk.getTileEntities(); if (entities.length > Settings.Chunk_Processor.MAX_ENTITIES) { - for (Entity ent : entities) { - if (!(ent instanceof Player)) { - ent.remove(); + int toRemove = entities.length - Settings.Chunk_Processor.MAX_ENTITIES; + int index = 0; + while (toRemove > 0 && index < entities.length) { + final Entity entity = entities[index++]; + if (!(entity instanceof Player)) { + entity.remove(); + toRemove--; } } + PlotSquared.debug( "PlotSquared detected unsafe chunk and processed: " + (chunk.getX() << 4) + "," + ( chunk.getX() << 4)); @@ -304,8 +309,9 @@ public class ChunkListener implements Listener { cleanChunk(chunk); return true; } - for (BlockState tile : tiles) { - tile.getBlock().setType(Material.AIR, false); + + for (int i = 0 ; i < (tiles.length - Settings.Chunk_Processor.MAX_TILES); i++) { + tiles[i].getBlock().setType(Material.AIR, false); } } return false; diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PaperListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PaperListener.java index f87050c88..6c7e4c9eb 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PaperListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PaperListener.java @@ -33,6 +33,7 @@ import com.destroystokyo.paper.event.entity.SlimePathfindEvent; import com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent; import com.plotsquared.bukkit.util.BukkitUtil; import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.configuration.Captions; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.location.Location; import com.plotsquared.core.player.PlotPlayer; @@ -41,6 +42,7 @@ import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.flag.implementations.DoneFlag; import org.bukkit.Chunk; import org.bukkit.block.Block; +import org.bukkit.block.TileState; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; @@ -48,7 +50,9 @@ import org.bukkit.entity.Projectile; import org.bukkit.entity.Slime; import org.bukkit.entity.ThrownPotion; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.projectiles.ProjectileSource; @@ -252,6 +256,27 @@ public class PaperListener implements Listener { } } + @EventHandler(priority = EventPriority.HIGHEST) public void onBlockPlace(BlockPlaceEvent event) { + if (!Settings.Paper_Components.TILE_ENTITY_CHECK || !Settings.Enabled_Components.CHUNK_PROCESSOR) { + return; + } + if (!(event.getBlock().getState(false) instanceof TileState)) { + return; + } + final Location location = BukkitUtil.getLocation(event.getBlock().getLocation()); + final PlotArea plotArea = location.getPlotArea(); + if (plotArea == null) { + return; + } + final int tileEntityCount = event.getBlock().getChunk().getTileEntities(false).length; + if (tileEntityCount >= Settings.Chunk_Processor.MAX_TILES) { + final PlotPlayer plotPlayer = BukkitUtil.getPlayer(event.getPlayer()); + Captions.TILE_ENTITY_CAP_REACHED.send(plotPlayer, Settings.Chunk_Processor.MAX_TILES); + event.setCancelled(true); + event.setBuild(false); + } + } + /** * Unsure if this will be any performance improvement over the spigot version, * but here it is anyway :) diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitUtil.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitUtil.java index 2371fe799..23fd8937b 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitUtil.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitUtil.java @@ -42,9 +42,13 @@ import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.util.uuid.UUIDHandler; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockCategories; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; import io.papermc.lib.PaperLib; import lombok.NonNull; import org.bukkit.Bukkit; @@ -95,10 +99,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; import java.util.function.IntConsumer; +import java.util.stream.Stream; @SuppressWarnings({"unused", "WeakerAccess"}) public class BukkitUtil extends WorldUtil { @@ -638,6 +644,38 @@ public class BukkitUtil extends WorldUtil { return types; } + private final Collection tileEntityTypes = new HashSet<>(); + @Override public Collection getTileEntityTypes() { + if (this.tileEntityTypes.isEmpty()) { + // Categories + tileEntityTypes.addAll(BlockCategories.BANNERS.getAll()); + tileEntityTypes.addAll(BlockCategories.SIGNS.getAll()); + tileEntityTypes.addAll(BlockCategories.BEDS.getAll()); + tileEntityTypes.addAll(BlockCategories.FLOWER_POTS.getAll()); + // Individual Types + // Add these from strings + Stream.of("barrel", "beacon", "beehive", "bee_nest", "bell", "blast_furnace", + "brewing_stand", "campfire", "chest", "ender_chest", "trapped_chest", + "command_block", "end_gateway", "hopper", "jigsaw", "jubekox", + "lectern", "note_block", "black_shulker_box", "blue_shulker_box", + "brown_shulker_box", "cyan_shulker_box", "gray_shulker_box", "green_shulker_box", + "light_blue_shulker_box", "light_gray_shulker_box", "lime_shulker_box", + "magenta_shulker_box", "orange_shulker_box", "pink_shulker_box", + "purple_shulker_box", "red_shulker_box", "shulker_box", "white_shulker_box", + "yellow_shulker_box", "smoker", "structure_block", "structure_void") + .map(BlockTypes::get) + .filter(Objects::nonNull) + .forEach(tileEntityTypes::add); + } + return this.tileEntityTypes; + } + + @Override + public int getTileEntityCount(String world, BlockVector2 chunk) { + return Bukkit.getWorld(world).getChunkAt(chunk.getBlockX(), chunk.getBlockZ()) + .getTileEntities().length; + } + private static void ensureLoaded(final String world, final int x, final int z, final Consumer chunkConsumer) { PaperLib.getChunkAtAsync(getWorld(world), x >> 4, z >> 4, true) diff --git a/Core/src/main/java/com/plotsquared/core/command/Set.java b/Core/src/main/java/com/plotsquared/core/command/Set.java index 6b225054f..4e6fcafc9 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Set.java +++ b/Core/src/main/java/com/plotsquared/core/command/Set.java @@ -37,8 +37,10 @@ import com.plotsquared.core.util.MainUtil; import com.plotsquared.core.util.PatternUtil; import com.plotsquared.core.util.Permissions; import com.plotsquared.core.util.StringMan; +import com.plotsquared.core.util.WorldUtil; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.world.block.BlockCategory; +import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import java.util.ArrayList; @@ -80,9 +82,15 @@ public class Set extends SubCommand { String material = StringMan.join(Arrays.copyOfRange(args, 1, args.length), ",").trim(); - final List forbiddenTypes = Settings.General.INVALID_BLOCKS; - if (!Permissions.hasPermission(player, Captions.PERMISSION_ADMIN_ALLOW_UNSAFE) - && !forbiddenTypes.isEmpty()) { + final List forbiddenTypes = new ArrayList<>(Settings.General.INVALID_BLOCKS); + + if (Settings.Enabled_Components.CHUNK_PROCESSOR) { + forbiddenTypes.addAll(WorldUtil.IMP.getTileEntityTypes().stream().map( + BlockType::getName).collect(Collectors.toList())); + } + + if (!Permissions.hasPermission(player, Captions.PERMISSION_ADMIN_ALLOW_UNSAFE) && + !forbiddenTypes.isEmpty()) { for (String forbiddenType : forbiddenTypes) { forbiddenType = forbiddenType.toLowerCase(Locale.ENGLISH); if (forbiddenType.startsWith("minecraft:")) { @@ -96,10 +104,9 @@ public class Set extends SubCommand { if (blockType.startsWith("##")) { try { - final BlockCategory category = BlockCategory.REGISTRY - .get(blockType.substring(2).toLowerCase(Locale.ENGLISH)); - if (category == null || !category - .contains(BlockTypes.get(forbiddenType))) { + final BlockCategory category = BlockCategory.REGISTRY.get(blockType.substring(2) + .replaceAll("[*^|]+", "").toLowerCase(Locale.ENGLISH)); + if (category == null || !category.contains(BlockTypes.get(forbiddenType))) { continue; } } catch (final Throwable ignored) { diff --git a/Core/src/main/java/com/plotsquared/core/configuration/Captions.java b/Core/src/main/java/com/plotsquared/core/configuration/Captions.java index 5909b119f..17c0d7c4d 100644 --- a/Core/src/main/java/com/plotsquared/core/configuration/Captions.java +++ b/Core/src/main/java/com/plotsquared/core/configuration/Captions.java @@ -446,6 +446,7 @@ public enum Captions implements Caption { NOT_VALID_PLOT_WORLD("$2That is not a valid plot area (case sensitive)", "Errors"), NO_PLOTS("$2You don't have any plots", "Errors"), WAIT_FOR_TIMER("$2A set block timer is bound to either the current plot or you. Please wait for it to finish", "Errors"), + TILE_ENTITY_CAP_REACHED("$2The total number of tile entities in this chunk may not exceed $1%s", "Errors"), // DEBUG_REPORT_CREATED("$1Uploaded a full debug to: $1%url%", "Paste"), PURGE_SUCCESS("$4Successfully purged %s plots", "Purge"), diff --git a/Core/src/main/java/com/plotsquared/core/configuration/Settings.java b/Core/src/main/java/com/plotsquared/core/configuration/Settings.java index c8e86883e..fe3edf6c3 100644 --- a/Core/src/main/java/com/plotsquared/core/configuration/Settings.java +++ b/Core/src/main/java/com/plotsquared/core/configuration/Settings.java @@ -485,6 +485,8 @@ public class Settings extends Config { public static boolean SPAWNER_SPAWN = true; @Comment("Cancel entity spawns from tick spawn rates before they happen (performance buff)") public static boolean CREATURE_SPAWN = true; + @Comment("Check the tile entity limit on block placement") + public static boolean TILE_ENTITY_CHECK = true; } diff --git a/Core/src/main/java/com/plotsquared/core/listener/ProcessedWEExtent.java b/Core/src/main/java/com/plotsquared/core/listener/ProcessedWEExtent.java index a67e743c8..0df6c3a3c 100644 --- a/Core/src/main/java/com/plotsquared/core/listener/ProcessedWEExtent.java +++ b/Core/src/main/java/com/plotsquared/core/listener/ProcessedWEExtent.java @@ -29,6 +29,7 @@ import com.plotsquared.core.PlotSquared; import com.plotsquared.core.configuration.Captions; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.util.WEManager; +import com.plotsquared.core.util.WorldUtil; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; @@ -45,6 +46,8 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; import java.util.Set; public class ProcessedWEExtent extends AbstractDelegateExtent { @@ -52,12 +55,11 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { private final Set mask; private final String world; private final int max; - int BScount = 0; int Ecount = 0; - boolean BSblocked = false; boolean Eblocked = false; private int count; private Extent parent; + private Map tileEntityCount = new HashMap<>(); public ProcessedWEExtent(String world, Set mask, int max, Extent child, Extent parent) { @@ -89,95 +91,39 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { @Override public > boolean setBlock(BlockVector3 location, T block) throws WorldEditException { - String id = block.getBlockType().getId(); - switch (id) { - case "54": - case "130": - case "142": - case "27": - case "137": - case "52": - case "154": - case "84": - case "25": - case "144": - case "138": - case "176": - case "177": - case "63": - case "68": - case "323": - case "117": - case "116": - case "28": - case "66": - case "157": - case "61": - case "62": - case "140": - case "146": - case "149": - case "150": - case "158": - case "23": - case "123": - case "124": - case "29": - case "33": - case "151": - case "178": - if (this.BSblocked) { - return false; - } - this.BScount++; - if (this.BScount > Settings.Chunk_Processor.MAX_TILES) { - this.BSblocked = true; - PlotSquared.debug( - Captions.PREFIX + "&cDetected unsafe WorldEdit: " + location.getX() + "," - + location.getZ()); - } - if (WEManager - .maskContains(this.mask, location.getX(), location.getY(), location.getZ())) { - if (this.count++ > this.max) { - if (this.parent != null) { - try { - Field field = - AbstractDelegateExtent.class.getDeclaredField("extent"); - field.setAccessible(true); - field.set(this.parent, new NullExtent()); - } catch (Exception e) { - e.printStackTrace(); - } - this.parent = null; - } - return false; - } - return super.setBlock(location, block); - } - break; - default: - if (WEManager - .maskContains(this.mask, location.getX(), location.getY(), location.getZ())) { - if (this.count++ > this.max) { - if (this.parent != null) { - try { - Field field = - AbstractDelegateExtent.class.getDeclaredField("extent"); - field.setAccessible(true); - field.set(this.parent, new NullExtent()); - } catch (Exception e) { - e.printStackTrace(); - } - this.parent = null; - } - return false; - } - super.setBlock(location, block); - } - return true; + final boolean isTile = WorldUtil.IMP.getTileEntityTypes().contains(block.getBlockType()); + if (isTile) { + final Integer[] tileEntityCount = this.tileEntityCount.computeIfAbsent(getChunkKey(location), + key -> new Integer[] {WorldUtil.IMP.getTileEntityCount(world, + BlockVector2.at(location.getBlockX() >> 4, location.getBlockZ() >> 4))}); + if (tileEntityCount[0] >= Settings.Chunk_Processor.MAX_TILES) { + return false; + } else { + tileEntityCount[0]++; + PlotSquared.debug(Captions.PREFIX + "&cDetected unsafe WorldEdit: " + location.getX() + "," + + location.getZ()); + } } - return false; + if (WEManager.maskContains(this.mask, location.getX(), location.getY(), location.getZ())) { + if (this.count++ > this.max) { + if (this.parent != null) { + try { + Field field = + AbstractDelegateExtent.class.getDeclaredField("extent"); + field.setAccessible(true); + field.set(this.parent, new NullExtent()); + } catch (Exception e) { + e.printStackTrace(); + } + this.parent = null; + } + return false; + } + return super.setBlock(location, block); + } + + return !isTile; } @Override public Entity createEntity(Location location, BaseEntity entity) { @@ -202,4 +148,10 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { return WEManager.maskContains(this.mask, position.getX(), position.getZ()) && super .setBiome(position, biome); } + + + private static long getChunkKey(final BlockVector3 location) { + return (long) (location.getBlockX() >> 4) & 4294967295L | ((long) (location.getBlockZ() >> 4) & 4294967295L) << 32; + } + } diff --git a/Core/src/main/java/com/plotsquared/core/util/WorldUtil.java b/Core/src/main/java/com/plotsquared/core/util/WorldUtil.java index ebcb82d1f..4d733d5eb 100644 --- a/Core/src/main/java/com/plotsquared/core/util/WorldUtil.java +++ b/Core/src/main/java/com/plotsquared/core/util/WorldUtil.java @@ -39,6 +39,7 @@ import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.entity.EntityType; import org.jetbrains.annotations.NotNull; @@ -48,6 +49,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URL; +import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -216,4 +218,8 @@ public abstract class WorldUtil { public abstract Set getTypesInCategory(final String category); + public abstract Collection getTileEntityTypes(); + + public abstract int getTileEntityCount(String world, BlockVector2 chunk); + }