diff --git a/src/main/java/bentobox/addon/limits/commands/AdminCommand.java b/src/main/java/bentobox/addon/limits/commands/AdminCommand.java index 7370a5e..e8ac941 100644 --- a/src/main/java/bentobox/addon/limits/commands/AdminCommand.java +++ b/src/main/java/bentobox/addon/limits/commands/AdminCommand.java @@ -26,6 +26,7 @@ public class AdminCommand extends CompositeCommand { public AdminCommand(Limits addon, CompositeCommand parent) { super(parent, "limits"); this.addon = addon; + new CalcCommand(addon, this); } /* (non-Javadoc) @@ -35,8 +36,8 @@ public class AdminCommand extends CompositeCommand { public void setup() { this.setPermission("limits.admin.limits"); this.setOnlyPlayer(true); - this.setParametersHelp("admin.limits.parameters"); - this.setDescription("admin.limits.description"); + this.setParametersHelp("admin.limits.main.parameters"); + this.setDescription("admin.limits.main.description"); } /* (non-Javadoc) diff --git a/src/main/java/bentobox/addon/limits/commands/CalcCommand.java b/src/main/java/bentobox/addon/limits/commands/CalcCommand.java new file mode 100644 index 0000000..77dd80d --- /dev/null +++ b/src/main/java/bentobox/addon/limits/commands/CalcCommand.java @@ -0,0 +1,81 @@ +package bentobox.addon.limits.commands; + +import bentobox.addon.limits.Limits; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.util.Util; + +/** + * + * @author YellowZaki + */ +public class CalcCommand extends CompositeCommand { + + private final Limits addon; + + /** + * Admin command + * + * @param addon - addon + */ + public CalcCommand(Limits addon, CompositeCommand parent) { + super(parent, "calc"); + this.addon = addon; + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup() + */ + @Override + public void setup() { + this.setPermission("limits.admin.limits.calc"); + this.setOnlyPlayer(false); + this.setParametersHelp("admin.limits.calc.parameters"); + this.setDescription("admin.limits.calc.description"); + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) + */ + @Override + public boolean execute(User user, String label, List args) { + if (args.size() == 1) { + final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); + if (playerUUID == null) { + user.sendMessage("general.errors.unknown-player", args.get(0)); + return true; + } else { + //Calculate + calcLimits(playerUUID, user); + } + return true; + } else { + showHelp(this, user); + return false; + } + } + + public void calcLimits(UUID targetPlayer, User sender) { + if (addon.getIslands().getIsland(getWorld(), targetPlayer) != null) { + new LimitsCalc(getWorld(), getPlugin(), targetPlayer, addon, sender); + } else { + sender.sendMessage("general.errors.player-has-no-island"); + } + } + + @Override + public Optional> tabComplete(User user, String alias, List args) { + String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; + if (args.isEmpty()) { + // Don't show every player on the server. Require at least the first letter + return Optional.empty(); + } + List options = new ArrayList<>(Util.getOnlinePlayerList(user)); + return Optional.of(Util.tabLimit(options, lastArg)); + } + +} diff --git a/src/main/java/bentobox/addon/limits/commands/LimitPanel.java b/src/main/java/bentobox/addon/limits/commands/LimitPanel.java index 3fd6abd..04faf99 100644 --- a/src/main/java/bentobox/addon/limits/commands/LimitPanel.java +++ b/src/main/java/bentobox/addon/limits/commands/LimitPanel.java @@ -53,7 +53,12 @@ class LimitPanel { for (Entry en : matLimits.entrySet()) { PanelItemBuilder pib = new PanelItemBuilder(); pib.name(Util.prettifyText(en.getKey().toString())); - pib.icon(en.getKey()); + if (en.getKey() == Material.REDSTONE_WIRE) { + pib.icon(Material.REDSTONE); + } + else { + pib.icon(en.getKey()); + } int count = ibc == null ? 0 : ibc.getBlockCount().getOrDefault(en.getKey(), 0); String color = count >= en.getValue() ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color"); pib.description(color diff --git a/src/main/java/bentobox/addon/limits/commands/LimitsCalc.java b/src/main/java/bentobox/addon/limits/commands/LimitsCalc.java new file mode 100644 index 0000000..5efb02f --- /dev/null +++ b/src/main/java/bentobox/addon/limits/commands/LimitsCalc.java @@ -0,0 +1,143 @@ +package bentobox.addon.limits.commands; + +import bentobox.addon.limits.Limits; +import bentobox.addon.limits.listeners.BlockLimitsListener; +import bentobox.addon.limits.objects.IslandBlockCount; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.scheduler.BukkitTask; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Pair; + +/** + * + * @author YellowZaki + */ +public class LimitsCalc { + + private boolean checking; + private Limits addon; + private BentoBox instance; + private World world; + private Island island; + private BlockLimitsListener bll; + private IslandBlockCount ibc; + private Map blockCount; + private BukkitTask task; + private User sender; + + + LimitsCalc(World world, BentoBox instance, UUID targetPlayer, Limits addon, User sender) { + this.checking = true; + this.addon = addon; + this.instance = instance; + this.world = world; + this.island = instance.getIslands().getIsland(world, targetPlayer); + this.bll = addon.getBlockLimitListener(); + this.ibc = bll.getIsland(island.getUniqueId()); + blockCount = new HashMap<>(); + this.sender = sender; + Set> chunksToScan = getChunksToScan(island); + this.task = addon.getServer().getScheduler().runTaskTimer(addon.getPlugin(), () -> { + Set chunkSnapshot = new HashSet<>(); + if (checking) { + Iterator> it = chunksToScan.iterator(); + if (!it.hasNext()) { + // Nothing left + tidyUp(); + return; + } + // Add chunk snapshots to the list + while (it.hasNext() && chunkSnapshot.size() < 200) { + Pair pair = it.next(); + if (!world.isChunkLoaded(pair.x, pair.z)) { + world.loadChunk(pair.x, pair.z); + chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot()); + world.unloadChunk(pair.x, pair.z); + } else { + chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot()); + } + it.remove(); + } + // Move to next step + checking = false; + checkChunksAsync(chunkSnapshot); + } + }, 0L, 1); + } + + private void checkChunksAsync(final Set chunkSnapshot) { + // Run async task to scan chunks + addon.getServer().getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> { + for (ChunkSnapshot chunk : chunkSnapshot) { + scanChunk(chunk); + } + // Nothing happened, change state + checking = true; + }); + + } + + private void scanChunk(ChunkSnapshot chunk) { + for (int x = 0; x < 16; x++) { + // Check if the block coordinate is inside the protection zone and if not, don't count it + if (chunk.getX() * 16 + x < island.getMinProtectedX() || chunk.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) { + continue; + } + for (int z = 0; z < 16; z++) { + // Check if the block coordinate is inside the protection zone and if not, don't count it + if (chunk.getZ() * 16 + z < island.getMinProtectedZ() || chunk.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) { + continue; + } + for (int y = 0; y < island.getCenter().getWorld().getMaxHeight(); y++) { + Material blockData = chunk.getBlockType(x, y, z); + // Air is free + if (!blockData.equals(Material.AIR)) { + checkBlock(blockData); + } + } + } + } + } + + private void checkBlock(Material md) { + md = bll.fixMaterial(md); + // md is limited + if (bll.getMaterialLimits(world, island.getUniqueId()).containsKey(md)) { + if (!blockCount.containsKey(md)) { + blockCount.put(md, 1); + } else { + blockCount.put(md, blockCount.get(md) + 1); + } + } + } + + private Set> getChunksToScan(Island island) { + Set> chunkSnapshot = new HashSet<>(); + for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 + 16); x += 16) { + for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 + 16); z += 16) { + Pair pair = new Pair<>(world.getBlockAt(x, 0, z).getChunk().getX(), world.getBlockAt(x, 0, z).getChunk().getZ()); + chunkSnapshot.add(pair); + } + } + return chunkSnapshot; + } + + private void tidyUp() { + // Cancel + task.cancel(); + ibc.setBlockCount(blockCount); + bll.setIsland(island.getUniqueId(), ibc); + sender.sendMessage("admin.limits.calc.finished"); + } + +} diff --git a/src/main/java/bentobox/addon/limits/listeners/BlockLimitsListener.java b/src/main/java/bentobox/addon/limits/listeners/BlockLimitsListener.java index 41458fd..d0ed381 100644 --- a/src/main/java/bentobox/addon/limits/listeners/BlockLimitsListener.java +++ b/src/main/java/bentobox/addon/limits/listeners/BlockLimitsListener.java @@ -32,6 +32,7 @@ import org.bukkit.event.block.BlockFromToEvent; import bentobox.addon.limits.Limits; import bentobox.addon.limits.objects.IslandBlockCount; +import org.bukkit.block.BlockFace; import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeleteEvent; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; @@ -107,6 +108,7 @@ public class BlockLimitsListener implements Listener { /** * Loads limit map from configuration section + * * @param cs - configuration section * @return limit map */ @@ -124,6 +126,7 @@ public class BlockLimitsListener implements Listener { return mats; } + /** * Save the count database completely */ @@ -140,6 +143,22 @@ public class BlockLimitsListener implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onBlock(BlockBreakEvent e) { notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), false), e.getBlock().getType()); + // Player breaks a block and there was a redstone dust/repeater/... above + if (e.getBlock().getRelative(BlockFace.UP).getType() == Material.REDSTONE_WIRE || e.getBlock().getRelative(BlockFace.UP).getType() == Material.REPEATER || e.getBlock().getRelative(BlockFace.UP).getType() == Material.COMPARATOR || e.getBlock().getRelative(BlockFace.UP).getType() == Material.REDSTONE_TORCH) { + process(e.getBlock().getRelative(BlockFace.UP), false); + } + if (e.getBlock().getRelative(BlockFace.EAST).getType() == Material.REDSTONE_WALL_TORCH) { + process(e.getBlock().getRelative(BlockFace.EAST), false); + } + if (e.getBlock().getRelative(BlockFace.WEST).getType() == Material.REDSTONE_WALL_TORCH) { + process(e.getBlock().getRelative(BlockFace.WEST), false); + } + if (e.getBlock().getRelative(BlockFace.SOUTH).getType() == Material.REDSTONE_WALL_TORCH) { + process(e.getBlock().getRelative(BlockFace.SOUTH), false); + } + if (e.getBlock().getRelative(BlockFace.NORTH).getType() == Material.REDSTONE_WALL_TORCH) { + process(e.getBlock().getRelative(BlockFace.NORTH), false); + } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -216,19 +235,38 @@ public class BlockLimitsListener implements Listener { } } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlock(BlockFromToEvent e) { + if (e.getBlock().isLiquid()) { + if (e.getToBlock().getType() == Material.REDSTONE_WIRE || e.getToBlock().getType() == Material.REPEATER || e.getToBlock().getType() == Material.COMPARATOR || e.getToBlock().getType() == Material.REDSTONE_TORCH || e.getToBlock().getType() == Material.REDSTONE_WALL_TORCH) { + process(e.getToBlock(), false); + } + } + } + private int process(Block b, boolean add) { return process(b, add, b.getType()); } + // It wouldn't make sense to count REDSTONE_WALL_TORCH and REDSTONE_TORCH as separed limits. + public Material fixMaterial(Material b) { + if (b == Material.REDSTONE_WALL_TORCH) { + return Material.REDSTONE_TORCH; + } else { + return b; + } + } + /** * Check if a block can be + * * @param b - block * @param add - true to add a block, false to remove * @param changeTo - material this block will become * @return limit amount if over limit, or -1 if no limitation */ private int process(Block b, boolean add, Material changeTo) { - if (DO_NOT_COUNT.contains(b.getType()) || !addon.inGameModeWorld(b.getWorld())) { + if (DO_NOT_COUNT.contains(fixMaterial(b.getType())) || !addon.inGameModeWorld(b.getWorld())) { return -1; } // Check if on island @@ -243,24 +281,24 @@ public class BlockLimitsListener implements Listener { saveMap.putIfAbsent(id, 0); if (add) { // Check limit - int limit = checkLimit(b.getWorld(), b.getType(), id); + int limit = checkLimit(b.getWorld(), fixMaterial(b.getType()), id); if (limit > -1) { return limit; } - islandCountMap.get(id).add(b.getType()); + islandCountMap.get(id).add(fixMaterial(b.getType())); saveMap.merge(id, 1, Integer::sum); } else { if (islandCountMap.containsKey(id)) { // Check for changes - if (!changeTo.equals(b.getType()) && changeTo.isBlock() && !DO_NOT_COUNT.contains(changeTo)) { + if (!fixMaterial(changeTo).equals(fixMaterial(b.getType())) && fixMaterial(changeTo).isBlock() && !DO_NOT_COUNT.contains(fixMaterial(changeTo))) { // Check limit - int limit = checkLimit(b.getWorld(), changeTo, id); + int limit = checkLimit(b.getWorld(), fixMaterial(changeTo), id); if (limit > -1) { return limit; } - islandCountMap.get(id).add(changeTo); + islandCountMap.get(id).add(fixMaterial(changeTo)); } - islandCountMap.get(id).remove(b.getType()); + islandCountMap.get(id).remove(fixMaterial(b.getType())); saveMap.merge(id, 1, Integer::sum); } } @@ -274,6 +312,7 @@ public class BlockLimitsListener implements Listener { /** * Check if this material is at its limit for world on this island + * * @param w - world * @param m - material * @param id - island id @@ -300,6 +339,7 @@ public class BlockLimitsListener implements Listener { /** * Gets an aggregate map of the limits for this island + * * @param w - world * @param id - island id * @return map of limits for materials @@ -322,6 +362,7 @@ public class BlockLimitsListener implements Listener { /** * Removes island from the database + * * @param e - island delete event */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -333,9 +374,9 @@ public class BlockLimitsListener implements Listener { } } - /** * Set the island block count values + * * @param islandId - island unique id * @param ibc - island block count */ @@ -346,6 +387,7 @@ public class BlockLimitsListener implements Listener { /** * Get the island block count + * * @param islandId - island unique id * @return island block count or null if there is none yet */ diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index ce2900f..8cd0490 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -9,8 +9,13 @@ limits: admin: limits: - parameters: "" - description: "show the island limits for player" + main: + parameters: "" + description: "show the island limits for player" + calc: + parameters: "" + description: "recalculate the island limits for player" + finished: "&aIsland recalc finished sucessfully!" island: limits: