getGameModes() {
+ return gameModes;
+ }
+
+ /**
+ * @return the blockLimitListener
+ */
+ public BlockLimitsListener getBlockLimitListener() {
+ return blockLimitListener;
+ }
+
+ /**
+ * Checks if this world is covered by the activated game modes
+ *
+ * @param world - world
+ * @return true or false
+ */
+ public boolean inGameModeWorld(World world) {
+ return gameModes.stream().anyMatch(gm -> gm.inWorld(world));
+ }
+
+ /**
+ * Get the name of the game mode for this world
+ *
+ * @param world - world
+ * @return game mode name or empty string if none
+ */
+ public String getGameModeName(World world) {
+ return gameModes.stream().filter(gm -> gm.inWorld(world)).findFirst().map(gm -> gm.getDescription().getName()).orElse("");
+ }
+
+ /**
+ * Get the permission prefix for this world
+ *
+ * @param world - world
+ * @return permisdsion prefix or empty string if none
+ */
+ public String getGameModePermPrefix(World world) {
+ return gameModes.stream().filter(gm -> gm.inWorld(world)).findFirst().map(GameModeAddon::getPermissionPrefix).orElse("");
+ }
+
+
+ /**
+ * Check if any of the game modes covered have this name
+ *
+ * @param gameMode - name of game mode
+ * @return true or false
+ */
+ public boolean isCoveredGameMode(String gameMode) {
+ return gameModes.stream().anyMatch(gm -> gm.getDescription().getName().equals(gameMode));
+ }
+
+ /**
+ * @return the joinListener
+ */
+ public JoinListener getJoinListener() {
+ return joinListener;
+ }
+
+ private void registerPlaceholders(GameModeAddon gm) {
+ if (getPlugin().getPlaceholdersManager() == null) return;
+ Registry.MATERIAL.stream()
+ .filter(Material::isBlock)
+ .forEach(m -> registerCountAndLimitPlaceholders(m, gm));
+
+ Arrays.stream(EntityType.values())
+ .forEach(e -> registerCountAndLimitPlaceholders(e, gm));
+ }
+
+ /**
+ * Registers placeholders for the count and limit of the material
+ * in the format of %Limits_(gamemode prefix)_island_(lowercase material name)_count%
+ * and %Limits_(gamemode prefix)_island_(lowercase material name)_limit%
+ *
+ * Example: registerCountAndLimitPlaceholders("HOPPER", gm);
+ * Placeholders:
+ * "Limits_bskyblock_island_hopper_count"
+ * "Limits_bskyblock_island_hopper_limit"
+ * "Limits_bskyblock_island_hopper_base_limit"
+ * "Limits_bskyblock_island_zombie_limit"
+ *
+ * @param m material
+ * @param gm game mode
+ */
+ private void registerCountAndLimitPlaceholders(Material m, GameModeAddon gm) {
+ getPlugin().getPlaceholdersManager().registerPlaceholder(this,
+ gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_count",
+ user -> String.valueOf(getCount(user, m, gm)));
+ getPlugin().getPlaceholdersManager().registerPlaceholder(this,
+ gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_limit",
+ user -> getLimit(user, m, gm));
+ getPlugin().getPlaceholdersManager().registerPlaceholder(this,
+ gm.getDescription().getName().toLowerCase() + "_island_" + m.toString().toLowerCase() + "_base_limit",
+ user -> getBaseLimit(user, m, gm));
+ }
+
+ private void registerCountAndLimitPlaceholders(EntityType e, GameModeAddon gm) {
+ getPlugin().getPlaceholdersManager().registerPlaceholder(this,
+ gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_limit",
+ user -> getLimit(user, e, gm));
+ getPlugin().getPlaceholdersManager().registerPlaceholder(this,
+ gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_base_limit",
+ user -> getBaseLimit(user, e, gm));
+ getPlugin().getPlaceholdersManager().registerPlaceholder(this,
+ gm.getDescription().getName().toLowerCase() + "_island_" + e.toString().toLowerCase() + "_count",
+ user -> String.valueOf(getCount(user, e, gm)));
+ }
+
+ /**
+ * @param user - Used to identify the island the user belongs to
+ * @param m - The material we are trying to count on the island
+ * @param gm Game Mode Addon
+ * @return Number of blocks of the specified material on the given user's island
+ */
+ private int getCount(@Nullable User user, Material m, GameModeAddon gm) {
+ Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
+ if (is == null) {
+ return 0;
+ }
+ @Nullable IslandBlockCount ibc = getBlockLimitListener().getIsland(is.getUniqueId());
+ if (ibc == null) {
+ return 0;
+ }
+ return ibc.getBlockCount(m);
+ }
+
+ private long getCount(@Nullable User user, EntityType e, GameModeAddon gm) {
+ Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
+ if (is == null || e.getEntityClass() == null) {
+ return 0;
+ }
+ Class extends Entity> entityClass = e.getEntityClass();
+ long count = is.getWorld().getEntitiesByClass(entityClass).stream()
+ .filter(ent -> is.inIslandSpace(ent.getLocation())).count();
+ /* NETHER */
+ if (islandWorldManager.isNetherIslands(is.getWorld()) && islandWorldManager.getNetherWorld(is.getWorld()) != null) {
+ count += islandWorldManager.getNetherWorld(is.getWorld()).getEntitiesByClass(entityClass).stream()
+ .filter(ent -> is.inIslandSpace(ent.getLocation())).count();
+ }
+ /* END */
+ if (islandWorldManager.isEndIslands(is.getWorld()) && islandWorldManager.getEndWorld(is.getWorld()) != null) {
+ count += islandWorldManager.getEndWorld(is.getWorld()).getEntitiesByClass(entityClass).stream()
+ .filter(ent -> is.inIslandSpace(ent.getLocation())).count();
+ }
+ return count;
+ }
+
+
+ /**
+ * @param user - Used to identify the island the user belongs to
+ * @param m - The material whose limit we are querying
+ * @param gm Game Mode Addon
+ * @return The limit of the specified material on the given user's island
+ */
+ private String getLimit(@Nullable User user, Material m, GameModeAddon gm) {
+ Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
+ if (is == null) {
+ return LIMIT_NOT_SET;
+ }
+ if (user != null) {
+ // Check the permissions of the user and update
+ this.getJoinListener().checkPerms(user.getPlayer(), gm.getPermissionPrefix() + "island.limit.",
+ is.getUniqueId(), gm.getDescription().getName());
+ }
+ int limit = this.getBlockLimitListener().
+ getMaterialLimits(is.getWorld(), is.getUniqueId()).getOrDefault(m, -1);
+
+ return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit);
+ }
+
+ private String getBaseLimit(@Nullable User user, Material m, GameModeAddon gm) {
+ Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
+ if (is == null) {
+ return LIMIT_NOT_SET;
+ }
+
+ int limit = this.getBlockLimitListener().
+ getMaterialLimits(is.getWorld(), is.getUniqueId()).
+ getOrDefault(m, -1);
+
+ if (limit > 0) {
+ limit -= this.getBlockLimitListener().getIsland(is).getBlockLimitOffset(m);
+ }
+
+ return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit);
+ }
+
+ private String getLimit(@Nullable User user, EntityType e, GameModeAddon gm) {
+ Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
+ if (is == null) {
+ return LIMIT_NOT_SET;
+ }
+
+ int limit = this.getBlockLimitListener().getIsland(is).getEntityLimit(e);
+ if (limit < 0 && this.getSettings().getLimits().containsKey(e)) {
+ limit = this.getSettings().getLimits().get(e);
+ }
+
+ return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit);
+ }
+
+ private String getBaseLimit(@Nullable User user, EntityType e, GameModeAddon gm) {
+ Island is = gm.getIslands().getIsland(gm.getOverWorld(), user);
+ if (is == null || !this.getSettings().getLimits().containsKey(e)) {
+ return LIMIT_NOT_SET;
+ }
+
+ int limit = this.getSettings().getLimits().get(e);
+
+ return limit == -1 ? LIMIT_NOT_SET : String.valueOf(limit);
+ }
+
+
+}
diff --git a/src/main/java/world/bentobox/limits/LimitsPladdon.java b/src/main/java/world/bentobox/limits/LimitsPladdon.java
index 53a5ea7..039b85d 100644
--- a/src/main/java/world/bentobox/limits/LimitsPladdon.java
+++ b/src/main/java/world/bentobox/limits/LimitsPladdon.java
@@ -1,19 +1,19 @@
-package world.bentobox.limits;
-
-
-import world.bentobox.bentobox.api.addons.Addon;
-import world.bentobox.bentobox.api.addons.Pladdon;
-
-
-public class LimitsPladdon extends Pladdon {
-
- private Addon addon;
-
- @Override
- public Addon getAddon() {
- if (addon == null) {
- addon = new Limits();
- }
- return addon;
- }
-}
+package world.bentobox.limits;
+
+
+import world.bentobox.bentobox.api.addons.Addon;
+import world.bentobox.bentobox.api.addons.Pladdon;
+
+
+public class LimitsPladdon extends Pladdon {
+
+ private Addon addon;
+
+ @Override
+ public Addon getAddon() {
+ if (addon == null) {
+ addon = new Limits();
+ }
+ return addon;
+ }
+}
diff --git a/src/main/java/world/bentobox/limits/Settings.java b/src/main/java/world/bentobox/limits/Settings.java
index dea309d..b190faf 100644
--- a/src/main/java/world/bentobox/limits/Settings.java
+++ b/src/main/java/world/bentobox/limits/Settings.java
@@ -1,174 +1,174 @@
-package world.bentobox.limits;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import org.bukkit.Material;
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.entity.EntityType;
-
-public class Settings {
-
- enum GeneralGroup {
- ANIMALS, MOBS
- }
-
- private final Map general = new EnumMap<>(GeneralGroup.class);
- private final Map limits = new EnumMap<>(EntityType.class);
- private final Map> groupLimits = new EnumMap<>(EntityType.class);
- private final List gameModes;
- private final boolean asyncGolums;
- private static final List DISALLOWED = Arrays.asList(
- EntityType.TNT,
- EntityType.EVOKER_FANGS,
- EntityType.LLAMA_SPIT,
- EntityType.DRAGON_FIREBALL,
- EntityType.AREA_EFFECT_CLOUD,
- EntityType.END_CRYSTAL,
- EntityType.SMALL_FIREBALL,
- EntityType.FIREBALL,
- EntityType.EXPERIENCE_BOTTLE,
- EntityType.EXPERIENCE_ORB,
- EntityType.SHULKER_BULLET,
- EntityType.WITHER_SKULL,
- EntityType.TRIDENT,
- EntityType.ARROW,
- EntityType.SPECTRAL_ARROW,
- EntityType.SNOWBALL,
- EntityType.EGG,
- EntityType.LEASH_KNOT,
- EntityType.GIANT,
- EntityType.ENDER_PEARL,
- EntityType.ENDER_DRAGON,
- EntityType.ITEM_FRAME,
- EntityType.PAINTING);
-
- public Settings(Limits addon) {
-
- // GameModes
- gameModes = addon.getConfig().getStringList("gamemodes");
-
- ConfigurationSection el = addon.getConfig().getConfigurationSection("entitylimits");
- if (el != null) {
- for (String key : el.getKeys(false)) {
- if (key.equalsIgnoreCase("ANIMALS")) {
- general.put(GeneralGroup.ANIMALS, el.getInt(key, 0));
- } else if (key.equalsIgnoreCase("MOBS")) {
- general.put(GeneralGroup.MOBS, el.getInt(key, 0));
- } else {
- EntityType type = getType(key);
- if (type != null) {
- if (DISALLOWED.contains(type)) {
- addon.logError("Entity type: " + key + " is not supported - skipping...");
- } else {
- limits.put(type, el.getInt(key, 0));
- }
- } else {
- addon.logError("Unknown entity type: " + key + " - skipping...");
- }
- }
- }
- }
- // Async Golums
- asyncGolums = addon.getConfig().getBoolean("async-golums", true);
-
- addon.log("Entity limits:");
- limits.entrySet().stream().map(e -> "Limit " + e.getKey().toString() + " to " + e.getValue()).forEach(addon::log);
-
- //group limits
- el = addon.getConfig().getConfigurationSection("entitygrouplimits");
- if (el != null) {
- for (String name : el.getKeys(false)) {
- int limit = el.getInt(name + ".limit");
- String iconName = el.getString(name + ".icon", "BARRIER");
- Material icon = Material.BARRIER;
- try {
- icon = Material.valueOf(iconName.toUpperCase(Locale.ENGLISH));
- } catch (Exception e) {
- addon.logError("Invalid group icon name: " + iconName + ". Use a Bukkit Material.");
- icon = Material.BARRIER;
- }
- Set entities = el.getStringList(name + ".entities").stream().map(s -> {
- EntityType type = getType(s);
- if (type != null) {
- if (DISALLOWED.contains(type)) {
- addon.logError("Entity type: " + s + " is not supported - skipping...");
- } else {
- return type;
- }
- } else {
- addon.logError("Unknown entity type: " + s + " - skipping...");
- }
- return null;
- }).filter(Objects::nonNull).collect(Collectors.toCollection(LinkedHashSet::new));
- if (entities.isEmpty())
- continue;
- EntityGroup group = new EntityGroup(name, entities, limit, icon);
- entities.forEach(e -> {
- List groups = groupLimits.getOrDefault(e, new ArrayList<>());
- groups.add(group);
- groupLimits.put(e, groups);
- });
- }
- }
-
- addon.log("Entity group limits:");
- getGroupLimitDefinitions().stream().map(e -> "Limit " + e.getName() + " (" + e.getTypes().stream().map(Enum::name).collect(Collectors.joining(", ")) + ") to " + e.getLimit()).forEach(addon::log);
- }
-
- private EntityType getType(String key) {
- return Arrays.stream(EntityType.values()).filter(v -> v.name().equalsIgnoreCase(key)).findFirst().orElse(null);
- }
-
- /**
- * @return the entity limits
- */
- public Map getLimits() {
- return Collections.unmodifiableMap(limits);
- }
-
- /**
- * @return the group limits
- */
- public Map> getGroupLimits() {
- return groupLimits;
- }
-
- /**
- * @return the group limit definitions
- */
- public List getGroupLimitDefinitions() {
- return groupLimits.values().stream().flatMap(Collection::stream).distinct().toList();
- }
-
- /**
- * @return the gameModes
- */
- public List getGameModes() {
- return gameModes;
- }
-
- /**
- * @return the asyncGolums
- */
- public boolean isAsyncGolums() {
- return asyncGolums;
- }
-
- /**
- * @return the general coverage map
- */
- public Map getGeneral() {
- return general;
- }
-}
+package world.bentobox.limits;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.EntityType;
+
+public class Settings {
+
+ enum GeneralGroup {
+ ANIMALS, MOBS
+ }
+
+ private final Map general = new EnumMap<>(GeneralGroup.class);
+ private final Map limits = new EnumMap<>(EntityType.class);
+ private final Map> groupLimits = new EnumMap<>(EntityType.class);
+ private final List gameModes;
+ private final boolean asyncGolums;
+ private static final List DISALLOWED = Arrays.asList(
+ EntityType.TNT,
+ EntityType.EVOKER_FANGS,
+ EntityType.LLAMA_SPIT,
+ EntityType.DRAGON_FIREBALL,
+ EntityType.AREA_EFFECT_CLOUD,
+ EntityType.END_CRYSTAL,
+ EntityType.SMALL_FIREBALL,
+ EntityType.FIREBALL,
+ EntityType.EXPERIENCE_BOTTLE,
+ EntityType.EXPERIENCE_ORB,
+ EntityType.SHULKER_BULLET,
+ EntityType.WITHER_SKULL,
+ EntityType.TRIDENT,
+ EntityType.ARROW,
+ EntityType.SPECTRAL_ARROW,
+ EntityType.SNOWBALL,
+ EntityType.EGG,
+ EntityType.LEASH_KNOT,
+ EntityType.GIANT,
+ EntityType.ENDER_PEARL,
+ EntityType.ENDER_DRAGON,
+ EntityType.ITEM_FRAME,
+ EntityType.PAINTING);
+
+ public Settings(Limits addon) {
+
+ // GameModes
+ gameModes = addon.getConfig().getStringList("gamemodes");
+
+ ConfigurationSection el = addon.getConfig().getConfigurationSection("entitylimits");
+ if (el != null) {
+ for (String key : el.getKeys(false)) {
+ if (key.equalsIgnoreCase("ANIMALS")) {
+ general.put(GeneralGroup.ANIMALS, el.getInt(key, 0));
+ } else if (key.equalsIgnoreCase("MOBS")) {
+ general.put(GeneralGroup.MOBS, el.getInt(key, 0));
+ } else {
+ EntityType type = getType(key);
+ if (type != null) {
+ if (DISALLOWED.contains(type)) {
+ addon.logError("Entity type: " + key + " is not supported - skipping...");
+ } else {
+ limits.put(type, el.getInt(key, 0));
+ }
+ } else {
+ addon.logError("Unknown entity type: " + key + " - skipping...");
+ }
+ }
+ }
+ }
+ // Async Golums
+ asyncGolums = addon.getConfig().getBoolean("async-golums", true);
+
+ addon.log("Entity limits:");
+ limits.entrySet().stream().map(e -> "Limit " + e.getKey().toString() + " to " + e.getValue()).forEach(addon::log);
+
+ //group limits
+ el = addon.getConfig().getConfigurationSection("entitygrouplimits");
+ if (el != null) {
+ for (String name : el.getKeys(false)) {
+ int limit = el.getInt(name + ".limit");
+ String iconName = el.getString(name + ".icon", "BARRIER");
+ Material icon = Material.BARRIER;
+ try {
+ icon = Material.valueOf(iconName.toUpperCase(Locale.ENGLISH));
+ } catch (Exception e) {
+ addon.logError("Invalid group icon name: " + iconName + ". Use a Bukkit Material.");
+ icon = Material.BARRIER;
+ }
+ Set entities = el.getStringList(name + ".entities").stream().map(s -> {
+ EntityType type = getType(s);
+ if (type != null) {
+ if (DISALLOWED.contains(type)) {
+ addon.logError("Entity type: " + s + " is not supported - skipping...");
+ } else {
+ return type;
+ }
+ } else {
+ addon.logError("Unknown entity type: " + s + " - skipping...");
+ }
+ return null;
+ }).filter(Objects::nonNull).collect(Collectors.toCollection(LinkedHashSet::new));
+ if (entities.isEmpty())
+ continue;
+ EntityGroup group = new EntityGroup(name, entities, limit, icon);
+ entities.forEach(e -> {
+ List groups = groupLimits.getOrDefault(e, new ArrayList<>());
+ groups.add(group);
+ groupLimits.put(e, groups);
+ });
+ }
+ }
+
+ addon.log("Entity group limits:");
+ getGroupLimitDefinitions().stream().map(e -> "Limit " + e.getName() + " (" + e.getTypes().stream().map(Enum::name).collect(Collectors.joining(", ")) + ") to " + e.getLimit()).forEach(addon::log);
+ }
+
+ private EntityType getType(String key) {
+ return Arrays.stream(EntityType.values()).filter(v -> v.name().equalsIgnoreCase(key)).findFirst().orElse(null);
+ }
+
+ /**
+ * @return the entity limits
+ */
+ public Map getLimits() {
+ return Collections.unmodifiableMap(limits);
+ }
+
+ /**
+ * @return the group limits
+ */
+ public Map> getGroupLimits() {
+ return groupLimits;
+ }
+
+ /**
+ * @return the group limit definitions
+ */
+ public List getGroupLimitDefinitions() {
+ return groupLimits.values().stream().flatMap(Collection::stream).distinct().toList();
+ }
+
+ /**
+ * @return the gameModes
+ */
+ public List getGameModes() {
+ return gameModes;
+ }
+
+ /**
+ * @return the asyncGolums
+ */
+ public boolean isAsyncGolums() {
+ return asyncGolums;
+ }
+
+ /**
+ * @return the general coverage map
+ */
+ public Map getGeneral() {
+ return general;
+ }
+}
diff --git a/src/main/java/world/bentobox/limits/calculators/Pipeliner.java b/src/main/java/world/bentobox/limits/calculators/Pipeliner.java
index 5d819cf..0a316af 100644
--- a/src/main/java/world/bentobox/limits/calculators/Pipeliner.java
+++ b/src/main/java/world/bentobox/limits/calculators/Pipeliner.java
@@ -1,151 +1,151 @@
-package world.bentobox.limits.calculators;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import org.bukkit.Bukkit;
-import org.bukkit.scheduler.BukkitTask;
-
-import world.bentobox.bentobox.BentoBox;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.calculators.Results.Result;
-
-/**
- * A pipeliner that will process one island at a time
- * @author tastybento
- *
- */
-public class Pipeliner {
-
- private static final int START_DURATION = 10; // 10 seconds
- private static final int CONCURRENT_COUNTS = 1;
- private final Queue toProcessQueue;
- private final Map inProcessQueue;
- private final BukkitTask task;
- private final Limits addon;
- private long time;
- private long count;
-
- /**
- * Construct the pipeliner
- */
- public Pipeliner(Limits addon) {
- this.addon = addon;
- toProcessQueue = new ConcurrentLinkedQueue<>();
- inProcessQueue = new HashMap<>();
- // Loop continuously - check every tick if there is an island to scan
- task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> {
- if (!BentoBox.getInstance().isEnabled()) {
- cancel();
- return;
- }
- // Complete the current to Process queue first
- if (!inProcessQueue.isEmpty() || toProcessQueue.isEmpty()) return;
- for (int j = 0; j < CONCURRENT_COUNTS && !toProcessQueue.isEmpty(); j++) {
- RecountCalculator iD = toProcessQueue.poll();
- // Ignore deleted or unonwed islands
- if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) {
- inProcessQueue.put(iD, System.currentTimeMillis());
- // Start the scanning of a island with the first chunk
- scanIsland(iD);
- }
- }
- }, 1L, 10L);
- }
-
- private void cancel() {
- task.cancel();
- }
-
- /**
- * @return number of islands currently in the queue or in process
- */
- public int getIslandsInQueue() {
- return inProcessQueue.size() + toProcessQueue.size();
- }
-
- /**
- * Scans one chunk of an island and adds the results to a results object
- * @param iD
- */
- private void scanIsland(RecountCalculator iD) {
- if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned() || task.isCancelled()) {
- // Island is deleted, so finish early with nothing
- inProcessQueue.remove(iD);
- iD.getR().complete(null);
- return;
- }
- iD.scanIsland(this);
- }
-
-
- /**
- * Adds an island to the scanning queue but only if the island is not already in the queue
- * @param island - the island to scan
- * @return CompletableFuture of the results. Results will be null if the island is already in the queue
- */
- public CompletableFuture addIsland(Island island) {
- // Check if queue already contains island
- if (inProcessQueue.keySet().parallelStream().map(RecountCalculator::getIsland).anyMatch(island::equals)
- || toProcessQueue.parallelStream().map(RecountCalculator::getIsland).anyMatch(island::equals)) {
- return CompletableFuture.completedFuture(new Results(Result.IN_PROGRESS));
- }
- return addToQueue(island);
- }
-
- private CompletableFuture addToQueue(Island island) {
- CompletableFuture r = new CompletableFuture<>();
- toProcessQueue.add(new RecountCalculator(addon, island, r));
- count++;
- return r;
- }
-
- /**
- * Get the average time it takes to run a level check
- * @return the average time in seconds
- */
- public int getTime() {
- return time == 0 || count == 0 ? START_DURATION : (int)((double)time/count/1000);
- }
-
- /**
- * Submit how long a level check took
- * @param time the time to set
- */
- public void setTime(long time) {
- // Running average
- this.time += time;
- }
-
- /**
- * Stop the current queue.
- */
- public void stop() {
- addon.log("Stopping Level queue");
- task.cancel();
- this.inProcessQueue.clear();
- this.toProcessQueue.clear();
- }
-
- /**
- * @return the inProcessQueue
- */
- protected Map getInProcessQueue() {
- return inProcessQueue;
- }
-
- /**
- * @return the task
- */
- protected BukkitTask getTask() {
- return task;
- }
-
-
-
-
-}
+package world.bentobox.limits.calculators;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.bukkit.Bukkit;
+import org.bukkit.scheduler.BukkitTask;
+
+import world.bentobox.bentobox.BentoBox;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.calculators.Results.Result;
+
+/**
+ * A pipeliner that will process one island at a time
+ * @author tastybento
+ *
+ */
+public class Pipeliner {
+
+ private static final int START_DURATION = 10; // 10 seconds
+ private static final int CONCURRENT_COUNTS = 1;
+ private final Queue toProcessQueue;
+ private final Map inProcessQueue;
+ private final BukkitTask task;
+ private final Limits addon;
+ private long time;
+ private long count;
+
+ /**
+ * Construct the pipeliner
+ */
+ public Pipeliner(Limits addon) {
+ this.addon = addon;
+ toProcessQueue = new ConcurrentLinkedQueue<>();
+ inProcessQueue = new HashMap<>();
+ // Loop continuously - check every tick if there is an island to scan
+ task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> {
+ if (!BentoBox.getInstance().isEnabled()) {
+ cancel();
+ return;
+ }
+ // Complete the current to Process queue first
+ if (!inProcessQueue.isEmpty() || toProcessQueue.isEmpty()) return;
+ for (int j = 0; j < CONCURRENT_COUNTS && !toProcessQueue.isEmpty(); j++) {
+ RecountCalculator iD = toProcessQueue.poll();
+ // Ignore deleted or unonwed islands
+ if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) {
+ inProcessQueue.put(iD, System.currentTimeMillis());
+ // Start the scanning of a island with the first chunk
+ scanIsland(iD);
+ }
+ }
+ }, 1L, 10L);
+ }
+
+ private void cancel() {
+ task.cancel();
+ }
+
+ /**
+ * @return number of islands currently in the queue or in process
+ */
+ public int getIslandsInQueue() {
+ return inProcessQueue.size() + toProcessQueue.size();
+ }
+
+ /**
+ * Scans one chunk of an island and adds the results to a results object
+ * @param iD
+ */
+ private void scanIsland(RecountCalculator iD) {
+ if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned() || task.isCancelled()) {
+ // Island is deleted, so finish early with nothing
+ inProcessQueue.remove(iD);
+ iD.getR().complete(null);
+ return;
+ }
+ iD.scanIsland(this);
+ }
+
+
+ /**
+ * Adds an island to the scanning queue but only if the island is not already in the queue
+ * @param island - the island to scan
+ * @return CompletableFuture of the results. Results will be null if the island is already in the queue
+ */
+ public CompletableFuture addIsland(Island island) {
+ // Check if queue already contains island
+ if (inProcessQueue.keySet().parallelStream().map(RecountCalculator::getIsland).anyMatch(island::equals)
+ || toProcessQueue.parallelStream().map(RecountCalculator::getIsland).anyMatch(island::equals)) {
+ return CompletableFuture.completedFuture(new Results(Result.IN_PROGRESS));
+ }
+ return addToQueue(island);
+ }
+
+ private CompletableFuture addToQueue(Island island) {
+ CompletableFuture r = new CompletableFuture<>();
+ toProcessQueue.add(new RecountCalculator(addon, island, r));
+ count++;
+ return r;
+ }
+
+ /**
+ * Get the average time it takes to run a level check
+ * @return the average time in seconds
+ */
+ public int getTime() {
+ return time == 0 || count == 0 ? START_DURATION : (int)((double)time/count/1000);
+ }
+
+ /**
+ * Submit how long a level check took
+ * @param time the time to set
+ */
+ public void setTime(long time) {
+ // Running average
+ this.time += time;
+ }
+
+ /**
+ * Stop the current queue.
+ */
+ public void stop() {
+ addon.log("Stopping Level queue");
+ task.cancel();
+ this.inProcessQueue.clear();
+ this.toProcessQueue.clear();
+ }
+
+ /**
+ * @return the inProcessQueue
+ */
+ protected Map getInProcessQueue() {
+ return inProcessQueue;
+ }
+
+ /**
+ * @return the task
+ */
+ protected BukkitTask getTask() {
+ return task;
+ }
+
+
+
+
+}
diff --git a/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java b/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java
index 92f6293..82229dc 100644
--- a/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java
+++ b/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java
@@ -1,359 +1,359 @@
-package world.bentobox.limits.calculators;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Queue;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import org.bukkit.Bukkit;
-import org.bukkit.Chunk;
-import org.bukkit.ChunkSnapshot;
-import org.bukkit.Location;
-import org.bukkit.Material;
-import org.bukkit.Tag;
-import org.bukkit.World;
-import org.bukkit.World.Environment;
-import org.bukkit.block.data.BlockData;
-import org.bukkit.block.data.type.Slab;
-import org.bukkit.scheduler.BukkitTask;
-
-import world.bentobox.bentobox.BentoBox;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.bentobox.util.Pair;
-import world.bentobox.bentobox.util.Util;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.calculators.Results.Result;
-import world.bentobox.limits.listeners.BlockLimitsListener;
-import world.bentobox.limits.objects.IslandBlockCount;
-
-/**
- * Counter for limits
- * @author tastybento
- *
- */
-public class RecountCalculator {
- public static final long MAX_AMOUNT = 10000;
- private static final int CHUNKS_TO_SCAN = 100;
- private static final int CALCULATION_TIMEOUT = 5; // Minutes
-
-
- private final Limits addon;
- private final Queue> chunksToCheck;
- private final Island island;
- private final CompletableFuture r;
-
-
- private final Results results;
- private final Map worlds = new EnumMap<>(Environment.class);
- private final List stackedBlocks = new ArrayList<>();
- private BukkitTask finishTask;
- private final BlockLimitsListener bll;
- private final World world;
- private IslandBlockCount ibc;
-
-
- /**
- * Constructor to get the level for an island
- * @param addon - addon
- * @param island - the island to scan
- * @param r - completable result that will be completed when the calculation is complete
- */
- public RecountCalculator(Limits addon, Island island, CompletableFuture r) {
- this.addon = addon;
- this.bll = addon.getBlockLimitListener();
- this.island = island;
- this.ibc = bll.getIsland(Objects.requireNonNull(island).getUniqueId());
- this.r = r;
- results = new Results();
- chunksToCheck = getChunksToScan(island);
- // Set up the worlds
- this.world = Objects.requireNonNull(Util.getWorld(island.getWorld()));
- worlds.put(Environment.NORMAL, world);
- boolean isNether = addon.getPlugin().getIWM().isNetherGenerate(world) && addon.getPlugin().getIWM().isNetherIslands(world);
- boolean isEnd = addon.getPlugin().getIWM().isEndGenerate(world) && addon.getPlugin().getIWM().isEndIslands(world);
-
- // Nether
- if (isNether) {
- World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
- if (nether != null) {
- worlds.put(Environment.NETHER, nether);
- }
- }
- // End
- if (isEnd) {
- World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld());
- if (end != null) {
- worlds.put(Environment.THE_END, end);
- }
- }
- }
-
- private void checkBlock(BlockData b) {
- Material md = bll.fixMaterial(b);
- // md is limited
- if (bll.getMaterialLimits(world, island.getUniqueId()).containsKey(md)) {
- results.mdCount.add(md);
- }
- }
- /**
- * Get a set of all the chunks in island
- * @param island - island
- * @return - set of pairs of x,z coordinates to check
- */
- private Queue> getChunksToScan(Island island) {
- Queue> chunkQueue = new ConcurrentLinkedQueue<>();
- 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) {
- chunkQueue.add(new Pair<>(x >> 4, z >> 4));
- }
- }
- return chunkQueue;
- }
-
-
- /**
- * @return the island
- */
- public Island getIsland() {
- return island;
- }
-
- /**
- * Get the completable result for this calculation
- * @return the r
- */
- public CompletableFuture getR() {
- return r;
- }
-
- /**
- * @return the results
- */
- public Results getResults() {
- return results;
- }
-
- /**
- * Get a chunk async
- * @param env - the environment
- * @param x - chunk x coordinate
- * @param z - chunk z coordinate
- * @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether
- */
- private CompletableFuture> getWorldChunk(Environment env, Queue> pairList) {
- if (worlds.containsKey(env)) {
- CompletableFuture> r2 = new CompletableFuture<>();
- List chunkList = new ArrayList<>();
- World world = worlds.get(env);
- // Get the chunk, and then coincidentally check the RoseStacker
- loadChunks(r2, world, pairList, chunkList);
- return r2;
- }
- return CompletableFuture.completedFuture(Collections.emptyList());
- }
-
- private void loadChunks(CompletableFuture> r2, World world, Queue> pairList,
- List chunkList) {
- if (pairList.isEmpty()) {
- r2.complete(chunkList);
- return;
- }
- Pair p = pairList.poll();
- Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> {
- if (chunk != null) {
- chunkList.add(chunk);
- // roseStackerCheck(chunk);
- }
- loadChunks(r2, world, pairList, chunkList); // Iteration
- });
- }
- /*
- private void roseStackerCheck(Chunk chunk) {
- if (addon.isRoseStackersEnabled()) {
- RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> {
- // Blocks below sea level can be scored differently
- boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight;
- // Check block once because the base block will be counted in the chunk snapshot
- for (int _x = 0; _x < e.getStackSize() - 1; _x++) {
- checkBlock(e.getBlock().getType(), belowSeaLevel);
- }
- });
- }
- }
- */
- /**
- * Count the blocks on the island
- * @param chunk chunk to scan
- */
- private void scanAsync(Chunk chunk) {
- ChunkSnapshot chunkSnapshot = chunk.getChunkSnapshot();
- 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 (chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || chunkSnapshot.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 (chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || chunkSnapshot.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
- continue;
- }
- // Only count to the highest block in the world for some optimization
- for (int y = chunk.getWorld().getMinHeight(); y < chunk.getWorld().getMaxHeight(); y++) {
- BlockData blockData = chunkSnapshot.getBlockData(x, y, z);
- // Slabs can be doubled, so check them twice
- if (Tag.SLABS.isTagged(blockData.getMaterial())) {
- Slab slab = (Slab)blockData;
- if (slab.getType().equals(Slab.Type.DOUBLE)) {
- checkBlock(blockData);
- }
- }
- // Hook for Wild Stackers (Blocks Only) - this has to use the real chunk
- /*
- if (addon.isStackersEnabled() && blockData.getMaterial() == Material.CAULDRON) {
- stackedBlocks.add(new Location(chunk.getWorld(), x + chunkSnapshot.getX() * 16,y,z + chunkSnapshot.getZ() * 16));
- }
- */
- // Add the value of the block's material
- checkBlock(blockData);
- }
- }
- }
- }
-
- /**
- * Scan the chunk chests and count the blocks
- * @param chunks - the chunk to scan
- * @return future that completes when the scan is done and supplies a boolean that will be true if the scan was successful, false if not
- */
- private CompletableFuture scanChunk(List chunks) {
- // If the chunk hasn't been generated, return
- if (chunks == null || chunks.isEmpty()) {
- return CompletableFuture.completedFuture(false);
- }
- // Count blocks in chunk
- CompletableFuture result = new CompletableFuture<>();
-
- Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> {
- chunks.forEach(chunk -> scanAsync(chunk));
- Bukkit.getScheduler().runTask(addon.getPlugin(),() -> result.complete(true));
- });
- return result;
- }
-
- /**
- * Scan the next chunk on the island
- * @return completable boolean future that will be true if more chunks are left to be scanned, and false if not
- */
- public CompletableFuture scanNextChunk() {
- if (chunksToCheck.isEmpty()) {
- addon.logError("Unexpected: no chunks to scan!");
- // This should not be needed, but just in case
- return CompletableFuture.completedFuture(false);
- }
- // Retrieve and remove from the queue
- Queue> pairList = new ConcurrentLinkedQueue<>();
- int i = 0;
- while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) {
- pairList.add(chunksToCheck.poll());
- }
- Queue> endPairList = new ConcurrentLinkedQueue<>(pairList);
- Queue> netherPairList = new ConcurrentLinkedQueue<>(pairList);
- // Set up the result
- CompletableFuture result = new CompletableFuture<>();
- // Get chunks and scan
- // Get chunks and scan
- getWorldChunk(Environment.THE_END, endPairList).thenAccept(endChunks ->
- scanChunk(endChunks).thenAccept(b ->
- getWorldChunk(Environment.NETHER, netherPairList).thenAccept(netherChunks ->
- scanChunk(netherChunks).thenAccept(b2 ->
- getWorldChunk(Environment.NORMAL, pairList).thenAccept(normalChunks ->
- scanChunk(normalChunks).thenAccept(b3 ->
- // Complete the result now that all chunks have been scanned
- result.complete(!chunksToCheck.isEmpty()))))
- )
- )
- );
-
- return result;
- }
-
- /**
- * Finalizes the calculations and makes the report
- */
- public void tidyUp() {
- // Finalize calculations
- if (ibc == null) {
- ibc = new IslandBlockCount(island.getUniqueId(), addon.getPlugin().getIWM().getAddon(world).map(a -> a.getDescription().getName()).orElse("default"));
- }
- ibc.getBlockCounts().clear();
- results.getMdCount().forEach(ibc::add);
- bll.setIsland(island.getUniqueId(), ibc);
- //Bukkit.getScheduler().runTask(addon.getPlugin(), () -> sender.sendMessage("admin.limits.calc.finished"));
-
- // All done.
- }
-
- public void scanIsland(Pipeliner pipeliner) {
- // Scan the next chunk
- scanNextChunk().thenAccept(r -> {
- if (!Bukkit.isPrimaryThread()) {
- addon.getPlugin().logError("scanChunk not on Primary Thread!");
- }
- // Timeout check
- if (System.currentTimeMillis() - pipeliner.getInProcessQueue().get(this) > CALCULATION_TIMEOUT * 60000) {
- // Done
- pipeliner.getInProcessQueue().remove(this);
- getR().complete(new Results(Result.TIMEOUT));
- addon.logError("Level calculation timed out after " + CALCULATION_TIMEOUT + "m for island: " + getIsland());
- return;
- }
- if (Boolean.TRUE.equals(r) && !pipeliner.getTask().isCancelled()) {
- // scanNextChunk returns true if there are more chunks to scan
- scanIsland(pipeliner);
- } else {
- // Done
- pipeliner.getInProcessQueue().remove(this);
- // Chunk finished
- // This was the last chunk
- handleStackedBlocks();
- long checkTime = System.currentTimeMillis();
- finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> {
- // Check every half second if all the chests and stacks have been cleared
- if ((stackedBlocks.isEmpty()) || System.currentTimeMillis() - checkTime > MAX_AMOUNT) {
- this.tidyUp();
- this.getR().complete(getResults());
- finishTask.cancel();
- }
- }, 0, 10L);
-
- }
- });
- }
-
- private void handleStackedBlocks() {
- // Deal with any stacked blocks
- /*
- Iterator it = stackedBlocks.iterator();
- while (it.hasNext()) {
- Location v = it.next();
- Util.getChunkAtAsync(v).thenAccept(c -> {
- Block cauldronBlock = v.getBlock();
- boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight;
- if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) {
- StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock);
- int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock);
- for (int _x = 0; _x < barrelAmt; _x++) {
- checkBlock(barrel.getType(), belowSeaLevel);
- }
- }
- it.remove();
- });
- }
- */
- }
-}
+package world.bentobox.limits.calculators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Chunk;
+import org.bukkit.ChunkSnapshot;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.Tag;
+import org.bukkit.World;
+import org.bukkit.World.Environment;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.block.data.type.Slab;
+import org.bukkit.scheduler.BukkitTask;
+
+import world.bentobox.bentobox.BentoBox;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.util.Pair;
+import world.bentobox.bentobox.util.Util;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.calculators.Results.Result;
+import world.bentobox.limits.listeners.BlockLimitsListener;
+import world.bentobox.limits.objects.IslandBlockCount;
+
+/**
+ * Counter for limits
+ * @author tastybento
+ *
+ */
+public class RecountCalculator {
+ public static final long MAX_AMOUNT = 10000;
+ private static final int CHUNKS_TO_SCAN = 100;
+ private static final int CALCULATION_TIMEOUT = 5; // Minutes
+
+
+ private final Limits addon;
+ private final Queue> chunksToCheck;
+ private final Island island;
+ private final CompletableFuture r;
+
+
+ private final Results results;
+ private final Map worlds = new EnumMap<>(Environment.class);
+ private final List stackedBlocks = new ArrayList<>();
+ private BukkitTask finishTask;
+ private final BlockLimitsListener bll;
+ private final World world;
+ private IslandBlockCount ibc;
+
+
+ /**
+ * Constructor to get the level for an island
+ * @param addon - addon
+ * @param island - the island to scan
+ * @param r - completable result that will be completed when the calculation is complete
+ */
+ public RecountCalculator(Limits addon, Island island, CompletableFuture r) {
+ this.addon = addon;
+ this.bll = addon.getBlockLimitListener();
+ this.island = island;
+ this.ibc = bll.getIsland(Objects.requireNonNull(island).getUniqueId());
+ this.r = r;
+ results = new Results();
+ chunksToCheck = getChunksToScan(island);
+ // Set up the worlds
+ this.world = Objects.requireNonNull(Util.getWorld(island.getWorld()));
+ worlds.put(Environment.NORMAL, world);
+ boolean isNether = addon.getPlugin().getIWM().isNetherGenerate(world) && addon.getPlugin().getIWM().isNetherIslands(world);
+ boolean isEnd = addon.getPlugin().getIWM().isEndGenerate(world) && addon.getPlugin().getIWM().isEndIslands(world);
+
+ // Nether
+ if (isNether) {
+ World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
+ if (nether != null) {
+ worlds.put(Environment.NETHER, nether);
+ }
+ }
+ // End
+ if (isEnd) {
+ World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld());
+ if (end != null) {
+ worlds.put(Environment.THE_END, end);
+ }
+ }
+ }
+
+ private void checkBlock(BlockData b) {
+ Material md = bll.fixMaterial(b);
+ // md is limited
+ if (bll.getMaterialLimits(world, island.getUniqueId()).containsKey(md)) {
+ results.mdCount.add(md);
+ }
+ }
+ /**
+ * Get a set of all the chunks in island
+ * @param island - island
+ * @return - set of pairs of x,z coordinates to check
+ */
+ private Queue> getChunksToScan(Island island) {
+ Queue> chunkQueue = new ConcurrentLinkedQueue<>();
+ 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) {
+ chunkQueue.add(new Pair<>(x >> 4, z >> 4));
+ }
+ }
+ return chunkQueue;
+ }
+
+
+ /**
+ * @return the island
+ */
+ public Island getIsland() {
+ return island;
+ }
+
+ /**
+ * Get the completable result for this calculation
+ * @return the r
+ */
+ public CompletableFuture getR() {
+ return r;
+ }
+
+ /**
+ * @return the results
+ */
+ public Results getResults() {
+ return results;
+ }
+
+ /**
+ * Get a chunk async
+ * @param env - the environment
+ * @param x - chunk x coordinate
+ * @param z - chunk z coordinate
+ * @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether
+ */
+ private CompletableFuture> getWorldChunk(Environment env, Queue> pairList) {
+ if (worlds.containsKey(env)) {
+ CompletableFuture> r2 = new CompletableFuture<>();
+ List chunkList = new ArrayList<>();
+ World world = worlds.get(env);
+ // Get the chunk, and then coincidentally check the RoseStacker
+ loadChunks(r2, world, pairList, chunkList);
+ return r2;
+ }
+ return CompletableFuture.completedFuture(Collections.emptyList());
+ }
+
+ private void loadChunks(CompletableFuture> r2, World world, Queue> pairList,
+ List chunkList) {
+ if (pairList.isEmpty()) {
+ r2.complete(chunkList);
+ return;
+ }
+ Pair p = pairList.poll();
+ Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> {
+ if (chunk != null) {
+ chunkList.add(chunk);
+ // roseStackerCheck(chunk);
+ }
+ loadChunks(r2, world, pairList, chunkList); // Iteration
+ });
+ }
+ /*
+ private void roseStackerCheck(Chunk chunk) {
+ if (addon.isRoseStackersEnabled()) {
+ RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> {
+ // Blocks below sea level can be scored differently
+ boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight;
+ // Check block once because the base block will be counted in the chunk snapshot
+ for (int _x = 0; _x < e.getStackSize() - 1; _x++) {
+ checkBlock(e.getBlock().getType(), belowSeaLevel);
+ }
+ });
+ }
+ }
+ */
+ /**
+ * Count the blocks on the island
+ * @param chunk chunk to scan
+ */
+ private void scanAsync(Chunk chunk) {
+ ChunkSnapshot chunkSnapshot = chunk.getChunkSnapshot();
+ 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 (chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || chunkSnapshot.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 (chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || chunkSnapshot.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
+ continue;
+ }
+ // Only count to the highest block in the world for some optimization
+ for (int y = chunk.getWorld().getMinHeight(); y < chunk.getWorld().getMaxHeight(); y++) {
+ BlockData blockData = chunkSnapshot.getBlockData(x, y, z);
+ // Slabs can be doubled, so check them twice
+ if (Tag.SLABS.isTagged(blockData.getMaterial())) {
+ Slab slab = (Slab)blockData;
+ if (slab.getType().equals(Slab.Type.DOUBLE)) {
+ checkBlock(blockData);
+ }
+ }
+ // Hook for Wild Stackers (Blocks Only) - this has to use the real chunk
+ /*
+ if (addon.isStackersEnabled() && blockData.getMaterial() == Material.CAULDRON) {
+ stackedBlocks.add(new Location(chunk.getWorld(), x + chunkSnapshot.getX() * 16,y,z + chunkSnapshot.getZ() * 16));
+ }
+ */
+ // Add the value of the block's material
+ checkBlock(blockData);
+ }
+ }
+ }
+ }
+
+ /**
+ * Scan the chunk chests and count the blocks
+ * @param chunks - the chunk to scan
+ * @return future that completes when the scan is done and supplies a boolean that will be true if the scan was successful, false if not
+ */
+ private CompletableFuture scanChunk(List chunks) {
+ // If the chunk hasn't been generated, return
+ if (chunks == null || chunks.isEmpty()) {
+ return CompletableFuture.completedFuture(false);
+ }
+ // Count blocks in chunk
+ CompletableFuture result = new CompletableFuture<>();
+
+ Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> {
+ chunks.forEach(chunk -> scanAsync(chunk));
+ Bukkit.getScheduler().runTask(addon.getPlugin(),() -> result.complete(true));
+ });
+ return result;
+ }
+
+ /**
+ * Scan the next chunk on the island
+ * @return completable boolean future that will be true if more chunks are left to be scanned, and false if not
+ */
+ public CompletableFuture scanNextChunk() {
+ if (chunksToCheck.isEmpty()) {
+ addon.logError("Unexpected: no chunks to scan!");
+ // This should not be needed, but just in case
+ return CompletableFuture.completedFuture(false);
+ }
+ // Retrieve and remove from the queue
+ Queue> pairList = new ConcurrentLinkedQueue<>();
+ int i = 0;
+ while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) {
+ pairList.add(chunksToCheck.poll());
+ }
+ Queue> endPairList = new ConcurrentLinkedQueue<>(pairList);
+ Queue> netherPairList = new ConcurrentLinkedQueue<>(pairList);
+ // Set up the result
+ CompletableFuture result = new CompletableFuture<>();
+ // Get chunks and scan
+ // Get chunks and scan
+ getWorldChunk(Environment.THE_END, endPairList).thenAccept(endChunks ->
+ scanChunk(endChunks).thenAccept(b ->
+ getWorldChunk(Environment.NETHER, netherPairList).thenAccept(netherChunks ->
+ scanChunk(netherChunks).thenAccept(b2 ->
+ getWorldChunk(Environment.NORMAL, pairList).thenAccept(normalChunks ->
+ scanChunk(normalChunks).thenAccept(b3 ->
+ // Complete the result now that all chunks have been scanned
+ result.complete(!chunksToCheck.isEmpty()))))
+ )
+ )
+ );
+
+ return result;
+ }
+
+ /**
+ * Finalizes the calculations and makes the report
+ */
+ public void tidyUp() {
+ // Finalize calculations
+ if (ibc == null) {
+ ibc = new IslandBlockCount(island.getUniqueId(), addon.getPlugin().getIWM().getAddon(world).map(a -> a.getDescription().getName()).orElse("default"));
+ }
+ ibc.getBlockCounts().clear();
+ results.getMdCount().forEach(ibc::add);
+ bll.setIsland(island.getUniqueId(), ibc);
+ //Bukkit.getScheduler().runTask(addon.getPlugin(), () -> sender.sendMessage("admin.limits.calc.finished"));
+
+ // All done.
+ }
+
+ public void scanIsland(Pipeliner pipeliner) {
+ // Scan the next chunk
+ scanNextChunk().thenAccept(r -> {
+ if (!Bukkit.isPrimaryThread()) {
+ addon.getPlugin().logError("scanChunk not on Primary Thread!");
+ }
+ // Timeout check
+ if (System.currentTimeMillis() - pipeliner.getInProcessQueue().get(this) > CALCULATION_TIMEOUT * 60000) {
+ // Done
+ pipeliner.getInProcessQueue().remove(this);
+ getR().complete(new Results(Result.TIMEOUT));
+ addon.logError("Level calculation timed out after " + CALCULATION_TIMEOUT + "m for island: " + getIsland());
+ return;
+ }
+ if (Boolean.TRUE.equals(r) && !pipeliner.getTask().isCancelled()) {
+ // scanNextChunk returns true if there are more chunks to scan
+ scanIsland(pipeliner);
+ } else {
+ // Done
+ pipeliner.getInProcessQueue().remove(this);
+ // Chunk finished
+ // This was the last chunk
+ handleStackedBlocks();
+ long checkTime = System.currentTimeMillis();
+ finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> {
+ // Check every half second if all the chests and stacks have been cleared
+ if ((stackedBlocks.isEmpty()) || System.currentTimeMillis() - checkTime > MAX_AMOUNT) {
+ this.tidyUp();
+ this.getR().complete(getResults());
+ finishTask.cancel();
+ }
+ }, 0, 10L);
+
+ }
+ });
+ }
+
+ private void handleStackedBlocks() {
+ // Deal with any stacked blocks
+ /*
+ Iterator it = stackedBlocks.iterator();
+ while (it.hasNext()) {
+ Location v = it.next();
+ Util.getChunkAtAsync(v).thenAccept(c -> {
+ Block cauldronBlock = v.getBlock();
+ boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight;
+ if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) {
+ StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock);
+ int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock);
+ for (int _x = 0; _x < barrelAmt; _x++) {
+ checkBlock(barrel.getType(), belowSeaLevel);
+ }
+ }
+ it.remove();
+ });
+ }
+ */
+ }
+}
diff --git a/src/main/java/world/bentobox/limits/calculators/Results.java b/src/main/java/world/bentobox/limits/calculators/Results.java
index b2c01e0..9d07619 100644
--- a/src/main/java/world/bentobox/limits/calculators/Results.java
+++ b/src/main/java/world/bentobox/limits/calculators/Results.java
@@ -1,57 +1,57 @@
-package world.bentobox.limits.calculators;
-
-import org.bukkit.Material;
-import org.bukkit.entity.EntityType;
-
-import com.google.common.collect.HashMultiset;
-import com.google.common.collect.Multiset;
-
-public class Results {
- public enum Result {
- /**
- * A level calc is already in progress
- */
- IN_PROGRESS,
- /**
- * Results will be available
- */
- AVAILABLE,
- /**
- * Result if calculation timed out
- */
- TIMEOUT
- }
- final Multiset mdCount = HashMultiset.create();
- final Multiset entityCount = HashMultiset.create();
-
- final Result state;
-
- public Results(Result state) {
- this.state = state;
- }
-
- public Results() {
- this.state = Result.AVAILABLE;
- }
- /**
- * @return the mdCount
- */
- public Multiset getMdCount() {
- return mdCount;
- }
-
- /**
- * @return the state
- */
- public Result getState() {
- return state;
- }
-
- /**
- * @return the entityCount
- */
- public Multiset getEntityCount() {
- return entityCount;
- }
-
+package world.bentobox.limits.calculators;
+
+import org.bukkit.Material;
+import org.bukkit.entity.EntityType;
+
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+
+public class Results {
+ public enum Result {
+ /**
+ * A level calc is already in progress
+ */
+ IN_PROGRESS,
+ /**
+ * Results will be available
+ */
+ AVAILABLE,
+ /**
+ * Result if calculation timed out
+ */
+ TIMEOUT
+ }
+ final Multiset mdCount = HashMultiset.create();
+ final Multiset entityCount = HashMultiset.create();
+
+ final Result state;
+
+ public Results(Result state) {
+ this.state = state;
+ }
+
+ public Results() {
+ this.state = Result.AVAILABLE;
+ }
+ /**
+ * @return the mdCount
+ */
+ public Multiset getMdCount() {
+ return mdCount;
+ }
+
+ /**
+ * @return the state
+ */
+ public Result getState() {
+ return state;
+ }
+
+ /**
+ * @return the entityCount
+ */
+ public Multiset getEntityCount() {
+ return entityCount;
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/world/bentobox/limits/commands/admin/AdminCommand.java b/src/main/java/world/bentobox/limits/commands/admin/AdminCommand.java
index 263b24e..ac99e10 100644
--- a/src/main/java/world/bentobox/limits/commands/admin/AdminCommand.java
+++ b/src/main/java/world/bentobox/limits/commands/admin/AdminCommand.java
@@ -1,81 +1,81 @@
-package world.bentobox.limits.commands.admin;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-
-import world.bentobox.bentobox.api.addons.GameModeAddon;
-import world.bentobox.bentobox.api.commands.CompositeCommand;
-import world.bentobox.bentobox.api.user.User;
-import world.bentobox.bentobox.util.Util;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.commands.player.LimitPanel;
-
-
-/**
- * Admin command for limits
- * @author tastybento
- *
- */
-public class AdminCommand extends CompositeCommand {
-
- private final Limits addon;
-
- /**
- * Admin command
- * @param addon - addon
- */
- public AdminCommand(Limits addon, CompositeCommand parent) {
- super(parent, "limits");
- this.addon = addon;
-
- new CalcCommand(this.addon, this);
- new OffsetCommand(this.addon, this);
- }
-
- /* (non-Javadoc)
- * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup()
- */
- @Override
- public void setup() {
- this.setPermission("limits.admin.limits");
- this.setOnlyPlayer(true);
- this.setParametersHelp("admin.limits.main.parameters");
- this.setDescription("admin.limits.main.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) {
- // Asking for another player's limits
- // Convert name to a UUID
- final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0));
- if (playerUUID == null) {
- user.sendMessage("general.errors.unknown-player", args.get(0));
- return true;
- } else {
- new LimitPanel(addon).showLimits((GameModeAddon)getAddon(), user, playerUUID);
- }
- return true;
- } else {
- showHelp(this, user);
- return false;
- }
- }
-
- @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));
- }
-
-}
+package world.bentobox.limits.commands.admin;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import world.bentobox.bentobox.api.addons.GameModeAddon;
+import world.bentobox.bentobox.api.commands.CompositeCommand;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.util.Util;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.commands.player.LimitPanel;
+
+
+/**
+ * Admin command for limits
+ * @author tastybento
+ *
+ */
+public class AdminCommand extends CompositeCommand {
+
+ private final Limits addon;
+
+ /**
+ * Admin command
+ * @param addon - addon
+ */
+ public AdminCommand(Limits addon, CompositeCommand parent) {
+ super(parent, "limits");
+ this.addon = addon;
+
+ new CalcCommand(this.addon, this);
+ new OffsetCommand(this.addon, this);
+ }
+
+ /* (non-Javadoc)
+ * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup()
+ */
+ @Override
+ public void setup() {
+ this.setPermission("limits.admin.limits");
+ this.setOnlyPlayer(true);
+ this.setParametersHelp("admin.limits.main.parameters");
+ this.setDescription("admin.limits.main.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) {
+ // Asking for another player's limits
+ // Convert name to a UUID
+ final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0));
+ if (playerUUID == null) {
+ user.sendMessage("general.errors.unknown-player", args.get(0));
+ return true;
+ } else {
+ new LimitPanel(addon).showLimits((GameModeAddon)getAddon(), user, playerUUID);
+ }
+ return true;
+ } else {
+ showHelp(this, user);
+ return false;
+ }
+ }
+
+ @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/world/bentobox/limits/commands/admin/CalcCommand.java b/src/main/java/world/bentobox/limits/commands/admin/CalcCommand.java
index f7e7303..08f7f7d 100644
--- a/src/main/java/world/bentobox/limits/commands/admin/CalcCommand.java
+++ b/src/main/java/world/bentobox/limits/commands/admin/CalcCommand.java
@@ -1,95 +1,95 @@
-package world.bentobox.limits.commands.admin;
-
-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.database.objects.Island;
-import world.bentobox.bentobox.util.Util;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.calculators.Pipeliner;
-
-/**
- *
- * @author YellowZaki, tastybento
- */
-public class CalcCommand extends CompositeCommand {
-
- private final Limits addon;
- private Island island;
-
- /**
- * Admin command
- *
- * @param addon - addon
- */
- public CalcCommand(Limits addon, CompositeCommand parent) {
- super(parent, "calc", "recount");
- 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;
- }
- island = addon.getIslands().getIsland(getWorld(), playerUUID);
- if (island == null) {
- user.sendMessage("general.errors.player-has-no-island");
- return false;
- } else {
- //Calculate
- user.sendMessage("island.limits.recount.now-recounting");
- new Pipeliner(addon).addIsland(island).thenAccept(results -> {
- if (results == null) {
- user.sendMessage("island.limits.recount.in-progress");
- } else {
- switch (results.getState()) {
- case TIMEOUT -> user.sendMessage("admin.limits.calc.timeout");
- default -> user.sendMessage("admin.limits.calc.finished");
- }
- }
- });
- }
-
- return true;
- } else {
- showHelp(this, user);
- return false;
- }
- }
-
-
-
- @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));
- }
-
-}
+package world.bentobox.limits.commands.admin;
+
+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.database.objects.Island;
+import world.bentobox.bentobox.util.Util;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.calculators.Pipeliner;
+
+/**
+ *
+ * @author YellowZaki, tastybento
+ */
+public class CalcCommand extends CompositeCommand {
+
+ private final Limits addon;
+ private Island island;
+
+ /**
+ * Admin command
+ *
+ * @param addon - addon
+ */
+ public CalcCommand(Limits addon, CompositeCommand parent) {
+ super(parent, "calc", "recount");
+ 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;
+ }
+ island = addon.getIslands().getIsland(getWorld(), playerUUID);
+ if (island == null) {
+ user.sendMessage("general.errors.player-has-no-island");
+ return false;
+ } else {
+ //Calculate
+ user.sendMessage("island.limits.recount.now-recounting");
+ new Pipeliner(addon).addIsland(island).thenAccept(results -> {
+ if (results == null) {
+ user.sendMessage("island.limits.recount.in-progress");
+ } else {
+ switch (results.getState()) {
+ case TIMEOUT -> user.sendMessage("admin.limits.calc.timeout");
+ default -> user.sendMessage("admin.limits.calc.finished");
+ }
+ }
+ });
+ }
+
+ return true;
+ } else {
+ showHelp(this, user);
+ return false;
+ }
+ }
+
+
+
+ @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/world/bentobox/limits/commands/admin/OffsetCommand.java b/src/main/java/world/bentobox/limits/commands/admin/OffsetCommand.java
index 3ab71b9..a675596 100644
--- a/src/main/java/world/bentobox/limits/commands/admin/OffsetCommand.java
+++ b/src/main/java/world/bentobox/limits/commands/admin/OffsetCommand.java
@@ -1,695 +1,695 @@
-//
-// Created by BONNe
-// Copyright - 2022
-//
-
-
-package world.bentobox.limits.commands.admin;
-
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-import org.bukkit.Material;
-import org.bukkit.NamespacedKey;
-import org.bukkit.entity.EntityType;
-import org.eclipse.jdt.annotation.Nullable;
-
-import com.google.common.base.Enums;
-
-import world.bentobox.bentobox.api.commands.CompositeCommand;
-import world.bentobox.bentobox.api.localization.TextVariables;
-import world.bentobox.bentobox.api.user.User;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.bentobox.util.Util;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.objects.IslandBlockCount;
-
-
-/**
- * This command manages offsets to the player island limits.
- */
-public class OffsetCommand extends CompositeCommand
-{
- /**
- * Instantiates a new Offset command.
- *
- * @param addon the addon
- * @param parent the parent
- */
- public OffsetCommand(Limits addon, CompositeCommand parent)
- {
- super(parent, "offset");
-
- new OffsetSetCommand(addon, this);
- new OffsetAddCommand(addon, this);
- new OffsetRemoveCommand(addon, this);
- new OffsetResetCommand(addon, this);
- new OffsetDisplayCommand(addon, this);
- }
-
-
- @Override
- public void setup()
- {
- this.setOnlyPlayer(false);
-
- this.setPermission("admin.limits.offset");
- this.setParametersHelp("admin.limits.offset.parameters");
- this.setDescription("admin.limits.offset.description");
- }
-
-
- @Override
- public boolean execute(User user, String s, List list)
- {
- this.showHelp(this, user);
- return true;
- }
-
-
- /**
- * This command allows setting limit offset for material or entity.
- */
- private static class OffsetSetCommand extends CompositeCommand
- {
- /**
- * Instantiates a new Offset set command.
- *
- * @param addon the addon
- * @param parent the parent
- */
- public OffsetSetCommand(Limits addon, CompositeCommand parent)
- {
- super(parent, "set");
- this.addon = addon;
- }
-
-
- @Override
- public void setup()
- {
- this.setOnlyPlayer(false);
-
- this.setPermission("admin.limits.offset.set");
- this.setParametersHelp("admin.limits.offset.set.parameters");
- this.setDescription("admin.limits.offset.set.description");
- }
-
-
- @Override
- public boolean execute(User user, String label, List args)
- {
- if (args.size() != 3)
- {
- // Show help
- this.showHelp(this, user);
- return false;
- }
-
- // Get target player
- UUID targetUUID = Util.getUUID(args.get(0));
-
- if (targetUUID == null)
- {
- user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
- return false;
- }
-
- Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
-
- if (island == null)
- {
- user.sendMessage("general.errors.player-has-no-island");
- return false;
- }
-
- IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
-
- // Get new offset
- if (!Util.isInteger(args.get(2), true))
- {
- user.sendMessage("general.errors.must-be-a-number", TextVariables.NUMBER, args.get(2));
- return false;
- }
-
- Material material = Material.matchMaterial(args.get(1));
- EntityType entityType = matchEntity(args.get(1));
-
- if (material == null && entityType == null)
- {
- user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
- return false;
- }
-
- int offset = Integer.parseInt(args.get(2));
-
- if (material != null && offset == islandData.getBlockLimitOffset(material) ||
- entityType != null && offset == islandData.getEntityLimitOffset(entityType))
- {
- user.sendMessage("admin.limits.offset.set.same",
- TextVariables.NAME,
- args.get(1),
- TextVariables.NUMBER,
- args.get(2));
- return false;
- }
-
- if (material != null)
- {
- islandData.setBlockLimitsOffset(material, offset);
- islandData.setChanged();
-
- user.sendMessage("admin.limits.offset.set.success",
- TextVariables.NUMBER, String.valueOf(offset),
- TextVariables.NAME, material.name());
- }
- else
- {
- islandData.setEntityLimitsOffset(entityType, offset);
- islandData.setChanged();
-
- user.sendMessage("admin.limits.offset.set.success",
- TextVariables.NUMBER, String.valueOf(offset),
- TextVariables.NAME, entityType.name());
- }
-
- return true;
- }
-
-
- @Override
- public Optional> tabComplete(User user, String alias, List args)
- {
- return OffsetCommand.craftTabComplete(user, alias, args);
- }
-
-
- /**
- * Instance of limits addon.
- */
- private final Limits addon;
- }
-
-
- /**
- * This command allows increasing limit offset for material or entity.
- */
- private static class OffsetAddCommand extends CompositeCommand
- {
- /**
- * Instantiates a new Offset add command.
- *
- * @param addon the addon
- * @param parent the parent
- */
- public OffsetAddCommand(Limits addon, CompositeCommand parent)
- {
- super(parent, "add");
- this.addon = addon;
- }
-
-
- @Override
- public void setup()
- {
- this.setOnlyPlayer(false);
-
- this.setPermission("admin.limits.offset.add");
- this.setParametersHelp("admin.limits.offset.add.parameters");
- this.setDescription("admin.limits.offset.add.description");
- }
-
-
- @Override
- public boolean execute(User user, String label, List args)
- {
- if (args.size() != 3)
- {
- // Show help
- this.showHelp(this, user);
- return false;
- }
-
- // Get target player
- UUID targetUUID = Util.getUUID(args.get(0));
-
- if (targetUUID == null)
- {
- user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
- return false;
- }
-
- Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
-
- if (island == null)
- {
- user.sendMessage("general.errors.player-has-no-island");
- return false;
- }
-
- IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
-
- // Get new offset
- if (!Util.isInteger(args.get(2), true) || Integer.parseInt(args.get(2)) < 0)
- {
- user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(2));
- return false;
- }
-
- Material material = Material.matchMaterial(args.get(1));
- EntityType entityType = matchEntity(args.get(1));
-
- if (material == null && entityType == null)
- {
- user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
- return false;
- }
-
- int offset = Integer.parseInt(args.get(2));
-
- if (material != null)
- {
- offset += islandData.getBlockLimitOffset(material);
-
- islandData.setBlockLimitsOffset(material, offset);
- islandData.setChanged();
-
- user.sendMessage("admin.limits.offset.add.success",
- TextVariables.NUMBER, String.valueOf(offset),
- TextVariables.NAME, material.name());
- }
- else
- {
- offset += islandData.getEntityLimitOffset(entityType);
-
- islandData.setEntityLimitsOffset(entityType, offset);
- islandData.setChanged();
-
- user.sendMessage("admin.limits.offset.add.success",
- TextVariables.NUMBER, String.valueOf(offset),
- TextVariables.NAME, entityType.name());
- }
-
- return true;
- }
-
-
- @Override
- public Optional> tabComplete(User user, String alias, List args)
- {
- return OffsetCommand.craftTabComplete(user, alias, args);
- }
-
-
- /**
- * Instance of limits addon.
- */
- private final Limits addon;
- }
-
-
- /**
- * This command allows reducing limit offset for material or entity.
- */
- private static class OffsetRemoveCommand extends CompositeCommand
- {
- /**
- * Instantiates a new Offset remove command.
- *
- * @param addon the addon
- * @param parent the parent
- */
- public OffsetRemoveCommand(Limits addon, CompositeCommand parent)
- {
- super(parent, "remove");
- this.addon = addon;
- }
-
-
- @Override
- public void setup()
- {
- this.setOnlyPlayer(false);
-
- this.setPermission("admin.limits.offset.remove");
- this.setParametersHelp("admin.limits.offset.remove.parameters");
- this.setDescription("admin.limits.offset.remove.description");
- }
-
-
- @Override
- public boolean execute(User user, String label, List args)
- {
- if (args.size() != 3)
- {
- // Show help
- this.showHelp(this, user);
- return false;
- }
-
- // Get target player
- UUID targetUUID = Util.getUUID(args.get(0));
-
- if (targetUUID == null)
- {
- user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
- return false;
- }
-
- Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
-
- if (island == null)
- {
- user.sendMessage("general.errors.player-has-no-island");
- return false;
- }
-
- IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
-
- // Get new offset
- if (!Util.isInteger(args.get(2), true) || Integer.parseInt(args.get(2)) < 0)
- {
- user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(2));
- return false;
- }
-
- Material material = Material.matchMaterial(args.get(1));
- EntityType entityType = matchEntity(args.get(1));
-
- if (material == null && entityType == null)
- {
- user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
- return false;
- }
-
- int offset = Integer.parseInt(args.get(2));
-
- if (material != null)
- {
- offset = islandData.getBlockLimitOffset(material) - offset;
-
- islandData.setBlockLimitsOffset(material, offset);
- islandData.setChanged();
-
- user.sendMessage("admin.limits.offset.remove.success",
- TextVariables.NUMBER, String.valueOf(offset),
- TextVariables.NAME, material.name());
- }
- else
- {
- offset = islandData.getEntityLimitOffset(entityType) - offset;
-
- islandData.setEntityLimitsOffset(entityType, offset);
- islandData.setChanged();
-
- user.sendMessage("admin.limits.offset.remove.success",
- TextVariables.NUMBER, String.valueOf(offset),
- TextVariables.NAME, entityType.name());
- }
-
- return true;
- }
-
-
- @Override
- public Optional> tabComplete(User user, String alias, List args)
- {
- return OffsetCommand.craftTabComplete(user, alias, args);
- }
-
-
- /**
- * Instance of limits addon.
- */
- private final Limits addon;
- }
-
-
- /**
- * This command allows resetting limit offset for material or entity.
- */
- private static class OffsetResetCommand extends CompositeCommand
- {
- /**
- * Instantiates a new Offset reset command.
- *
- * @param addon the addon
- * @param parent the parent
- */
- public OffsetResetCommand(Limits addon, CompositeCommand parent)
- {
- super(parent, "reset");
- this.addon = addon;
- }
-
-
- @Override
- public void setup()
- {
- this.setOnlyPlayer(false);
-
- this.setPermission("admin.limits.offset.reset");
- this.setParametersHelp("admin.limits.offset.reset.parameters");
- this.setDescription("admin.limits.offset.reset.description");
- }
-
-
- @Override
- public boolean execute(User user, String label, List args)
- {
- if (args.size() != 2)
- {
- // Show help
- this.showHelp(this, user);
- return false;
- }
-
- // Get target player
- UUID targetUUID = Util.getUUID(args.get(0));
-
- if (targetUUID == null)
- {
- user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
- return false;
- }
-
- Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
-
- if (island == null)
- {
- user.sendMessage("general.errors.player-has-no-island");
- return false;
- }
-
- IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
-
- Material material = Material.matchMaterial(args.get(1));
- EntityType entityType = matchEntity(args.get(1));
-
- if (material == null && entityType == null)
- {
- user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
- return false;
- }
-
- if (material != null)
- {
- islandData.setBlockLimitsOffset(material, 0);
- islandData.setChanged();
-
- user.sendMessage("admin.limits.offset.reset.success",
- TextVariables.NAME, material.name());
- }
- else
- {
- islandData.setEntityLimitsOffset(entityType, 0);
- islandData.setChanged();
-
- user.sendMessage("admin.limits.offset.reset.success",
- TextVariables.NAME, entityType.name());
- }
-
- return true;
- }
-
-
- @Override
- public Optional> tabComplete(User user, String alias, List args)
- {
- return OffsetCommand.craftTabComplete(user, alias, args);
- }
-
-
- /**
- * Instance of limits addon.
- */
- private final Limits addon;
- }
-
-
- /**
- * This command allows viewing limit offset for material or entity.
- */
- private static class OffsetDisplayCommand extends CompositeCommand
- {
- /**
- * Instantiates a new Offset display command.
- *
- * @param addon the addon
- * @param parent the parent
- */
- public OffsetDisplayCommand(Limits addon, CompositeCommand parent)
- {
- super(parent, "view", "display");
- this.addon = addon;
- }
-
-
- @Override
- public void setup()
- {
- this.setOnlyPlayer(false);
-
- this.setPermission("admin.limits.offset.view");
- this.setParametersHelp("admin.limits.offset.view.parameters");
- this.setDescription("admin.limits.offset.view.description");
- }
-
-
- @Override
- public boolean execute(User user, String label, List args)
- {
- if (args.size() != 2)
- {
- // Show help
- this.showHelp(this, user);
- return false;
- }
-
- // Get target player
- UUID targetUUID = Util.getUUID(args.get(0));
-
- if (targetUUID == null)
- {
- user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
- return false;
- }
-
- Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
-
- if (island == null)
- {
- user.sendMessage("general.errors.player-has-no-island");
- return false;
- }
-
- IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
-
- Material material = Material.matchMaterial(args.get(1));
- EntityType entityType = matchEntity(args.get(1));
-
- if (material == null && entityType == null)
- {
- user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
- return false;
- }
-
- if (material != null)
- {
- int offset = islandData.getBlockLimitOffset(material);
- user.sendMessage("admin.limits.offset.view.message",
- TextVariables.NAME, material.name(),
- TextVariables.NUMBER, String.valueOf(offset));
- }
- else
- {
- int offset = islandData.getEntityLimitOffset(entityType);
- user.sendMessage("admin.limits.offset.view.message",
- TextVariables.NAME, entityType.name(),
- TextVariables.NUMBER, String.valueOf(offset));
- }
-
- return true;
- }
-
-
- @Override
- public Optional> tabComplete(User user, String alias, List args)
- {
- return OffsetCommand.craftTabComplete(user, alias, args);
- }
-
-
- /**
- * Instance of limits addon.
- */
- private final Limits addon;
- }
-
-
- /**
- * This material matches name to an entity type.
- * @param name Name that must be matched.
- * @return EntityType or null.
- */
- @Nullable
- private static EntityType matchEntity(String name)
- {
- String filtered = name;
-
- if (filtered.startsWith(NamespacedKey.MINECRAFT + ":"))
- {
- filtered = filtered.substring((NamespacedKey.MINECRAFT + ":").length());
- }
-
- filtered = filtered.toUpperCase(java.util.Locale.ENGLISH);
- filtered = filtered.replaceAll("\\s+", "_").replaceAll("\\W", "");
-
- return Enums.getIfPresent(EntityType.class, filtered).orNull();
- }
-
-
- /**
- * This method crafts tab complete for all subcommands
- * @param user User who runs command.
- * @param alias Command alias.
- * @param args List of args.
- * @return Optional list of strings.
- */
- private static Optional> craftTabComplete(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();
- }
- else if (args.size() == 4)
- {
- List options = new ArrayList<>(Util.getOnlinePlayerList(user));
- return Optional.of(Util.tabLimit(options, lastArg));
- }
- else if (args.size() == 5)
- {
- List options = Arrays.stream(Material.values()).
- map(Enum::name).
- collect(Collectors.toList());
-
- options.addAll(Arrays.stream(EntityType.values()).
- map(Enum::name).
- collect(Collectors.toList()));
-
- return Optional.of(Util.tabLimit(options, lastArg));
- }
- else
- {
- return Optional.empty();
- }
- }
-}
+//
+// Created by BONNe
+// Copyright - 2022
+//
+
+
+package world.bentobox.limits.commands.admin;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.entity.EntityType;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.common.base.Enums;
+
+import world.bentobox.bentobox.api.commands.CompositeCommand;
+import world.bentobox.bentobox.api.localization.TextVariables;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.util.Util;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.objects.IslandBlockCount;
+
+
+/**
+ * This command manages offsets to the player island limits.
+ */
+public class OffsetCommand extends CompositeCommand
+{
+ /**
+ * Instantiates a new Offset command.
+ *
+ * @param addon the addon
+ * @param parent the parent
+ */
+ public OffsetCommand(Limits addon, CompositeCommand parent)
+ {
+ super(parent, "offset");
+
+ new OffsetSetCommand(addon, this);
+ new OffsetAddCommand(addon, this);
+ new OffsetRemoveCommand(addon, this);
+ new OffsetResetCommand(addon, this);
+ new OffsetDisplayCommand(addon, this);
+ }
+
+
+ @Override
+ public void setup()
+ {
+ this.setOnlyPlayer(false);
+
+ this.setPermission("admin.limits.offset");
+ this.setParametersHelp("admin.limits.offset.parameters");
+ this.setDescription("admin.limits.offset.description");
+ }
+
+
+ @Override
+ public boolean execute(User user, String s, List list)
+ {
+ this.showHelp(this, user);
+ return true;
+ }
+
+
+ /**
+ * This command allows setting limit offset for material or entity.
+ */
+ private static class OffsetSetCommand extends CompositeCommand
+ {
+ /**
+ * Instantiates a new Offset set command.
+ *
+ * @param addon the addon
+ * @param parent the parent
+ */
+ public OffsetSetCommand(Limits addon, CompositeCommand parent)
+ {
+ super(parent, "set");
+ this.addon = addon;
+ }
+
+
+ @Override
+ public void setup()
+ {
+ this.setOnlyPlayer(false);
+
+ this.setPermission("admin.limits.offset.set");
+ this.setParametersHelp("admin.limits.offset.set.parameters");
+ this.setDescription("admin.limits.offset.set.description");
+ }
+
+
+ @Override
+ public boolean execute(User user, String label, List args)
+ {
+ if (args.size() != 3)
+ {
+ // Show help
+ this.showHelp(this, user);
+ return false;
+ }
+
+ // Get target player
+ UUID targetUUID = Util.getUUID(args.get(0));
+
+ if (targetUUID == null)
+ {
+ user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
+ return false;
+ }
+
+ Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
+
+ if (island == null)
+ {
+ user.sendMessage("general.errors.player-has-no-island");
+ return false;
+ }
+
+ IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
+
+ // Get new offset
+ if (!Util.isInteger(args.get(2), true))
+ {
+ user.sendMessage("general.errors.must-be-a-number", TextVariables.NUMBER, args.get(2));
+ return false;
+ }
+
+ Material material = Material.matchMaterial(args.get(1));
+ EntityType entityType = matchEntity(args.get(1));
+
+ if (material == null && entityType == null)
+ {
+ user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
+ return false;
+ }
+
+ int offset = Integer.parseInt(args.get(2));
+
+ if (material != null && offset == islandData.getBlockLimitOffset(material) ||
+ entityType != null && offset == islandData.getEntityLimitOffset(entityType))
+ {
+ user.sendMessage("admin.limits.offset.set.same",
+ TextVariables.NAME,
+ args.get(1),
+ TextVariables.NUMBER,
+ args.get(2));
+ return false;
+ }
+
+ if (material != null)
+ {
+ islandData.setBlockLimitsOffset(material, offset);
+ islandData.setChanged();
+
+ user.sendMessage("admin.limits.offset.set.success",
+ TextVariables.NUMBER, String.valueOf(offset),
+ TextVariables.NAME, material.name());
+ }
+ else
+ {
+ islandData.setEntityLimitsOffset(entityType, offset);
+ islandData.setChanged();
+
+ user.sendMessage("admin.limits.offset.set.success",
+ TextVariables.NUMBER, String.valueOf(offset),
+ TextVariables.NAME, entityType.name());
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public Optional> tabComplete(User user, String alias, List args)
+ {
+ return OffsetCommand.craftTabComplete(user, alias, args);
+ }
+
+
+ /**
+ * Instance of limits addon.
+ */
+ private final Limits addon;
+ }
+
+
+ /**
+ * This command allows increasing limit offset for material or entity.
+ */
+ private static class OffsetAddCommand extends CompositeCommand
+ {
+ /**
+ * Instantiates a new Offset add command.
+ *
+ * @param addon the addon
+ * @param parent the parent
+ */
+ public OffsetAddCommand(Limits addon, CompositeCommand parent)
+ {
+ super(parent, "add");
+ this.addon = addon;
+ }
+
+
+ @Override
+ public void setup()
+ {
+ this.setOnlyPlayer(false);
+
+ this.setPermission("admin.limits.offset.add");
+ this.setParametersHelp("admin.limits.offset.add.parameters");
+ this.setDescription("admin.limits.offset.add.description");
+ }
+
+
+ @Override
+ public boolean execute(User user, String label, List args)
+ {
+ if (args.size() != 3)
+ {
+ // Show help
+ this.showHelp(this, user);
+ return false;
+ }
+
+ // Get target player
+ UUID targetUUID = Util.getUUID(args.get(0));
+
+ if (targetUUID == null)
+ {
+ user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
+ return false;
+ }
+
+ Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
+
+ if (island == null)
+ {
+ user.sendMessage("general.errors.player-has-no-island");
+ return false;
+ }
+
+ IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
+
+ // Get new offset
+ if (!Util.isInteger(args.get(2), true) || Integer.parseInt(args.get(2)) < 0)
+ {
+ user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(2));
+ return false;
+ }
+
+ Material material = Material.matchMaterial(args.get(1));
+ EntityType entityType = matchEntity(args.get(1));
+
+ if (material == null && entityType == null)
+ {
+ user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
+ return false;
+ }
+
+ int offset = Integer.parseInt(args.get(2));
+
+ if (material != null)
+ {
+ offset += islandData.getBlockLimitOffset(material);
+
+ islandData.setBlockLimitsOffset(material, offset);
+ islandData.setChanged();
+
+ user.sendMessage("admin.limits.offset.add.success",
+ TextVariables.NUMBER, String.valueOf(offset),
+ TextVariables.NAME, material.name());
+ }
+ else
+ {
+ offset += islandData.getEntityLimitOffset(entityType);
+
+ islandData.setEntityLimitsOffset(entityType, offset);
+ islandData.setChanged();
+
+ user.sendMessage("admin.limits.offset.add.success",
+ TextVariables.NUMBER, String.valueOf(offset),
+ TextVariables.NAME, entityType.name());
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public Optional> tabComplete(User user, String alias, List args)
+ {
+ return OffsetCommand.craftTabComplete(user, alias, args);
+ }
+
+
+ /**
+ * Instance of limits addon.
+ */
+ private final Limits addon;
+ }
+
+
+ /**
+ * This command allows reducing limit offset for material or entity.
+ */
+ private static class OffsetRemoveCommand extends CompositeCommand
+ {
+ /**
+ * Instantiates a new Offset remove command.
+ *
+ * @param addon the addon
+ * @param parent the parent
+ */
+ public OffsetRemoveCommand(Limits addon, CompositeCommand parent)
+ {
+ super(parent, "remove");
+ this.addon = addon;
+ }
+
+
+ @Override
+ public void setup()
+ {
+ this.setOnlyPlayer(false);
+
+ this.setPermission("admin.limits.offset.remove");
+ this.setParametersHelp("admin.limits.offset.remove.parameters");
+ this.setDescription("admin.limits.offset.remove.description");
+ }
+
+
+ @Override
+ public boolean execute(User user, String label, List args)
+ {
+ if (args.size() != 3)
+ {
+ // Show help
+ this.showHelp(this, user);
+ return false;
+ }
+
+ // Get target player
+ UUID targetUUID = Util.getUUID(args.get(0));
+
+ if (targetUUID == null)
+ {
+ user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
+ return false;
+ }
+
+ Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
+
+ if (island == null)
+ {
+ user.sendMessage("general.errors.player-has-no-island");
+ return false;
+ }
+
+ IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
+
+ // Get new offset
+ if (!Util.isInteger(args.get(2), true) || Integer.parseInt(args.get(2)) < 0)
+ {
+ user.sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, args.get(2));
+ return false;
+ }
+
+ Material material = Material.matchMaterial(args.get(1));
+ EntityType entityType = matchEntity(args.get(1));
+
+ if (material == null && entityType == null)
+ {
+ user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
+ return false;
+ }
+
+ int offset = Integer.parseInt(args.get(2));
+
+ if (material != null)
+ {
+ offset = islandData.getBlockLimitOffset(material) - offset;
+
+ islandData.setBlockLimitsOffset(material, offset);
+ islandData.setChanged();
+
+ user.sendMessage("admin.limits.offset.remove.success",
+ TextVariables.NUMBER, String.valueOf(offset),
+ TextVariables.NAME, material.name());
+ }
+ else
+ {
+ offset = islandData.getEntityLimitOffset(entityType) - offset;
+
+ islandData.setEntityLimitsOffset(entityType, offset);
+ islandData.setChanged();
+
+ user.sendMessage("admin.limits.offset.remove.success",
+ TextVariables.NUMBER, String.valueOf(offset),
+ TextVariables.NAME, entityType.name());
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public Optional> tabComplete(User user, String alias, List args)
+ {
+ return OffsetCommand.craftTabComplete(user, alias, args);
+ }
+
+
+ /**
+ * Instance of limits addon.
+ */
+ private final Limits addon;
+ }
+
+
+ /**
+ * This command allows resetting limit offset for material or entity.
+ */
+ private static class OffsetResetCommand extends CompositeCommand
+ {
+ /**
+ * Instantiates a new Offset reset command.
+ *
+ * @param addon the addon
+ * @param parent the parent
+ */
+ public OffsetResetCommand(Limits addon, CompositeCommand parent)
+ {
+ super(parent, "reset");
+ this.addon = addon;
+ }
+
+
+ @Override
+ public void setup()
+ {
+ this.setOnlyPlayer(false);
+
+ this.setPermission("admin.limits.offset.reset");
+ this.setParametersHelp("admin.limits.offset.reset.parameters");
+ this.setDescription("admin.limits.offset.reset.description");
+ }
+
+
+ @Override
+ public boolean execute(User user, String label, List args)
+ {
+ if (args.size() != 2)
+ {
+ // Show help
+ this.showHelp(this, user);
+ return false;
+ }
+
+ // Get target player
+ UUID targetUUID = Util.getUUID(args.get(0));
+
+ if (targetUUID == null)
+ {
+ user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
+ return false;
+ }
+
+ Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
+
+ if (island == null)
+ {
+ user.sendMessage("general.errors.player-has-no-island");
+ return false;
+ }
+
+ IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
+
+ Material material = Material.matchMaterial(args.get(1));
+ EntityType entityType = matchEntity(args.get(1));
+
+ if (material == null && entityType == null)
+ {
+ user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
+ return false;
+ }
+
+ if (material != null)
+ {
+ islandData.setBlockLimitsOffset(material, 0);
+ islandData.setChanged();
+
+ user.sendMessage("admin.limits.offset.reset.success",
+ TextVariables.NAME, material.name());
+ }
+ else
+ {
+ islandData.setEntityLimitsOffset(entityType, 0);
+ islandData.setChanged();
+
+ user.sendMessage("admin.limits.offset.reset.success",
+ TextVariables.NAME, entityType.name());
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public Optional> tabComplete(User user, String alias, List args)
+ {
+ return OffsetCommand.craftTabComplete(user, alias, args);
+ }
+
+
+ /**
+ * Instance of limits addon.
+ */
+ private final Limits addon;
+ }
+
+
+ /**
+ * This command allows viewing limit offset for material or entity.
+ */
+ private static class OffsetDisplayCommand extends CompositeCommand
+ {
+ /**
+ * Instantiates a new Offset display command.
+ *
+ * @param addon the addon
+ * @param parent the parent
+ */
+ public OffsetDisplayCommand(Limits addon, CompositeCommand parent)
+ {
+ super(parent, "view", "display");
+ this.addon = addon;
+ }
+
+
+ @Override
+ public void setup()
+ {
+ this.setOnlyPlayer(false);
+
+ this.setPermission("admin.limits.offset.view");
+ this.setParametersHelp("admin.limits.offset.view.parameters");
+ this.setDescription("admin.limits.offset.view.description");
+ }
+
+
+ @Override
+ public boolean execute(User user, String label, List args)
+ {
+ if (args.size() != 2)
+ {
+ // Show help
+ this.showHelp(this, user);
+ return false;
+ }
+
+ // Get target player
+ UUID targetUUID = Util.getUUID(args.get(0));
+
+ if (targetUUID == null)
+ {
+ user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
+ return false;
+ }
+
+ Island island = this.getIslands().getIsland(this.getWorld(), targetUUID);
+
+ if (island == null)
+ {
+ user.sendMessage("general.errors.player-has-no-island");
+ return false;
+ }
+
+ IslandBlockCount islandData = this.addon.getBlockLimitListener().getIsland(island);
+
+ Material material = Material.matchMaterial(args.get(1));
+ EntityType entityType = matchEntity(args.get(1));
+
+ if (material == null && entityType == null)
+ {
+ user.sendMessage("admin.limits.offset.unknown", TextVariables.NAME, args.get(1));
+ return false;
+ }
+
+ if (material != null)
+ {
+ int offset = islandData.getBlockLimitOffset(material);
+ user.sendMessage("admin.limits.offset.view.message",
+ TextVariables.NAME, material.name(),
+ TextVariables.NUMBER, String.valueOf(offset));
+ }
+ else
+ {
+ int offset = islandData.getEntityLimitOffset(entityType);
+ user.sendMessage("admin.limits.offset.view.message",
+ TextVariables.NAME, entityType.name(),
+ TextVariables.NUMBER, String.valueOf(offset));
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public Optional> tabComplete(User user, String alias, List args)
+ {
+ return OffsetCommand.craftTabComplete(user, alias, args);
+ }
+
+
+ /**
+ * Instance of limits addon.
+ */
+ private final Limits addon;
+ }
+
+
+ /**
+ * This material matches name to an entity type.
+ * @param name Name that must be matched.
+ * @return EntityType or null.
+ */
+ @Nullable
+ private static EntityType matchEntity(String name)
+ {
+ String filtered = name;
+
+ if (filtered.startsWith(NamespacedKey.MINECRAFT + ":"))
+ {
+ filtered = filtered.substring((NamespacedKey.MINECRAFT + ":").length());
+ }
+
+ filtered = filtered.toUpperCase(java.util.Locale.ENGLISH);
+ filtered = filtered.replaceAll("\\s+", "_").replaceAll("\\W", "");
+
+ return Enums.getIfPresent(EntityType.class, filtered).orNull();
+ }
+
+
+ /**
+ * This method crafts tab complete for all subcommands
+ * @param user User who runs command.
+ * @param alias Command alias.
+ * @param args List of args.
+ * @return Optional list of strings.
+ */
+ private static Optional> craftTabComplete(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();
+ }
+ else if (args.size() == 4)
+ {
+ List options = new ArrayList<>(Util.getOnlinePlayerList(user));
+ return Optional.of(Util.tabLimit(options, lastArg));
+ }
+ else if (args.size() == 5)
+ {
+ List options = Arrays.stream(Material.values()).
+ map(Enum::name).
+ collect(Collectors.toList());
+
+ options.addAll(Arrays.stream(EntityType.values()).
+ map(Enum::name).
+ collect(Collectors.toList()));
+
+ return Optional.of(Util.tabLimit(options, lastArg));
+ }
+ else
+ {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/src/main/java/world/bentobox/limits/commands/player/LimitPanel.java b/src/main/java/world/bentobox/limits/commands/player/LimitPanel.java
index bb2d6e2..d0acf7d 100644
--- a/src/main/java/world/bentobox/limits/commands/player/LimitPanel.java
+++ b/src/main/java/world/bentobox/limits/commands/player/LimitPanel.java
@@ -1,78 +1,78 @@
-package world.bentobox.limits.commands.player;
-
-import java.util.Map;
-import java.util.UUID;
-
-import org.bukkit.Bukkit;
-import org.bukkit.Material;
-import org.bukkit.World;
-import org.bukkit.entity.Player;
-
-import world.bentobox.bentobox.api.addons.GameModeAddon;
-import world.bentobox.bentobox.api.panels.builders.TabbedPanelBuilder;
-import world.bentobox.bentobox.api.user.User;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.objects.IslandBlockCount;
-
-/**
- * Shows a panel of the blocks that are limited and their status
- * @author tastybento
- *
- */
-public class LimitPanel {
-
- private final Limits addon;
-
- /**
- * @param addon - limit addon
- */
- public LimitPanel(Limits addon) {
- this.addon = addon;
- }
-
- /**
- * Show the limits panel
- * @param gm - game mode
- * @param user - user asking
- * @param target - target uuid
- */
- public void showLimits(GameModeAddon gm, User user, UUID target) {
- // Get world
- World world = gm.getOverWorld();
- // Get the island for the target
- Island island = addon.getIslands().getIsland(world, target);
- if (island == null) {
- if (user.getUniqueId().equals(target)) {
- user.sendMessage("general.errors.no-island");
- } else {
- user.sendMessage("general.errors.player-has-no-island");
- }
- return;
- }
- // See if the target is online
- Player targetPlayer = Bukkit.getPlayer(target);
- if (targetPlayer != null) {
- // Update perms
- addon.getJoinListener().checkPerms(targetPlayer, gm.getPermissionPrefix() + "island.limit.", island.getUniqueId(), gm.getDescription().getName());
- }
- // Get the limits for this island
- IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId());
- Map matLimits = addon.getBlockLimitListener().getMaterialLimits(world, island.getUniqueId());
- if (matLimits.isEmpty() && addon.getSettings().getLimits().isEmpty()) {
- user.sendMessage("island.limits.no-limits");
- return;
- }
-
- new TabbedPanelBuilder()
- .user(user)
- .world(world)
- .tab(0, new LimitTab(addon, ibc, matLimits, island, world, user, LimitTab.SORT_BY.A2Z))
- .tab(1, new LimitTab(addon, ibc, matLimits, island, world, user, LimitTab.SORT_BY.Z2A))
- .startingSlot(0)
- .size(54)
- .build().openPanel();
-
- }
-
-}
+package world.bentobox.limits.commands.player;
+
+import java.util.Map;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+
+import world.bentobox.bentobox.api.addons.GameModeAddon;
+import world.bentobox.bentobox.api.panels.builders.TabbedPanelBuilder;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.objects.IslandBlockCount;
+
+/**
+ * Shows a panel of the blocks that are limited and their status
+ * @author tastybento
+ *
+ */
+public class LimitPanel {
+
+ private final Limits addon;
+
+ /**
+ * @param addon - limit addon
+ */
+ public LimitPanel(Limits addon) {
+ this.addon = addon;
+ }
+
+ /**
+ * Show the limits panel
+ * @param gm - game mode
+ * @param user - user asking
+ * @param target - target uuid
+ */
+ public void showLimits(GameModeAddon gm, User user, UUID target) {
+ // Get world
+ World world = gm.getOverWorld();
+ // Get the island for the target
+ Island island = addon.getIslands().getIsland(world, target);
+ if (island == null) {
+ if (user.getUniqueId().equals(target)) {
+ user.sendMessage("general.errors.no-island");
+ } else {
+ user.sendMessage("general.errors.player-has-no-island");
+ }
+ return;
+ }
+ // See if the target is online
+ Player targetPlayer = Bukkit.getPlayer(target);
+ if (targetPlayer != null) {
+ // Update perms
+ addon.getJoinListener().checkPerms(targetPlayer, gm.getPermissionPrefix() + "island.limit.", island.getUniqueId(), gm.getDescription().getName());
+ }
+ // Get the limits for this island
+ IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId());
+ Map matLimits = addon.getBlockLimitListener().getMaterialLimits(world, island.getUniqueId());
+ if (matLimits.isEmpty() && addon.getSettings().getLimits().isEmpty()) {
+ user.sendMessage("island.limits.no-limits");
+ return;
+ }
+
+ new TabbedPanelBuilder()
+ .user(user)
+ .world(world)
+ .tab(0, new LimitTab(addon, ibc, matLimits, island, world, user, LimitTab.SORT_BY.A2Z))
+ .tab(1, new LimitTab(addon, ibc, matLimits, island, world, user, LimitTab.SORT_BY.Z2A))
+ .startingSlot(0)
+ .size(54)
+ .build().openPanel();
+
+ }
+
+}
diff --git a/src/main/java/world/bentobox/limits/commands/player/LimitTab.java b/src/main/java/world/bentobox/limits/commands/player/LimitTab.java
index cf6a211..85199f8 100644
--- a/src/main/java/world/bentobox/limits/commands/player/LimitTab.java
+++ b/src/main/java/world/bentobox/limits/commands/player/LimitTab.java
@@ -1,269 +1,269 @@
-package world.bentobox.limits.commands.player;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import org.bukkit.Material;
-import org.bukkit.World;
-import org.bukkit.entity.EntityType;
-import org.eclipse.jdt.annotation.Nullable;
-
-import com.google.common.collect.ImmutableMap;
-
-import world.bentobox.bentobox.api.localization.TextVariables;
-import world.bentobox.bentobox.api.panels.PanelItem;
-import world.bentobox.bentobox.api.panels.Tab;
-import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
-import world.bentobox.bentobox.api.user.User;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.bentobox.util.Util;
-import world.bentobox.limits.EntityGroup;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.objects.IslandBlockCount;
-
-/**
- * @author tastybento
- *
- */
-public class LimitTab implements Tab {
-
- enum SORT_BY {
- A2Z,
- Z2A
- }
-
- /**
- * This maps the entity types to the icon that should be shown in the panel
- * If the icon is null, then the entity type is not covered by the addon
- */
- private static final Map E2M = ImmutableMap.builder()
- .put(EntityType.MOOSHROOM, Material.MOOSHROOM_SPAWN_EGG).put(EntityType.SNOW_GOLEM, Material.SNOW_BLOCK)
- .put(EntityType.IRON_GOLEM, Material.IRON_BLOCK)
- .put(EntityType.ILLUSIONER, Material.VILLAGER_SPAWN_EGG)
- .put(EntityType.WITHER, Material.WITHER_SKELETON_SKULL)
- //.put(EntityType.BOAT, Material.OAK_BOAT)
- .put(EntityType.ARMOR_STAND, Material.ARMOR_STAND)
- .put(EntityType.ITEM_FRAME, Material.ITEM_FRAME)
- .put(EntityType.PAINTING, Material.PAINTING)
- // Minecarts
- .put(EntityType.TNT_MINECART, Material.TNT_MINECART).put(EntityType.CHEST_MINECART, Material.CHEST_MINECART)
- .put(EntityType.COMMAND_BLOCK_MINECART, Material.COMMAND_BLOCK_MINECART)
- .put(EntityType.FURNACE_MINECART, Material.FURNACE_MINECART)
- .put(EntityType.HOPPER_MINECART, Material.HOPPER_MINECART)
- .put(EntityType.SPAWNER_MINECART, Material.MINECART)
- //.put(EntityType.CHEST_BOAT, Material.OAK_CHEST_BOAT)
- .build();
- // This is a map of blocks to Items
- private static final Map B2M;
- static {
- ImmutableMap.Builder builder = ImmutableMap.builder()
- .put(Material.POTATOES, Material.POTATO)
- .put(Material.CARROTS, Material.CARROT)
- .put(Material.BEETROOTS, Material.BEETROOT)
- .put(Material.REDSTONE_WIRE, Material.REDSTONE).put(Material.MELON_STEM, Material.MELON)
- .put(Material.PUMPKIN_STEM, Material.PUMPKIN);
- // Block to Material icons
- Optional.ofNullable(Material.getMaterial("SWEET_BERRY_BUSH")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("SWEET_BERRIES"))));
- Optional.ofNullable(Material.getMaterial("BAMBOO_SAPLING")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("BAMBOO"))));
- B2M = builder.build();
- }
-
- private final World world;
- private final User user;
- private final Limits addon;
- private final List<@Nullable PanelItem> result;
- private final SORT_BY sortBy;
-
- public LimitTab(Limits addon, IslandBlockCount ibc, Map matLimits, Island island, World world, User user, SORT_BY sortBy) {
- this.addon = addon;
- this.world = world;
- this.user = user;
- this.sortBy = sortBy;
- result = new ArrayList<>();
- addMaterialIcons(ibc, matLimits);
- addEntityLimits(ibc, island);
- addEntityGroupLimits(ibc, island);
- // Sort
- if (sortBy == SORT_BY.Z2A) {
- result.sort((o1, o2) -> o2.getName().compareTo(o1.getName()));
- } else {
- result.sort(Comparator.comparing(PanelItem::getName));
- }
-
- }
-
- private void addEntityGroupLimits(IslandBlockCount ibc, Island island) {
- // Entity group limits
- Map groupMap = addon.getSettings().getGroupLimitDefinitions().stream().collect(Collectors.toMap(e -> e, EntityGroup::getLimit));
- // Group by same loop up map
- Map groupByName = groupMap.keySet().stream().collect(Collectors.toMap(EntityGroup::getName, e -> e));
- // Merge in any permission-based limits
- if (ibc == null) {
- return;
- }
- ibc.getEntityGroupLimits().entrySet().stream()
- .filter(e -> groupByName.containsKey(e.getKey()))
- .forEach(e -> groupMap.put(groupByName.get(e.getKey()), e.getValue()));
- // Update the group map for each group limit offset. If the value already exists add it
- ibc.getEntityGroupLimitsOffset().forEach((key, value) -> {
- if (groupByName.get(key) != null) {
- groupMap.put(groupByName.get(key), (groupMap.getOrDefault(groupByName.get(key), 0) + value));
- }
- });
- groupMap.forEach((v, limit) -> {
- PanelItemBuilder pib = new PanelItemBuilder();
- pib.name(user.getTranslation("island.limits.panel.entity-group-name-syntax", TextVariables.NAME,
- v.getName()));
- String description = "";
- description += "(" + prettyNames(v) + ")\n";
- pib.icon(v.getIcon());
- long count = getCount(island, v);
- String color = count >= limit ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color");
- description += color
- + user.getTranslation("island.limits.block-limit-syntax",
- TextVariables.NUMBER, String.valueOf(count),
- "[limit]", String.valueOf(limit));
- pib.description(description);
- result.add(pib.build());
- });
- }
-
- private void addEntityLimits(IslandBlockCount ibc, Island island) {
- // Entity limits
- Map map = new HashMap<>(addon.getSettings().getLimits());
- // Merge in any permission-based limits
- if (ibc != null) {
- map.putAll(ibc.getEntityLimits());
- ibc.getEntityLimitsOffset().forEach((k,v) -> map.put(k, map.getOrDefault(k, 0) + v));
- }
-
- map.forEach((k,v) -> {
- PanelItemBuilder pib = new PanelItemBuilder();
- pib.name(user.getTranslation("island.limits.panel.entity-name-syntax", TextVariables.NAME,
- Util.prettifyText(k.toString())));
- Material m;
- try {
- if (E2M.containsKey(k)) {
- m = E2M.get(k);
- } else if (k.isAlive()) {
- m = Material.valueOf(k + "_SPAWN_EGG");
- } else {
- // Regular material
- m = Material.valueOf(k.toString());
- }
- } catch (Exception e) {
- m = Material.BARRIER;
- }
- pib.icon(m);
- long count = getCount(island, k);
- String color = count >= v ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color");
- pib.description(color
- + user.getTranslation("island.limits.block-limit-syntax",
- TextVariables.NUMBER, String.valueOf(count),
- "[limit]", String.valueOf(v)));
- result.add(pib.build());
- });
-
- }
-
- private void addMaterialIcons(IslandBlockCount ibc, Map matLimits) {
- // Material limits
- for (Entry en : matLimits.entrySet()) {
- PanelItemBuilder pib = new PanelItemBuilder();
- pib.name(user.getTranslation("island.limits.panel.block-name-syntax", TextVariables.NAME,
- Util.prettifyText(en.getKey().toString())));
- // Adjust icon
- pib.icon(B2M.getOrDefault(en.getKey(), en.getKey()));
-
- int count = ibc == null ? 0 : ibc.getBlockCounts().getOrDefault(en.getKey(), 0);
- int value = en.getValue();
- String color = count >= value ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color");
- pib.description(color
- + user.getTranslation("island.limits.block-limit-syntax",
- TextVariables.NUMBER, String.valueOf(count),
- "[limit]", String.valueOf(value)));
- result.add(pib.build());
- }
- }
-
- @Override
- public PanelItem getIcon() {
- return new PanelItemBuilder().icon(Material.MAGENTA_GLAZED_TERRACOTTA).name(this.getName()).build();
- }
-
- @Override
- public String getName() {
- String sort = user.getTranslation(world, "island.limits.panel." + sortBy);
- return user.getTranslation(world, "island.limits.panel.title-syntax", "[title]",
- user.getTranslation(world, "limits.panel-title"), "[sort]", sort);
- }
-
- @Override
- public List<@Nullable PanelItem> getPanelItems() {
- return result;
- }
-
- @Override
- public String getPermission() {
- return "";
- }
-
- private String prettyNames(EntityGroup v) {
- StringBuilder sb = new StringBuilder();
- List l = new ArrayList<>(v.getTypes());
- for(int i = 0; i < l.size(); i++)
- {
- sb.append(Util.prettifyText(l.get(i).toString()));
- if (i + 1 < l.size())
- sb.append(", ");
- if((i+1) % 5 == 0)
- sb.append("\n");
- }
- return sb.toString();
- }
-
- long getCount(Island island, EntityType ent) {
- long count = island.getWorld().getEntities().stream()
- .filter(e -> e.getType().equals(ent))
- .filter(e -> island.inIslandSpace(e.getLocation())).count();
- // Nether
- if (addon.getPlugin().getIWM().isNetherIslands(island.getWorld()) && addon.getPlugin().getIWM().getNetherWorld(island.getWorld()) != null) {
- count += addon.getPlugin().getIWM().getNetherWorld(island.getWorld()).getEntities().stream()
- .filter(e -> e.getType().equals(ent))
- .filter(e -> island.inIslandSpace(e.getLocation())).count();
- }
- // End
- if (addon.getPlugin().getIWM().isEndIslands(island.getWorld()) && addon.getPlugin().getIWM().getEndWorld(island.getWorld()) != null) {
- count += addon.getPlugin().getIWM().getEndWorld(island.getWorld()).getEntities().stream()
- .filter(e -> e.getType().equals(ent))
- .filter(e -> island.inIslandSpace(e.getLocation())).count();
- }
- return count;
- }
-
- long getCount(Island island, EntityGroup group) {
- long count = island.getWorld().getEntities().stream()
- .filter(e -> group.contains(e.getType()))
- .filter(e -> island.inIslandSpace(e.getLocation())).count();
- // Nether
- if (addon.getPlugin().getIWM().isNetherIslands(island.getWorld()) && addon.getPlugin().getIWM().getNetherWorld(island.getWorld()) != null) {
- count += addon.getPlugin().getIWM().getNetherWorld(island.getWorld()).getEntities().stream()
- .filter(e -> group.contains(e.getType()))
- .filter(e -> island.inIslandSpace(e.getLocation())).count();
- }
- // End
- if (addon.getPlugin().getIWM().isEndIslands(island.getWorld()) && addon.getPlugin().getIWM().getEndWorld(island.getWorld()) != null) {
- count += addon.getPlugin().getIWM().getEndWorld(island.getWorld()).getEntities().stream()
- .filter(e -> group.contains(e.getType()))
- .filter(e -> island.inIslandSpace(e.getLocation())).count();
- }
- return count;
- }
-}
+package world.bentobox.limits.commands.player;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.entity.EntityType;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.common.collect.ImmutableMap;
+
+import world.bentobox.bentobox.api.localization.TextVariables;
+import world.bentobox.bentobox.api.panels.PanelItem;
+import world.bentobox.bentobox.api.panels.Tab;
+import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.util.Util;
+import world.bentobox.limits.EntityGroup;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.objects.IslandBlockCount;
+
+/**
+ * @author tastybento
+ *
+ */
+public class LimitTab implements Tab {
+
+ enum SORT_BY {
+ A2Z,
+ Z2A
+ }
+
+ /**
+ * This maps the entity types to the icon that should be shown in the panel
+ * If the icon is null, then the entity type is not covered by the addon
+ */
+ private static final Map E2M = ImmutableMap.builder()
+ .put(EntityType.MOOSHROOM, Material.MOOSHROOM_SPAWN_EGG).put(EntityType.SNOW_GOLEM, Material.SNOW_BLOCK)
+ .put(EntityType.IRON_GOLEM, Material.IRON_BLOCK)
+ .put(EntityType.ILLUSIONER, Material.VILLAGER_SPAWN_EGG)
+ .put(EntityType.WITHER, Material.WITHER_SKELETON_SKULL)
+ //.put(EntityType.BOAT, Material.OAK_BOAT)
+ .put(EntityType.ARMOR_STAND, Material.ARMOR_STAND)
+ .put(EntityType.ITEM_FRAME, Material.ITEM_FRAME)
+ .put(EntityType.PAINTING, Material.PAINTING)
+ // Minecarts
+ .put(EntityType.TNT_MINECART, Material.TNT_MINECART).put(EntityType.CHEST_MINECART, Material.CHEST_MINECART)
+ .put(EntityType.COMMAND_BLOCK_MINECART, Material.COMMAND_BLOCK_MINECART)
+ .put(EntityType.FURNACE_MINECART, Material.FURNACE_MINECART)
+ .put(EntityType.HOPPER_MINECART, Material.HOPPER_MINECART)
+ .put(EntityType.SPAWNER_MINECART, Material.MINECART)
+ //.put(EntityType.CHEST_BOAT, Material.OAK_CHEST_BOAT)
+ .build();
+ // This is a map of blocks to Items
+ private static final Map B2M;
+ static {
+ ImmutableMap.Builder builder = ImmutableMap.builder()
+ .put(Material.POTATOES, Material.POTATO)
+ .put(Material.CARROTS, Material.CARROT)
+ .put(Material.BEETROOTS, Material.BEETROOT)
+ .put(Material.REDSTONE_WIRE, Material.REDSTONE).put(Material.MELON_STEM, Material.MELON)
+ .put(Material.PUMPKIN_STEM, Material.PUMPKIN);
+ // Block to Material icons
+ Optional.ofNullable(Material.getMaterial("SWEET_BERRY_BUSH")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("SWEET_BERRIES"))));
+ Optional.ofNullable(Material.getMaterial("BAMBOO_SAPLING")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("BAMBOO"))));
+ B2M = builder.build();
+ }
+
+ private final World world;
+ private final User user;
+ private final Limits addon;
+ private final List<@Nullable PanelItem> result;
+ private final SORT_BY sortBy;
+
+ public LimitTab(Limits addon, IslandBlockCount ibc, Map matLimits, Island island, World world, User user, SORT_BY sortBy) {
+ this.addon = addon;
+ this.world = world;
+ this.user = user;
+ this.sortBy = sortBy;
+ result = new ArrayList<>();
+ addMaterialIcons(ibc, matLimits);
+ addEntityLimits(ibc, island);
+ addEntityGroupLimits(ibc, island);
+ // Sort
+ if (sortBy == SORT_BY.Z2A) {
+ result.sort((o1, o2) -> o2.getName().compareTo(o1.getName()));
+ } else {
+ result.sort(Comparator.comparing(PanelItem::getName));
+ }
+
+ }
+
+ private void addEntityGroupLimits(IslandBlockCount ibc, Island island) {
+ // Entity group limits
+ Map groupMap = addon.getSettings().getGroupLimitDefinitions().stream().collect(Collectors.toMap(e -> e, EntityGroup::getLimit));
+ // Group by same loop up map
+ Map groupByName = groupMap.keySet().stream().collect(Collectors.toMap(EntityGroup::getName, e -> e));
+ // Merge in any permission-based limits
+ if (ibc == null) {
+ return;
+ }
+ ibc.getEntityGroupLimits().entrySet().stream()
+ .filter(e -> groupByName.containsKey(e.getKey()))
+ .forEach(e -> groupMap.put(groupByName.get(e.getKey()), e.getValue()));
+ // Update the group map for each group limit offset. If the value already exists add it
+ ibc.getEntityGroupLimitsOffset().forEach((key, value) -> {
+ if (groupByName.get(key) != null) {
+ groupMap.put(groupByName.get(key), (groupMap.getOrDefault(groupByName.get(key), 0) + value));
+ }
+ });
+ groupMap.forEach((v, limit) -> {
+ PanelItemBuilder pib = new PanelItemBuilder();
+ pib.name(user.getTranslation("island.limits.panel.entity-group-name-syntax", TextVariables.NAME,
+ v.getName()));
+ String description = "";
+ description += "(" + prettyNames(v) + ")\n";
+ pib.icon(v.getIcon());
+ long count = getCount(island, v);
+ String color = count >= limit ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color");
+ description += color
+ + user.getTranslation("island.limits.block-limit-syntax",
+ TextVariables.NUMBER, String.valueOf(count),
+ "[limit]", String.valueOf(limit));
+ pib.description(description);
+ result.add(pib.build());
+ });
+ }
+
+ private void addEntityLimits(IslandBlockCount ibc, Island island) {
+ // Entity limits
+ Map map = new HashMap<>(addon.getSettings().getLimits());
+ // Merge in any permission-based limits
+ if (ibc != null) {
+ map.putAll(ibc.getEntityLimits());
+ ibc.getEntityLimitsOffset().forEach((k,v) -> map.put(k, map.getOrDefault(k, 0) + v));
+ }
+
+ map.forEach((k,v) -> {
+ PanelItemBuilder pib = new PanelItemBuilder();
+ pib.name(user.getTranslation("island.limits.panel.entity-name-syntax", TextVariables.NAME,
+ Util.prettifyText(k.toString())));
+ Material m;
+ try {
+ if (E2M.containsKey(k)) {
+ m = E2M.get(k);
+ } else if (k.isAlive()) {
+ m = Material.valueOf(k + "_SPAWN_EGG");
+ } else {
+ // Regular material
+ m = Material.valueOf(k.toString());
+ }
+ } catch (Exception e) {
+ m = Material.BARRIER;
+ }
+ pib.icon(m);
+ long count = getCount(island, k);
+ String color = count >= v ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color");
+ pib.description(color
+ + user.getTranslation("island.limits.block-limit-syntax",
+ TextVariables.NUMBER, String.valueOf(count),
+ "[limit]", String.valueOf(v)));
+ result.add(pib.build());
+ });
+
+ }
+
+ private void addMaterialIcons(IslandBlockCount ibc, Map matLimits) {
+ // Material limits
+ for (Entry en : matLimits.entrySet()) {
+ PanelItemBuilder pib = new PanelItemBuilder();
+ pib.name(user.getTranslation("island.limits.panel.block-name-syntax", TextVariables.NAME,
+ Util.prettifyText(en.getKey().toString())));
+ // Adjust icon
+ pib.icon(B2M.getOrDefault(en.getKey(), en.getKey()));
+
+ int count = ibc == null ? 0 : ibc.getBlockCounts().getOrDefault(en.getKey(), 0);
+ int value = en.getValue();
+ String color = count >= value ? user.getTranslation("island.limits.max-color") : user.getTranslation("island.limits.regular-color");
+ pib.description(color
+ + user.getTranslation("island.limits.block-limit-syntax",
+ TextVariables.NUMBER, String.valueOf(count),
+ "[limit]", String.valueOf(value)));
+ result.add(pib.build());
+ }
+ }
+
+ @Override
+ public PanelItem getIcon() {
+ return new PanelItemBuilder().icon(Material.MAGENTA_GLAZED_TERRACOTTA).name(this.getName()).build();
+ }
+
+ @Override
+ public String getName() {
+ String sort = user.getTranslation(world, "island.limits.panel." + sortBy);
+ return user.getTranslation(world, "island.limits.panel.title-syntax", "[title]",
+ user.getTranslation(world, "limits.panel-title"), "[sort]", sort);
+ }
+
+ @Override
+ public List<@Nullable PanelItem> getPanelItems() {
+ return result;
+ }
+
+ @Override
+ public String getPermission() {
+ return "";
+ }
+
+ private String prettyNames(EntityGroup v) {
+ StringBuilder sb = new StringBuilder();
+ List l = new ArrayList<>(v.getTypes());
+ for(int i = 0; i < l.size(); i++)
+ {
+ sb.append(Util.prettifyText(l.get(i).toString()));
+ if (i + 1 < l.size())
+ sb.append(", ");
+ if((i+1) % 5 == 0)
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ long getCount(Island island, EntityType ent) {
+ long count = island.getWorld().getEntities().stream()
+ .filter(e -> e.getType().equals(ent))
+ .filter(e -> island.inIslandSpace(e.getLocation())).count();
+ // Nether
+ if (addon.getPlugin().getIWM().isNetherIslands(island.getWorld()) && addon.getPlugin().getIWM().getNetherWorld(island.getWorld()) != null) {
+ count += addon.getPlugin().getIWM().getNetherWorld(island.getWorld()).getEntities().stream()
+ .filter(e -> e.getType().equals(ent))
+ .filter(e -> island.inIslandSpace(e.getLocation())).count();
+ }
+ // End
+ if (addon.getPlugin().getIWM().isEndIslands(island.getWorld()) && addon.getPlugin().getIWM().getEndWorld(island.getWorld()) != null) {
+ count += addon.getPlugin().getIWM().getEndWorld(island.getWorld()).getEntities().stream()
+ .filter(e -> e.getType().equals(ent))
+ .filter(e -> island.inIslandSpace(e.getLocation())).count();
+ }
+ return count;
+ }
+
+ long getCount(Island island, EntityGroup group) {
+ long count = island.getWorld().getEntities().stream()
+ .filter(e -> group.contains(e.getType()))
+ .filter(e -> island.inIslandSpace(e.getLocation())).count();
+ // Nether
+ if (addon.getPlugin().getIWM().isNetherIslands(island.getWorld()) && addon.getPlugin().getIWM().getNetherWorld(island.getWorld()) != null) {
+ count += addon.getPlugin().getIWM().getNetherWorld(island.getWorld()).getEntities().stream()
+ .filter(e -> group.contains(e.getType()))
+ .filter(e -> island.inIslandSpace(e.getLocation())).count();
+ }
+ // End
+ if (addon.getPlugin().getIWM().isEndIslands(island.getWorld()) && addon.getPlugin().getIWM().getEndWorld(island.getWorld()) != null) {
+ count += addon.getPlugin().getIWM().getEndWorld(island.getWorld()).getEntities().stream()
+ .filter(e -> group.contains(e.getType()))
+ .filter(e -> island.inIslandSpace(e.getLocation())).count();
+ }
+ return count;
+ }
+}
diff --git a/src/main/java/world/bentobox/limits/commands/player/PlayerCommand.java b/src/main/java/world/bentobox/limits/commands/player/PlayerCommand.java
index e0d9add..0031a35 100644
--- a/src/main/java/world/bentobox/limits/commands/player/PlayerCommand.java
+++ b/src/main/java/world/bentobox/limits/commands/player/PlayerCommand.java
@@ -1,71 +1,71 @@
-package world.bentobox.limits.commands.player;
-
-import java.util.List;
-import java.util.Optional;
-
-import world.bentobox.bentobox.api.addons.GameModeAddon;
-import world.bentobox.bentobox.api.commands.CompositeCommand;
-import world.bentobox.bentobox.api.user.User;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.limits.Limits;
-
-/**
- * User command for limits
- * @author tastybento
- *
- */
-public class PlayerCommand extends CompositeCommand {
-
- private final Limits addon;
-
- /**
- * Top level command
- * @param addon - addon
- */
- public PlayerCommand(Limits addon, CompositeCommand parent) {
- super(parent, "limits");
- this.addon = addon;
- new RecountCommand(addon, this);
- }
-
- /* (non-Javadoc)
- * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup()
- */
- @Override
- public void setup() {
- this.setPermission("limits.player.limits");
- this.setOnlyPlayer(true);
- this.setParametersHelp("island.limits.parameters");
- this.setDescription("island.limits.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.isEmpty()) {
- showHelp(this, user);
- return false;
- } else {
- // Report the limit for the island, which is governed by the owner of the island
- Optional opIsland = getIslands().getIslandAt(user.getLocation());
- if (opIsland.isEmpty()) {
- user.sendMessage("island.limits.errors.not-on-island");
- return false;
- }
- Island island = opIsland.get();
- if (!island.getWorld().equals(getWorld())) {
- user.sendMessage("general.errors.wrong-world");
- return false;
- }
- if (island.getOwner() == null) {
- user.sendMessage("island.limits.errors.no-owner");
- return false;
- }
- new LimitPanel(addon).showLimits((GameModeAddon) getAddon(), user, island.getOwner());
- return true;
- }
- }
-
-}
+package world.bentobox.limits.commands.player;
+
+import java.util.List;
+import java.util.Optional;
+
+import world.bentobox.bentobox.api.addons.GameModeAddon;
+import world.bentobox.bentobox.api.commands.CompositeCommand;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.limits.Limits;
+
+/**
+ * User command for limits
+ * @author tastybento
+ *
+ */
+public class PlayerCommand extends CompositeCommand {
+
+ private final Limits addon;
+
+ /**
+ * Top level command
+ * @param addon - addon
+ */
+ public PlayerCommand(Limits addon, CompositeCommand parent) {
+ super(parent, "limits");
+ this.addon = addon;
+ new RecountCommand(addon, this);
+ }
+
+ /* (non-Javadoc)
+ * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup()
+ */
+ @Override
+ public void setup() {
+ this.setPermission("limits.player.limits");
+ this.setOnlyPlayer(true);
+ this.setParametersHelp("island.limits.parameters");
+ this.setDescription("island.limits.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.isEmpty()) {
+ showHelp(this, user);
+ return false;
+ } else {
+ // Report the limit for the island, which is governed by the owner of the island
+ Optional opIsland = getIslands().getIslandAt(user.getLocation());
+ if (opIsland.isEmpty()) {
+ user.sendMessage("island.limits.errors.not-on-island");
+ return false;
+ }
+ Island island = opIsland.get();
+ if (!island.getWorld().equals(getWorld())) {
+ user.sendMessage("general.errors.wrong-world");
+ return false;
+ }
+ if (island.getOwner() == null) {
+ user.sendMessage("island.limits.errors.no-owner");
+ return false;
+ }
+ new LimitPanel(addon).showLimits((GameModeAddon) getAddon(), user, island.getOwner());
+ return true;
+ }
+ }
+
+}
diff --git a/src/main/java/world/bentobox/limits/commands/player/RecountCommand.java b/src/main/java/world/bentobox/limits/commands/player/RecountCommand.java
index 9f330b5..4b466b4 100644
--- a/src/main/java/world/bentobox/limits/commands/player/RecountCommand.java
+++ b/src/main/java/world/bentobox/limits/commands/player/RecountCommand.java
@@ -1,77 +1,77 @@
-package world.bentobox.limits.commands.player;
-
-import java.util.List;
-
-import org.eclipse.jdt.annotation.Nullable;
-
-import world.bentobox.bentobox.api.commands.CompositeCommand;
-import world.bentobox.bentobox.api.user.User;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.calculators.Pipeliner;
-
-/**
- *
- * @author tastybento
- */
-public class RecountCommand extends CompositeCommand {
-
- private final Limits addon;
- private @Nullable Island island;
-
- /**
- * Player command to do a recount. Has a cooldown
- *
- * @param addon - addon
- */
- public RecountCommand(Limits addon, CompositeCommand parent) {
- super(parent, "recount");
- this.addon = addon;
- }
-
- /* (non-Javadoc)
- * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup()
- */
- @Override
- public void setup() {
- this.setPermission("limits.player.recount");
- this.setOnlyPlayer(true);
- this.setParametersHelp("island.limits.recount.parameters");
- this.setDescription("island.limits.recount.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 canExecute(User user, String label, List args) {
- if (!args.isEmpty()) {
- showHelp(this, user);
- return false;
- }
- island = addon.getIslands().getIsland(getWorld(), user);
- if (island == null) {
- user.sendMessage("general.errors.no-island");
- return false;
- }
- return !checkCooldown(user);
- }
- @Override
- public boolean execute(User user, String label, List args) {
- // Set cooldown
- setCooldown(user.getUniqueId(), addon.getConfig().getInt("cooldown", 120));
- user.sendMessage("island.limits.recount.now-recounting");
- new Pipeliner(addon).addIsland(island).thenAccept(results -> {
- if (results == null) {
- user.sendMessage("island.limits.recount.in-progress");
- } else {
- switch (results.getState()) {
- case TIMEOUT -> user.sendMessage("admin.limits.calc.timeout");
- default -> user.sendMessage("admin.limits.calc.finished");
- }
- }
- });
- return true;
- }
-
-}
+package world.bentobox.limits.commands.player;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import world.bentobox.bentobox.api.commands.CompositeCommand;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.calculators.Pipeliner;
+
+/**
+ *
+ * @author tastybento
+ */
+public class RecountCommand extends CompositeCommand {
+
+ private final Limits addon;
+ private @Nullable Island island;
+
+ /**
+ * Player command to do a recount. Has a cooldown
+ *
+ * @param addon - addon
+ */
+ public RecountCommand(Limits addon, CompositeCommand parent) {
+ super(parent, "recount");
+ this.addon = addon;
+ }
+
+ /* (non-Javadoc)
+ * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#setup()
+ */
+ @Override
+ public void setup() {
+ this.setPermission("limits.player.recount");
+ this.setOnlyPlayer(true);
+ this.setParametersHelp("island.limits.recount.parameters");
+ this.setDescription("island.limits.recount.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 canExecute(User user, String label, List args) {
+ if (!args.isEmpty()) {
+ showHelp(this, user);
+ return false;
+ }
+ island = addon.getIslands().getIsland(getWorld(), user);
+ if (island == null) {
+ user.sendMessage("general.errors.no-island");
+ return false;
+ }
+ return !checkCooldown(user);
+ }
+ @Override
+ public boolean execute(User user, String label, List args) {
+ // Set cooldown
+ setCooldown(user.getUniqueId(), addon.getConfig().getInt("cooldown", 120));
+ user.sendMessage("island.limits.recount.now-recounting");
+ new Pipeliner(addon).addIsland(island).thenAccept(results -> {
+ if (results == null) {
+ user.sendMessage("island.limits.recount.in-progress");
+ } else {
+ switch (results.getState()) {
+ case TIMEOUT -> user.sendMessage("admin.limits.calc.timeout");
+ default -> user.sendMessage("admin.limits.calc.finished");
+ }
+ }
+ });
+ return true;
+ }
+
+}
diff --git a/src/main/java/world/bentobox/limits/events/LimitsJoinPermCheckEvent.java b/src/main/java/world/bentobox/limits/events/LimitsJoinPermCheckEvent.java
index b156020..a1a03d9 100644
--- a/src/main/java/world/bentobox/limits/events/LimitsJoinPermCheckEvent.java
+++ b/src/main/java/world/bentobox/limits/events/LimitsJoinPermCheckEvent.java
@@ -1,121 +1,121 @@
-package world.bentobox.limits.events;
-
-import org.bukkit.entity.Player;
-import org.bukkit.event.Cancellable;
-import org.bukkit.event.HandlerList;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-
-import world.bentobox.bentobox.api.events.BentoBoxEvent;
-import world.bentobox.limits.objects.IslandBlockCount;
-
-/**
- * Fired when a player joins the server and before limit settings for their island are changed based
- * on the player's permissions. If cancelled, no limit settings will be made.
- * @author tastybento
- *
- */
-public class LimitsJoinPermCheckEvent extends BentoBoxEvent implements Cancellable {
-
- private final Player player;
- private final String islandId;
- private IslandBlockCount ibc;
- private boolean cancel;
- private boolean ignorePerms;
- private static final HandlerList handlers = new HandlerList();
-
- @Override
- public @NonNull HandlerList getHandlers() {
- return getHandlerList();
- }
-
- public static HandlerList getHandlerList() {
- return handlers;
- }
-
- /**
- * Fired when a player joins the server and before limit settings for their island are changed based
- * on the player's permissions. If cancelled, no limit settings will be made.
- * @param player - player joining
- * @param islandId - the unique island id.
- * @param ibc - IslandBlockCount object for this island
- */
- public LimitsJoinPermCheckEvent(@NonNull Player player, @NonNull String islandId, @Nullable IslandBlockCount ibc) {
- super();
- this.player = player;
- this.islandId = islandId;
- this.ibc = ibc;
- }
-
-
- /**
- * Get the player joining
- * @return the player
- */
- @NonNull
- public Player getPlayer() {
- return player;
- }
-
-
- /**
- * Get the unique island id. Use the islands manager to obtain the island
- * @return the islandId
- */
- @NonNull
- public String getIslandId() {
- return islandId;
- }
-
-
- /**
- * Get the island block count
- * @return the ibc
- */
- @Nullable
- public IslandBlockCount getIbc() {
- return ibc;
- }
-
-
- /**
- * Set the island block count to a specific setting
- * @param ibc the ibc to set
- */
- public void setIbc(@Nullable IslandBlockCount ibc) {
- this.ibc = ibc;
- }
-
-
- @Override
- public boolean isCancelled() {
- return cancel;
- }
-
- @Override
- public void setCancelled(boolean cancel) {
- this.cancel = cancel;
-
- }
-
-
- /**
- * Check if player's perms should be considered or not
- * @return the ignorePerms
- */
- public boolean isIgnorePerms() {
- return ignorePerms;
- }
-
-
- /**
- * Ignore player's perms. This differs to canceling the event in that the IslandBlockCount will be used if given via
- * {@link #setIbc(IslandBlockCount ibc)}
- * @param ignorePerms the ignorePerms to set
- */
- public void setIgnorePerms(boolean ignorePerms) {
- this.ignorePerms = ignorePerms;
- }
-
-
-}
+package world.bentobox.limits.events;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.HandlerList;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import world.bentobox.bentobox.api.events.BentoBoxEvent;
+import world.bentobox.limits.objects.IslandBlockCount;
+
+/**
+ * Fired when a player joins the server and before limit settings for their island are changed based
+ * on the player's permissions. If cancelled, no limit settings will be made.
+ * @author tastybento
+ *
+ */
+public class LimitsJoinPermCheckEvent extends BentoBoxEvent implements Cancellable {
+
+ private final Player player;
+ private final String islandId;
+ private IslandBlockCount ibc;
+ private boolean cancel;
+ private boolean ignorePerms;
+ private static final HandlerList handlers = new HandlerList();
+
+ @Override
+ public @NonNull HandlerList getHandlers() {
+ return getHandlerList();
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ /**
+ * Fired when a player joins the server and before limit settings for their island are changed based
+ * on the player's permissions. If cancelled, no limit settings will be made.
+ * @param player - player joining
+ * @param islandId - the unique island id.
+ * @param ibc - IslandBlockCount object for this island
+ */
+ public LimitsJoinPermCheckEvent(@NonNull Player player, @NonNull String islandId, @Nullable IslandBlockCount ibc) {
+ super();
+ this.player = player;
+ this.islandId = islandId;
+ this.ibc = ibc;
+ }
+
+
+ /**
+ * Get the player joining
+ * @return the player
+ */
+ @NonNull
+ public Player getPlayer() {
+ return player;
+ }
+
+
+ /**
+ * Get the unique island id. Use the islands manager to obtain the island
+ * @return the islandId
+ */
+ @NonNull
+ public String getIslandId() {
+ return islandId;
+ }
+
+
+ /**
+ * Get the island block count
+ * @return the ibc
+ */
+ @Nullable
+ public IslandBlockCount getIbc() {
+ return ibc;
+ }
+
+
+ /**
+ * Set the island block count to a specific setting
+ * @param ibc the ibc to set
+ */
+ public void setIbc(@Nullable IslandBlockCount ibc) {
+ this.ibc = ibc;
+ }
+
+
+ @Override
+ public boolean isCancelled() {
+ return cancel;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ this.cancel = cancel;
+
+ }
+
+
+ /**
+ * Check if player's perms should be considered or not
+ * @return the ignorePerms
+ */
+ public boolean isIgnorePerms() {
+ return ignorePerms;
+ }
+
+
+ /**
+ * Ignore player's perms. This differs to canceling the event in that the IslandBlockCount will be used if given via
+ * {@link #setIbc(IslandBlockCount ibc)}
+ * @param ignorePerms the ignorePerms to set
+ */
+ public void setIgnorePerms(boolean ignorePerms) {
+ this.ignorePerms = ignorePerms;
+ }
+
+
+}
diff --git a/src/main/java/world/bentobox/limits/events/LimitsPermCheckEvent.java b/src/main/java/world/bentobox/limits/events/LimitsPermCheckEvent.java
index 17d40e8..e0c6e25 100644
--- a/src/main/java/world/bentobox/limits/events/LimitsPermCheckEvent.java
+++ b/src/main/java/world/bentobox/limits/events/LimitsPermCheckEvent.java
@@ -1,116 +1,116 @@
-package world.bentobox.limits.events;
-
-import org.bukkit.Material;
-import org.bukkit.entity.EntityType;
-import org.bukkit.entity.Player;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-
-import world.bentobox.limits.EntityGroup;
-import world.bentobox.limits.objects.IslandBlockCount;
-
-/**
- * Fired when a player joins the server and for each perm-based limit setting.
- * If cancelled, no limit settings will be made.
- * Settings can be adjusted and will be used.
- * @author tastybento
- *
- */
-public class LimitsPermCheckEvent extends LimitsJoinPermCheckEvent {
-
- private @Nullable EntityGroup entityGroup;
- private @Nullable EntityType entityType;
- private @Nullable Material material;
- private int value;
-
- /**
- * Fired when a player joins the server and for each perm-based limit setting.
- * If cancelled, no limit settings will be made.
- * Settings can be adjusted and will be used.
- * @param player - player joining
- * @param islandId - the unique island id.
- * @param ibc - IslandBlockCount object for this island
- * @param material - material being limited, or null
- * @param entityType - entity type being limited, or null
- * @param entgroup - entity group being limited, or null
- * @param value - numeric limit given by the perm
- */
- public LimitsPermCheckEvent(@NonNull Player player,
- @NonNull String islandId,
- @Nullable IslandBlockCount ibc,
- @Nullable EntityGroup entgroup,
- @Nullable EntityType entityType,
- @Nullable Material material,
- int value) {
- super(player, islandId, ibc);
- this.entityGroup = entgroup;
- this.entityType = entityType;
- this.material = material;
- this.value = value;
- }
-
- /**
- * @return the entityGroup
- */
- public @Nullable EntityGroup getEntityGroup() {
- return entityGroup;
- }
-
-
- /**
- * @param entityGroup the entityGroup to set
- */
- public void setEntityGroup(@Nullable EntityGroup entityGroup) {
- this.entityGroup = entityGroup;
- }
-
-
- /**
- * @return the entityType
- */
- public @Nullable EntityType getEntityType() {
- return entityType;
- }
-
-
- /**
- * @param entityType the entityType to set
- */
- public void setEntityType(@Nullable EntityType entityType) {
- this.entityType = entityType;
- }
-
-
- /**
- * @return the material
- */
- public @Nullable Material getMaterial() {
- return material;
- }
-
-
- /**
- * @param material the material to set
- */
- public void setMaterial(@Nullable Material material) {
- this.material = material;
- }
-
-
- /**
- * @return the value
- */
- public int getValue() {
- return value;
- }
-
-
- /**
- * @param value the value to set
- */
- public void setValue(int value) {
- this.value = value;
- }
-
-
-}
+package world.bentobox.limits.events;
+
+import org.bukkit.Material;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import world.bentobox.limits.EntityGroup;
+import world.bentobox.limits.objects.IslandBlockCount;
+
+/**
+ * Fired when a player joins the server and for each perm-based limit setting.
+ * If cancelled, no limit settings will be made.
+ * Settings can be adjusted and will be used.
+ * @author tastybento
+ *
+ */
+public class LimitsPermCheckEvent extends LimitsJoinPermCheckEvent {
+
+ private @Nullable EntityGroup entityGroup;
+ private @Nullable EntityType entityType;
+ private @Nullable Material material;
+ private int value;
+
+ /**
+ * Fired when a player joins the server and for each perm-based limit setting.
+ * If cancelled, no limit settings will be made.
+ * Settings can be adjusted and will be used.
+ * @param player - player joining
+ * @param islandId - the unique island id.
+ * @param ibc - IslandBlockCount object for this island
+ * @param material - material being limited, or null
+ * @param entityType - entity type being limited, or null
+ * @param entgroup - entity group being limited, or null
+ * @param value - numeric limit given by the perm
+ */
+ public LimitsPermCheckEvent(@NonNull Player player,
+ @NonNull String islandId,
+ @Nullable IslandBlockCount ibc,
+ @Nullable EntityGroup entgroup,
+ @Nullable EntityType entityType,
+ @Nullable Material material,
+ int value) {
+ super(player, islandId, ibc);
+ this.entityGroup = entgroup;
+ this.entityType = entityType;
+ this.material = material;
+ this.value = value;
+ }
+
+ /**
+ * @return the entityGroup
+ */
+ public @Nullable EntityGroup getEntityGroup() {
+ return entityGroup;
+ }
+
+
+ /**
+ * @param entityGroup the entityGroup to set
+ */
+ public void setEntityGroup(@Nullable EntityGroup entityGroup) {
+ this.entityGroup = entityGroup;
+ }
+
+
+ /**
+ * @return the entityType
+ */
+ public @Nullable EntityType getEntityType() {
+ return entityType;
+ }
+
+
+ /**
+ * @param entityType the entityType to set
+ */
+ public void setEntityType(@Nullable EntityType entityType) {
+ this.entityType = entityType;
+ }
+
+
+ /**
+ * @return the material
+ */
+ public @Nullable Material getMaterial() {
+ return material;
+ }
+
+
+ /**
+ * @param material the material to set
+ */
+ public void setMaterial(@Nullable Material material) {
+ this.material = material;
+ }
+
+
+ /**
+ * @return the value
+ */
+ public int getValue() {
+ return value;
+ }
+
+
+ /**
+ * @param value the value to set
+ */
+ public void setValue(int value) {
+ this.value = value;
+ }
+
+
+}
diff --git a/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java b/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java
index 35e2ead..0103274 100644
--- a/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java
+++ b/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java
@@ -1,501 +1,506 @@
-package world.bentobox.limits.listeners;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-
-import org.bukkit.Bukkit;
-import org.bukkit.Material;
-import org.bukkit.World;
-import org.bukkit.block.Block;
-import org.bukkit.block.BlockFace;
-import org.bukkit.block.data.BlockData;
-import org.bukkit.block.data.type.TechnicalPiston;
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.event.Cancellable;
-import org.bukkit.event.Event;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.EventPriority;
-import org.bukkit.event.Listener;
-import org.bukkit.event.block.Action;
-import org.bukkit.event.block.BlockBreakEvent;
-import org.bukkit.event.block.BlockBurnEvent;
-import org.bukkit.event.block.BlockExplodeEvent;
-import org.bukkit.event.block.BlockFadeEvent;
-import org.bukkit.event.block.BlockFormEvent;
-import org.bukkit.event.block.BlockFromToEvent;
-import org.bukkit.event.block.BlockGrowEvent;
-import org.bukkit.event.block.BlockMultiPlaceEvent;
-import org.bukkit.event.block.BlockPlaceEvent;
-import org.bukkit.event.block.BlockSpreadEvent;
-import org.bukkit.event.block.EntityBlockFormEvent;
-import org.bukkit.event.block.LeavesDecayEvent;
-import org.bukkit.event.entity.EntityChangeBlockEvent;
-import org.bukkit.event.entity.EntityExplodeEvent;
-import org.bukkit.event.player.PlayerInteractEvent;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-
-import world.bentobox.bentobox.api.events.island.IslandDeleteEvent;
-import world.bentobox.bentobox.api.localization.TextVariables;
-import world.bentobox.bentobox.api.user.User;
-import world.bentobox.bentobox.database.Database;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.bentobox.util.Util;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.objects.IslandBlockCount;
-
-/**
- * @author tastybento
- *
- */
-public class BlockLimitsListener implements Listener {
-
- /**
- * Blocks that are not counted
- */
- private static final List DO_NOT_COUNT = Arrays.asList(Material.LAVA, Material.WATER, Material.AIR, Material.FIRE, Material.END_PORTAL, Material.NETHER_PORTAL);
- private static final List STACKABLE;
-
- static {
- List stackable = new ArrayList<>();
- stackable.add(Material.SUGAR_CANE);
- Optional.ofNullable(Material.getMaterial("BAMBOO")).ifPresent(stackable::add);
- STACKABLE = Collections.unmodifiableList(stackable);
- }
-
- /**
- * Save every 10 blocks of change
- */
- private static final Integer CHANGE_LIMIT = 9;
- private final Limits addon;
- private final Map islandCountMap = new HashMap<>();
- private final Map saveMap = new HashMap<>();
- private final Database handler;
- private final Map> worldLimitMap = new HashMap<>();
- private Map defaultLimitMap = new EnumMap<>(Material.class);
-
- public BlockLimitsListener(Limits addon) {
- this.addon = addon;
- handler = new Database<>(addon, IslandBlockCount.class);
- List toBeDeleted = new ArrayList<>();
- handler.loadObjects().forEach(ibc -> {
- // Clean up
- if (addon.isCoveredGameMode(ibc.getGameMode())) {
- ibc.getBlockCounts().keySet().removeIf(DO_NOT_COUNT::contains);
- // Store
- islandCountMap.put(ibc.getUniqueId(), ibc);
- } else {
- toBeDeleted.add(ibc.getUniqueId());
- }
- });
- toBeDeleted.forEach(handler::deleteID);
- loadAllLimits();
- }
-
- /**
- * Loads the default and world-specific limits
- */
- private void loadAllLimits() {
- // Load the default limits
- addon.log("Loading default limits");
- if (addon.getConfig().isConfigurationSection("blocklimits")) {
- ConfigurationSection limitConfig = addon.getConfig().getConfigurationSection("blocklimits");
- defaultLimitMap = loadLimits(Objects.requireNonNull(limitConfig));
- }
-
- // Load specific worlds
- if (addon.getConfig().isConfigurationSection("worlds")) {
- ConfigurationSection worlds = addon.getConfig().getConfigurationSection("worlds");
- for (String worldName : Objects.requireNonNull(worlds).getKeys(false)) {
- World world = Bukkit.getWorld(worldName);
- if (world != null && addon.inGameModeWorld(world)) {
- addon.log("Loading limits for " + world.getName());
- worldLimitMap.putIfAbsent(world, new HashMap<>());
- ConfigurationSection matsConfig = worlds.getConfigurationSection(worldName);
- worldLimitMap.put(world, loadLimits(Objects.requireNonNull(matsConfig)));
- }
- }
- }
-
- }
-
- /**
- * Loads limit map from configuration section
- *
- * @param cs - configuration section
- * @return limit map
- */
- private Map loadLimits(ConfigurationSection cs) {
- Map mats = new EnumMap<>(Material.class);
- for (String material : cs.getKeys(false)) {
- Material mat = Material.getMaterial(material);
- if (mat != null && mat.isBlock() && !DO_NOT_COUNT.contains(mat)) {
- mats.put(mat, cs.getInt(material));
- addon.log("Limit " + mat + " to " + cs.getInt(material));
- } else {
- addon.logError("Material " + material + " is not a valid block. Skipping...");
- }
- }
- return mats;
- }
-
-
- /**
- * Save the count database completely
- */
- public void save() {
- islandCountMap.values().stream().filter(IslandBlockCount::isChanged).forEach(handler::saveObjectAsync);
- }
-
- // Player-related events
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockPlaceEvent e) {
- notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType());
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockBreakEvent e) {
- handleBreak(e, e.getBlock());
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onTurtleEggBreak(PlayerInteractEvent e) {
- if (e.getAction().equals(Action.PHYSICAL) && e.getClickedBlock() != null && e.getClickedBlock().getType().equals(Material.TURTLE_EGG)) {
- handleBreak(e, e.getClickedBlock());
- }
- }
-
- private void handleBreak(Event e, Block b) {
- if (!addon.inGameModeWorld(b.getWorld())) {
- return;
- }
- Material mat = b.getType();
- // Check for stackable plants
- if (STACKABLE.contains(b.getType())) {
- // Check for blocks above
- Block block = b;
- while(block.getRelative(BlockFace.UP).getType().equals(mat) && block.getY() < b.getWorld().getMaxHeight()) {
- block = block.getRelative(BlockFace.UP);
- process(block, false);
- }
- }
- process(b, false);
- // Player breaks a block and there was a redstone dust/repeater/... above
- if (b.getRelative(BlockFace.UP).getType() == Material.REDSTONE_WIRE || b.getRelative(BlockFace.UP).getType() == Material.REPEATER || b.getRelative(BlockFace.UP).getType() == Material.COMPARATOR || b.getRelative(BlockFace.UP).getType() == Material.REDSTONE_TORCH) {
- process(b.getRelative(BlockFace.UP), false);
- }
- if (b.getRelative(BlockFace.EAST).getType() == Material.REDSTONE_WALL_TORCH) {
- process(b.getRelative(BlockFace.EAST), false);
- }
- if (b.getRelative(BlockFace.WEST).getType() == Material.REDSTONE_WALL_TORCH) {
- process(b.getRelative(BlockFace.WEST), false);
- }
- if (b.getRelative(BlockFace.SOUTH).getType() == Material.REDSTONE_WALL_TORCH) {
- process(b.getRelative(BlockFace.SOUTH), false);
- }
- if (b.getRelative(BlockFace.NORTH).getType() == Material.REDSTONE_WALL_TORCH) {
- process(b.getRelative(BlockFace.NORTH), false);
- }
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockMultiPlaceEvent e) {
- notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType());
- }
-
- /**
- * Cancel the event and notify the user of failure
- * @param e event
- * @param user user
- * @param limit maximum limit allowed
- * @param m material
- */
- private void notify(Cancellable e, User user, int limit, Material m) {
- if (limit > -1) {
- user.notify("block-limits.hit-limit",
- "[material]", Util.prettifyText(m.toString()),
- TextVariables.NUMBER, String.valueOf(limit));
- e.setCancelled(true);
- }
- }
-
- // Non-player events
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockBurnEvent e) {
- process(e.getBlock(), false);
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockExplodeEvent e) {
- e.blockList().forEach(b -> process(b, false));
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockFadeEvent e) {
- process(e.getBlock(), false);
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockFormEvent e) {
- process(e.getBlock(), true);
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockSpreadEvent e) {
- process(e.getBlock(), true);
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(EntityBlockFormEvent e) {
- process(e.getBlock(), true);
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockGrowEvent e) {
- if (process(e.getNewState().getBlock(), true) > -1) {
- e.setCancelled(true);
- e.getBlock().getWorld().getBlockAt(e.getBlock().getLocation()).setBlockData(e.getBlock().getBlockData());
- } else {
- process(e.getBlock(), false);
- }
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(LeavesDecayEvent e) {
- process(e.getBlock(), false);
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(EntityExplodeEvent e) {
- e.blockList().forEach(b -> process(b, false));
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(EntityChangeBlockEvent e) {
- process(e.getBlock(), false);
- if (e.getBlock().getType().equals(Material.FARMLAND)) {
- process(e.getBlock().getRelative(BlockFace.UP), false);
- }
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onBlock(BlockFromToEvent e) {
- if (e.getBlock().isLiquid()
- && (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);
- }
- }
-
- /**
- * Return equivalents. Maps things like wall materials to their non-wall equivalents
- * @param b block data
- * @return material that matches the block data
- */
- public Material fixMaterial(BlockData b) {
- Material mat = b.getMaterial();
-
- if (mat == Material.REDSTONE_WALL_TORCH) {
- return Material.REDSTONE_TORCH;
- } else if (mat == Material.WALL_TORCH) {
- return Material.TORCH;
- } else if (mat == Material.ZOMBIE_WALL_HEAD) {
- return Material.ZOMBIE_HEAD;
- } else if (mat == Material.CREEPER_WALL_HEAD) {
- return Material.CREEPER_HEAD;
- } else if (mat == Material.PLAYER_WALL_HEAD) {
- return Material.PLAYER_HEAD;
- } else if (mat == Material.DRAGON_WALL_HEAD) {
- return Material.DRAGON_HEAD;
- } else if (mat == Material.BAMBOO_SAPLING) {
- return Material.BAMBOO;
- } else if (mat == Material.PISTON_HEAD || mat == Material.MOVING_PISTON) {
- TechnicalPiston tp = (TechnicalPiston) b;
- if (tp.getType() == TechnicalPiston.Type.NORMAL) {
- return Material.PISTON;
- } else {
- return Material.STICKY_PISTON;
- }
- }
- return mat;
- }
-
- /**
- * Check if a block can be
- *
- * @param b - block
- * @param add - true to add a block, false to remove
- * @return limit amount if over limit, or -1 if no limitation
- */
- private int process(Block b, boolean add) {
- if (DO_NOT_COUNT.contains(fixMaterial(b.getBlockData())) || !addon.inGameModeWorld(b.getWorld())) {
- return -1;
- }
- // Check if on island
- return addon.getIslands().getIslandAt(b.getLocation()).map(i -> {
- String id = i.getUniqueId();
- String gameMode = addon.getGameModeName(b.getWorld());
- if (gameMode.isEmpty()) {
- // Invalid world
- return -1;
- }
- // Ignore the center block - usually bedrock, but for AOneBlock it's the magic block
- if (addon.getConfig().getBoolean("ignore-center-block", true) && i.getCenter().equals(b.getLocation())) {
- return -1;
- }
- islandCountMap.putIfAbsent(id, new IslandBlockCount(id, gameMode));
- if (add) {
- // Check limit
- int limit = checkLimit(b.getWorld(), fixMaterial(b.getBlockData()), id);
- if (limit > -1) {
- return limit;
- }
- islandCountMap.get(id).add(fixMaterial(b.getBlockData()));
- } else {
- if (islandCountMap.containsKey(id)) {
- islandCountMap.get(id).remove(fixMaterial(b.getBlockData()));
- }
- }
- updateSaveMap(id);
- return -1;
- }).orElse(-1);
- }
-
- /**
- * Removed a block from any island limit count
- * @param b - block to remove
- */
- public void removeBlock(Block b) {
- // Get island
- addon.getIslands().getIslandAt(b.getLocation()).ifPresent(i -> {
- String id = i.getUniqueId();
- String gameMode = addon.getGameModeName(b.getWorld());
- if (gameMode.isEmpty()) {
- // Invalid world
- return;
- }
- islandCountMap.computeIfAbsent(id, k -> new IslandBlockCount(id, gameMode)).remove(fixMaterial(b.getBlockData()));
- updateSaveMap(id);
- });
- }
- private void updateSaveMap(String id) {
- saveMap.putIfAbsent(id, 0);
- if (saveMap.merge(id, 1, Integer::sum) > CHANGE_LIMIT) {
- handler.saveObjectAsync(islandCountMap.get(id));
- saveMap.remove(id);
- }
- }
-
-
- /**
- * Check if this material is at its limit for world on this island
- *
- * @param w - world
- * @param m - material
- * @param id - island id
- * @return limit amount if at limit or -1 if no limit
- */
- private int checkLimit(World w, Material m, String id) {
- // Check island limits
- IslandBlockCount ibc = islandCountMap.get(id);
- if (ibc.isBlockLimited(m)) {
- return ibc.isAtLimit(m) ? ibc.getBlockLimit(m) + ibc.getBlockLimitOffset(m) : -1;
- }
- // Check specific world limits
- if (worldLimitMap.containsKey(w) && worldLimitMap.get(w).containsKey(m)) {
- // Material is overridden in world
- return ibc.isAtLimit(m, worldLimitMap.get(w).get(m)) ? worldLimitMap.get(w).get(m) + ibc.getBlockLimitOffset(m) : -1;
- }
- // Check default limit map
- if (defaultLimitMap.containsKey(m) && ibc.isAtLimit(m, defaultLimitMap.get(m))) {
- return defaultLimitMap.get(m) + ibc.getBlockLimitOffset(m);
- }
- // No limit
- return -1;
- }
-
- /**
- * Gets an aggregate map of the limits for this island
- *
- * @param w - world
- * @param id - island id
- * @return map of limits for materials
- */
- public Map getMaterialLimits(World w, String id) {
- // Merge limits
- Map result = new EnumMap<>(Material.class);
- // Default
- result.putAll(defaultLimitMap);
- // World
- if (worldLimitMap.containsKey(w)) {
- result.putAll(worldLimitMap.get(w));
- }
- // Island
- if (islandCountMap.containsKey(id)) {
- IslandBlockCount islandBlockCount = islandCountMap.get(id);
- result.putAll(islandBlockCount.getBlockLimits());
-
- // Add offsets to the every limit.
- islandBlockCount.getBlockLimitsOffset().forEach((material, offset) ->
- result.put(material, result.getOrDefault(material, 0) + offset));
- }
- return result;
- }
-
- /**
- * Removes island from the database
- *
- * @param e - island delete event
- */
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onIslandDelete(IslandDeleteEvent e) {
- islandCountMap.remove(e.getIsland().getUniqueId());
- saveMap.remove(e.getIsland().getUniqueId());
- if (handler.objectExists(e.getIsland().getUniqueId())) {
- handler.deleteID(e.getIsland().getUniqueId());
- }
- }
-
- /**
- * Set the island block count values
- *
- * @param islandId - island unique id
- * @param ibc - island block count
- */
- public void setIsland(String islandId, IslandBlockCount ibc) {
- islandCountMap.put(islandId, ibc);
- handler.saveObjectAsync(ibc);
- }
-
- /**
- * Get the island block count
- *
- * @param islandId - island unique id
- * @return island block count or null if there is none yet
- */
- @Nullable
- public IslandBlockCount getIsland(String islandId) {
- return islandCountMap.get(islandId);
- }
-
- /**
- * Get the island block count for island and make one if it does not exist
- * @param island island
- * @return island block count
- */
- @NonNull
- public IslandBlockCount getIsland(Island island) {
- return islandCountMap.computeIfAbsent(island.getUniqueId(), k -> new IslandBlockCount(k, island.getGameMode()));
- }
-
-}
+package world.bentobox.limits.listeners;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.block.data.type.TechnicalPiston;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockBurnEvent;
+import org.bukkit.event.block.BlockExplodeEvent;
+import org.bukkit.event.block.BlockFadeEvent;
+import org.bukkit.event.block.BlockFormEvent;
+import org.bukkit.event.block.BlockFromToEvent;
+import org.bukkit.event.block.BlockGrowEvent;
+import org.bukkit.event.block.BlockMultiPlaceEvent;
+import org.bukkit.event.block.BlockPlaceEvent;
+import org.bukkit.event.block.BlockSpreadEvent;
+import org.bukkit.event.block.EntityBlockFormEvent;
+import org.bukkit.event.block.LeavesDecayEvent;
+import org.bukkit.event.entity.EntityChangeBlockEvent;
+import org.bukkit.event.entity.EntityExplodeEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import world.bentobox.bentobox.api.events.island.IslandDeleteEvent;
+import world.bentobox.bentobox.api.localization.TextVariables;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.Database;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.util.Util;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.objects.IslandBlockCount;
+
+/**
+ * @author tastybento
+ *
+ */
+public class BlockLimitsListener implements Listener {
+
+ /**
+ * Blocks that are not counted
+ */
+ private static final List DO_NOT_COUNT = Arrays.asList(Material.LAVA, Material.WATER, Material.AIR, Material.FIRE, Material.END_PORTAL, Material.NETHER_PORTAL);
+ private static final List STACKABLE;
+
+ static {
+ List stackable = new ArrayList<>();
+ stackable.add(Material.SUGAR_CANE);
+ Optional.ofNullable(Material.getMaterial("BAMBOO")).ifPresent(stackable::add);
+ STACKABLE = Collections.unmodifiableList(stackable);
+ }
+
+ /**
+ * Save every 10 blocks of change
+ */
+ private static final Integer CHANGE_LIMIT = 9;
+ private final Limits addon;
+ private final Map islandCountMap = new HashMap<>();
+ private final Map saveMap = new HashMap<>();
+ private final Database handler;
+ private final Map> worldLimitMap = new HashMap<>();
+ private Map defaultLimitMap = new EnumMap<>(Material.class);
+
+ public BlockLimitsListener(Limits addon) {
+ this.addon = addon;
+ handler = new Database<>(addon, IslandBlockCount.class);
+ List toBeDeleted = new ArrayList<>();
+ handler.loadObjects().forEach(ibc -> {
+ // Clean up
+ if (addon.isCoveredGameMode(ibc.getGameMode())) {
+ ibc.getBlockCounts().keySet().removeIf(DO_NOT_COUNT::contains);
+ // Store
+ islandCountMap.put(ibc.getUniqueId(), ibc);
+ } else {
+ toBeDeleted.add(ibc.getUniqueId());
+ }
+ });
+ toBeDeleted.forEach(handler::deleteID);
+ loadAllLimits();
+ }
+
+ /**
+ * Loads the default and world-specific limits
+ */
+ private void loadAllLimits() {
+ // Load the default limits
+ addon.log("Loading default limits");
+ if (addon.getConfig().isConfigurationSection("blocklimits")) {
+ ConfigurationSection limitConfig = addon.getConfig().getConfigurationSection("blocklimits");
+ defaultLimitMap = loadLimits(Objects.requireNonNull(limitConfig));
+ }
+
+ // Load specific worlds
+ if (addon.getConfig().isConfigurationSection("worlds")) {
+ ConfigurationSection worlds = addon.getConfig().getConfigurationSection("worlds");
+ for (String worldName : Objects.requireNonNull(worlds).getKeys(false)) {
+ World world = Bukkit.getWorld(worldName);
+ if (world != null && addon.inGameModeWorld(world)) {
+ addon.log("Loading limits for " + world.getName());
+ worldLimitMap.putIfAbsent(world, new HashMap<>());
+ ConfigurationSection matsConfig = worlds.getConfigurationSection(worldName);
+ worldLimitMap.put(world, loadLimits(Objects.requireNonNull(matsConfig)));
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Loads limit map from configuration section
+ *
+ * @param cs - configuration section
+ * @return limit map
+ */
+ private Map loadLimits(ConfigurationSection cs) {
+ Map mats = new EnumMap<>(Material.class);
+ for (String material : cs.getKeys(false)) {
+ Material mat = Material.getMaterial(material);
+ if (mat != null && mat.isBlock() && !DO_NOT_COUNT.contains(mat)) {
+ mats.put(mat, cs.getInt(material));
+ addon.log("Limit " + mat + " to " + cs.getInt(material));
+ } else {
+ addon.logError("Material " + material + " is not a valid block. Skipping...");
+ }
+ }
+ return mats;
+ }
+
+
+ /**
+ * Save the count database completely
+ */
+ public void save() {
+ islandCountMap.values().stream().filter(IslandBlockCount::isChanged).forEach(handler::saveObjectAsync);
+ }
+
+ // Player-related events
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockPlaceEvent e) {
+ notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType());
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockBreakEvent e) {
+ if (e.getBlock().hasMetadata("blockbreakevent-ignore")) {
+ // Ignore event due to Advanced Enchantments. See https://ae.advancedplugins.net/for-developers/plugin-compatiblity-issues
+ // @since 1.28.0
+ return;
+ }
+ handleBreak(e, e.getBlock());
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onTurtleEggBreak(PlayerInteractEvent e) {
+ if (e.getAction().equals(Action.PHYSICAL) && e.getClickedBlock() != null && e.getClickedBlock().getType().equals(Material.TURTLE_EGG)) {
+ handleBreak(e, e.getClickedBlock());
+ }
+ }
+
+ private void handleBreak(Event e, Block b) {
+ if (!addon.inGameModeWorld(b.getWorld())) {
+ return;
+ }
+ Material mat = b.getType();
+ // Check for stackable plants
+ if (STACKABLE.contains(b.getType())) {
+ // Check for blocks above
+ Block block = b;
+ while(block.getRelative(BlockFace.UP).getType().equals(mat) && block.getY() < b.getWorld().getMaxHeight()) {
+ block = block.getRelative(BlockFace.UP);
+ process(block, false);
+ }
+ }
+ process(b, false);
+ // Player breaks a block and there was a redstone dust/repeater/... above
+ if (b.getRelative(BlockFace.UP).getType() == Material.REDSTONE_WIRE || b.getRelative(BlockFace.UP).getType() == Material.REPEATER || b.getRelative(BlockFace.UP).getType() == Material.COMPARATOR || b.getRelative(BlockFace.UP).getType() == Material.REDSTONE_TORCH) {
+ process(b.getRelative(BlockFace.UP), false);
+ }
+ if (b.getRelative(BlockFace.EAST).getType() == Material.REDSTONE_WALL_TORCH) {
+ process(b.getRelative(BlockFace.EAST), false);
+ }
+ if (b.getRelative(BlockFace.WEST).getType() == Material.REDSTONE_WALL_TORCH) {
+ process(b.getRelative(BlockFace.WEST), false);
+ }
+ if (b.getRelative(BlockFace.SOUTH).getType() == Material.REDSTONE_WALL_TORCH) {
+ process(b.getRelative(BlockFace.SOUTH), false);
+ }
+ if (b.getRelative(BlockFace.NORTH).getType() == Material.REDSTONE_WALL_TORCH) {
+ process(b.getRelative(BlockFace.NORTH), false);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockMultiPlaceEvent e) {
+ notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType());
+ }
+
+ /**
+ * Cancel the event and notify the user of failure
+ * @param e event
+ * @param user user
+ * @param limit maximum limit allowed
+ * @param m material
+ */
+ private void notify(Cancellable e, User user, int limit, Material m) {
+ if (limit > -1) {
+ user.notify("block-limits.hit-limit",
+ "[material]", Util.prettifyText(m.toString()),
+ TextVariables.NUMBER, String.valueOf(limit));
+ e.setCancelled(true);
+ }
+ }
+
+ // Non-player events
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockBurnEvent e) {
+ process(e.getBlock(), false);
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockExplodeEvent e) {
+ e.blockList().forEach(b -> process(b, false));
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockFadeEvent e) {
+ process(e.getBlock(), false);
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockFormEvent e) {
+ process(e.getBlock(), true);
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockSpreadEvent e) {
+ process(e.getBlock(), true);
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(EntityBlockFormEvent e) {
+ process(e.getBlock(), true);
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockGrowEvent e) {
+ if (process(e.getNewState().getBlock(), true) > -1) {
+ e.setCancelled(true);
+ e.getBlock().getWorld().getBlockAt(e.getBlock().getLocation()).setBlockData(e.getBlock().getBlockData());
+ } else {
+ process(e.getBlock(), false);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(LeavesDecayEvent e) {
+ process(e.getBlock(), false);
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(EntityExplodeEvent e) {
+ e.blockList().forEach(b -> process(b, false));
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(EntityChangeBlockEvent e) {
+ process(e.getBlock(), false);
+ if (e.getBlock().getType().equals(Material.FARMLAND)) {
+ process(e.getBlock().getRelative(BlockFace.UP), false);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onBlock(BlockFromToEvent e) {
+ if (e.getBlock().isLiquid()
+ && (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);
+ }
+ }
+
+ /**
+ * Return equivalents. Maps things like wall materials to their non-wall equivalents
+ * @param b block data
+ * @return material that matches the block data
+ */
+ public Material fixMaterial(BlockData b) {
+ Material mat = b.getMaterial();
+
+ if (mat == Material.REDSTONE_WALL_TORCH) {
+ return Material.REDSTONE_TORCH;
+ } else if (mat == Material.WALL_TORCH) {
+ return Material.TORCH;
+ } else if (mat == Material.ZOMBIE_WALL_HEAD) {
+ return Material.ZOMBIE_HEAD;
+ } else if (mat == Material.CREEPER_WALL_HEAD) {
+ return Material.CREEPER_HEAD;
+ } else if (mat == Material.PLAYER_WALL_HEAD) {
+ return Material.PLAYER_HEAD;
+ } else if (mat == Material.DRAGON_WALL_HEAD) {
+ return Material.DRAGON_HEAD;
+ } else if (mat == Material.BAMBOO_SAPLING) {
+ return Material.BAMBOO;
+ } else if (mat == Material.PISTON_HEAD || mat == Material.MOVING_PISTON) {
+ TechnicalPiston tp = (TechnicalPiston) b;
+ if (tp.getType() == TechnicalPiston.Type.NORMAL) {
+ return Material.PISTON;
+ } else {
+ return Material.STICKY_PISTON;
+ }
+ }
+ return mat;
+ }
+
+ /**
+ * Check if a block can be
+ *
+ * @param b - block
+ * @param add - true to add a block, false to remove
+ * @return limit amount if over limit, or -1 if no limitation
+ */
+ private int process(Block b, boolean add) {
+ if (DO_NOT_COUNT.contains(fixMaterial(b.getBlockData())) || !addon.inGameModeWorld(b.getWorld())) {
+ return -1;
+ }
+ // Check if on island
+ return addon.getIslands().getIslandAt(b.getLocation()).map(i -> {
+ String id = i.getUniqueId();
+ String gameMode = addon.getGameModeName(b.getWorld());
+ if (gameMode.isEmpty()) {
+ // Invalid world
+ return -1;
+ }
+ // Ignore the center block - usually bedrock, but for AOneBlock it's the magic block
+ if (addon.getConfig().getBoolean("ignore-center-block", true) && i.getCenter().equals(b.getLocation())) {
+ return -1;
+ }
+ islandCountMap.putIfAbsent(id, new IslandBlockCount(id, gameMode));
+ if (add) {
+ // Check limit
+ int limit = checkLimit(b.getWorld(), fixMaterial(b.getBlockData()), id);
+ if (limit > -1) {
+ return limit;
+ }
+ islandCountMap.get(id).add(fixMaterial(b.getBlockData()));
+ } else {
+ if (islandCountMap.containsKey(id)) {
+ islandCountMap.get(id).remove(fixMaterial(b.getBlockData()));
+ }
+ }
+ updateSaveMap(id);
+ return -1;
+ }).orElse(-1);
+ }
+
+ /**
+ * Removed a block from any island limit count
+ * @param b - block to remove
+ */
+ public void removeBlock(Block b) {
+ // Get island
+ addon.getIslands().getIslandAt(b.getLocation()).ifPresent(i -> {
+ String id = i.getUniqueId();
+ String gameMode = addon.getGameModeName(b.getWorld());
+ if (gameMode.isEmpty()) {
+ // Invalid world
+ return;
+ }
+ islandCountMap.computeIfAbsent(id, k -> new IslandBlockCount(id, gameMode)).remove(fixMaterial(b.getBlockData()));
+ updateSaveMap(id);
+ });
+ }
+ private void updateSaveMap(String id) {
+ saveMap.putIfAbsent(id, 0);
+ if (saveMap.merge(id, 1, Integer::sum) > CHANGE_LIMIT) {
+ handler.saveObjectAsync(islandCountMap.get(id));
+ saveMap.remove(id);
+ }
+ }
+
+
+ /**
+ * Check if this material is at its limit for world on this island
+ *
+ * @param w - world
+ * @param m - material
+ * @param id - island id
+ * @return limit amount if at limit or -1 if no limit
+ */
+ private int checkLimit(World w, Material m, String id) {
+ // Check island limits
+ IslandBlockCount ibc = islandCountMap.get(id);
+ if (ibc.isBlockLimited(m)) {
+ return ibc.isAtLimit(m) ? ibc.getBlockLimit(m) + ibc.getBlockLimitOffset(m) : -1;
+ }
+ // Check specific world limits
+ if (worldLimitMap.containsKey(w) && worldLimitMap.get(w).containsKey(m)) {
+ // Material is overridden in world
+ return ibc.isAtLimit(m, worldLimitMap.get(w).get(m)) ? worldLimitMap.get(w).get(m) + ibc.getBlockLimitOffset(m) : -1;
+ }
+ // Check default limit map
+ if (defaultLimitMap.containsKey(m) && ibc.isAtLimit(m, defaultLimitMap.get(m))) {
+ return defaultLimitMap.get(m) + ibc.getBlockLimitOffset(m);
+ }
+ // No limit
+ return -1;
+ }
+
+ /**
+ * Gets an aggregate map of the limits for this island
+ *
+ * @param w - world
+ * @param id - island id
+ * @return map of limits for materials
+ */
+ public Map getMaterialLimits(World w, String id) {
+ // Merge limits
+ Map result = new EnumMap<>(Material.class);
+ // Default
+ result.putAll(defaultLimitMap);
+ // World
+ if (worldLimitMap.containsKey(w)) {
+ result.putAll(worldLimitMap.get(w));
+ }
+ // Island
+ if (islandCountMap.containsKey(id)) {
+ IslandBlockCount islandBlockCount = islandCountMap.get(id);
+ result.putAll(islandBlockCount.getBlockLimits());
+
+ // Add offsets to the every limit.
+ islandBlockCount.getBlockLimitsOffset().forEach((material, offset) ->
+ result.put(material, result.getOrDefault(material, 0) + offset));
+ }
+ return result;
+ }
+
+ /**
+ * Removes island from the database
+ *
+ * @param e - island delete event
+ */
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onIslandDelete(IslandDeleteEvent e) {
+ islandCountMap.remove(e.getIsland().getUniqueId());
+ saveMap.remove(e.getIsland().getUniqueId());
+ if (handler.objectExists(e.getIsland().getUniqueId())) {
+ handler.deleteID(e.getIsland().getUniqueId());
+ }
+ }
+
+ /**
+ * Set the island block count values
+ *
+ * @param islandId - island unique id
+ * @param ibc - island block count
+ */
+ public void setIsland(String islandId, IslandBlockCount ibc) {
+ islandCountMap.put(islandId, ibc);
+ handler.saveObjectAsync(ibc);
+ }
+
+ /**
+ * Get the island block count
+ *
+ * @param islandId - island unique id
+ * @return island block count or null if there is none yet
+ */
+ @Nullable
+ public IslandBlockCount getIsland(String islandId) {
+ return islandCountMap.get(islandId);
+ }
+
+ /**
+ * Get the island block count for island and make one if it does not exist
+ * @param island island
+ * @return island block count
+ */
+ @NonNull
+ public IslandBlockCount getIsland(Island island) {
+ return islandCountMap.computeIfAbsent(island.getUniqueId(), k -> new IslandBlockCount(k, island.getGameMode()));
+ }
+
+}
diff --git a/src/main/java/world/bentobox/limits/listeners/EntityLimitListener.java b/src/main/java/world/bentobox/limits/listeners/EntityLimitListener.java
index 31b21a1..62d058a 100644
--- a/src/main/java/world/bentobox/limits/listeners/EntityLimitListener.java
+++ b/src/main/java/world/bentobox/limits/listeners/EntityLimitListener.java
@@ -1,474 +1,463 @@
-package world.bentobox.limits.listeners;
-
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-import org.bukkit.Bukkit;
-import org.bukkit.Location;
-import org.bukkit.Material;
-import org.bukkit.Tag;
-import org.bukkit.World;
-import org.bukkit.block.Block;
-import org.bukkit.block.BlockFace;
-import org.bukkit.entity.Breedable;
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.EntityType;
-import org.bukkit.entity.LivingEntity;
-import org.bukkit.entity.Player;
-import org.bukkit.entity.Villager;
-import org.bukkit.event.Cancellable;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.EventPriority;
-import org.bukkit.event.Listener;
-import org.bukkit.event.entity.CreatureSpawnEvent;
-import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
-import org.bukkit.event.entity.EntityBreedEvent;
-import org.bukkit.event.hanging.HangingPlaceEvent;
-import org.bukkit.event.vehicle.VehicleCreateEvent;
-import org.eclipse.jdt.annotation.Nullable;
-
-import world.bentobox.bentobox.BentoBox;
-import world.bentobox.bentobox.api.localization.TextVariables;
-import world.bentobox.bentobox.api.user.User;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.bentobox.util.Util;
-import world.bentobox.limits.EntityGroup;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.Settings;
-import world.bentobox.limits.objects.IslandBlockCount;
-
-public class EntityLimitListener implements Listener {
- private static final String MOD_BYPASS = "mod.bypass";
- private final Limits addon;
- private final List justSpawned = new ArrayList<>();
- private static final List CARDINALS = List.of(BlockFace.UP, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.DOWN);
-
- /**
- * Handles entity and natural limitations
- * @param addon - Limits object
- */
- public EntityLimitListener(Limits addon) {
- this.addon = addon;
- }
-
- /**
- * Handles minecart placing
- * @param e - event
- */
- @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
- public void onMinecart(VehicleCreateEvent e) {
- // Return if not in a known world
- if (!addon.inGameModeWorld(e.getVehicle().getWorld())) {
- return;
- }
- // Debounce
- if (justSpawned.contains(e.getVehicle().getUniqueId())) {
- justSpawned.remove(e.getVehicle().getUniqueId());
- return;
- }
- // Check island
- addon.getIslands().getProtectedIslandAt(e.getVehicle().getLocation())
- // Ignore spawn
- .filter(i -> !i.isSpawn())
- .ifPresent(island -> {
- // Check if the player is at the limit
- AtLimitResult res = atLimit(island, e.getVehicle());
- if (res.hit()) {
- e.setCancelled(true);
- this.tellPlayers(e.getVehicle().getLocation(), e.getVehicle(), SpawnReason.MOUNT, res);
- }
- });
- }
-
-
- @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
- public void onBreed(final EntityBreedEvent e) {
- if (addon.inGameModeWorld(e.getEntity().getWorld())
- && e.getBreeder() != null
- && (e.getBreeder() instanceof Player p)
- && !(p.isOp() || p.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS))
- && !checkLimit(e, e.getEntity(), SpawnReason.BREEDING, false)
- && e.getFather() instanceof Breedable f && e.getMother() instanceof Breedable m) {
- f.setBreed(false);
- m.setBreed(false);
- }
- }
-
- @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
- public void onCreatureSpawn(final CreatureSpawnEvent e) {
- // Return if not in a known world
- if (!addon.inGameModeWorld(e.getLocation().getWorld())) {
- return;
- }
- if (justSpawned.contains(e.getEntity().getUniqueId())) {
- justSpawned.remove(e.getEntity().getUniqueId());
- return;
- }
- if (e.getSpawnReason().equals(SpawnReason.SHOULDER_ENTITY) || (!(e.getEntity() instanceof Villager ) && e.getSpawnReason().equals(SpawnReason.BREEDING))) {
- // Special case - do nothing - jumping around spawns parrots as they drop off player's shoulder
- // Ignore breeding because it's handled in the EntityBreedEvent listener
- return;
- }
- // Some checks can be done async, some not
- if (e.getSpawnReason().equals(SpawnReason.BUILD_SNOWMAN) || e.getSpawnReason().equals(SpawnReason.BUILD_IRONGOLEM)) {
- checkLimit(e, e.getEntity(), e.getSpawnReason(), addon.getSettings().isAsyncGolums());
- } else {
- // Check limit sync
- checkLimit(e, e.getEntity(), e.getSpawnReason(), false);
- }
-
- }
-
- /**
- * handles paintings and item frames
- * @param e - event
- */
- @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
- public void onBlock(HangingPlaceEvent e) {
- if (!addon.inGameModeWorld(e.getBlock().getWorld())) {
- return;
- }
- Player player = e.getPlayer();
- if (player == null) return;
- addon.getIslands().getIslandAt(e.getEntity().getLocation()).ifPresent(island -> {
- boolean bypass = Objects.requireNonNull(player).isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS);
- // Check if entity can be hung
- AtLimitResult res;
- if (!bypass && !island.isSpawn() && (res = atLimit(island, e.getEntity())).hit()) {
- // Not allowed
- e.setCancelled(true);
- if (res.getTypelimit() != null) {
- User.getInstance(player).notify("block-limits.hit-limit", "[material]",
- Util.prettifyText(e.getEntity().getType().toString()),
- TextVariables.NUMBER, String.valueOf(res.getTypelimit().getValue()));
- } else {
- User.getInstance(player).notify("block-limits.hit-limit", "[material]",
- res.getGrouplimit().getKey().getName() + " (" + res.getGrouplimit().getKey().getTypes().stream().map(x -> Util.prettifyText(x.toString())).collect(Collectors.joining(", ")) + ")",
- TextVariables.NUMBER, String.valueOf(res.getGrouplimit().getValue()));
- }
- }
- });
- }
-
- /**
- * Check if a creature is allowed to spawn or not
- * @param e - CreatureSpawnEvent
- * @param async - true if check can be done async, false if not
- * @return true if allowed or asycn, false if not.
- */
- private boolean checkLimit(Cancellable c, LivingEntity e, SpawnReason reason, boolean async) {
- Location l = e.getLocation();
- if (async) {
- c.setCancelled(true);
- }
- return processIsland(c, e, l, reason, async);
- }
-
- private boolean processIsland(Cancellable c, LivingEntity e, Location l, SpawnReason reason, boolean async) {
- if (addon.getIslands().getIslandAt(e.getLocation()).isEmpty()) {
- c.setCancelled(false);
- return true;
- }
- Island island = addon.getIslands().getIslandAt(e.getLocation()).get();
- // Check if creature is allowed to spawn or not
- AtLimitResult res = atLimit(island, e);
- if (island.isSpawn() || !res.hit()) {
- // Allowed
- if (async) {
- Bukkit.getScheduler().runTask(BentoBox.getInstance(), () -> preSpawn(e.getType(), reason, l));
- } // else do nothing
- } else {
- if (async) {
- e.remove();
- } else {
- c.setCancelled(true);
- }
- // If the reason is anything but because of a spawner then tell players within range
- tellPlayers(l, e, reason, res);
- return false;
- }
- return true;
- }
-
- private void preSpawn(EntityType entityType, SpawnReason reason, Location l) {
-
- // Check for entities that need cleanup
- switch (reason) {
- case BUILD_IRONGOLEM -> detectIronGolem(l);
- case BUILD_SNOWMAN -> detectSnowman(l);
- case BUILD_WITHER -> {
- detectWither(l);
- }
- default -> throw new IllegalArgumentException("Unexpected value: " + reason);
- }
- Entity entity = l.getWorld().spawnEntity(l, entityType);
- justSpawned.add(entity.getUniqueId());
- if (reason == SpawnReason.BUILD_WITHER) {
- // Create explosion
- l.getWorld().createExplosion(l, 7F, true, true, entity);
- }
- }
-
- private void detectIronGolem(Location l) {
- Block legs = l.getBlock();
- // Erase legs
- addon.getBlockLimitListener().removeBlock(legs);
- legs.setType(Material.AIR);
- // Look around for possible constructions
- for (BlockFace bf : CARDINALS) {
- Block body = legs.getRelative(bf);
- if (body.getType().equals(Material.IRON_BLOCK)) {
- // Check for head
- Block head = body.getRelative(bf);
- if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) {
- // Check for arms the rule is that they must be opposite and have nothing "beneath" them
- for (BlockFace bf2 : CARDINALS) {
- Block arm1 = body.getRelative(bf2);
- Block arm2 = body.getRelative(bf2.getOppositeFace());
- if (arm1.getType() == Material.IRON_BLOCK && arm2.getType() == Material.IRON_BLOCK
- && arm1.getRelative(bf.getOppositeFace()).isEmpty()
- && arm2.getRelative(bf.getOppositeFace()).isEmpty()) {
- // Erase!
- addon.getBlockLimitListener().removeBlock(body);
- addon.getBlockLimitListener().removeBlock(arm1);
- addon.getBlockLimitListener().removeBlock(arm2);
- addon.getBlockLimitListener().removeBlock(head);
- body.setType(Material.AIR);
- arm1.setType(Material.AIR);
- arm2.setType(Material.AIR);
- head.setType(Material.AIR);
- return;
- }
- }
- }
- }
- }
- }
-
- private void detectSnowman(Location l) {
- Block legs = l.getBlock();
- // Erase legs
- addon.getBlockLimitListener().removeBlock(legs);
- legs.setType(Material.AIR);
- // Look around for possible constructions
- for (BlockFace bf : CARDINALS) {
- Block body = legs.getRelative(bf);
- if (body.getType().equals(Material.SNOW_BLOCK)) {
- // Check for head
- Block head = body.getRelative(bf);
- if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) {
- // Erase
- addon.getBlockLimitListener().removeBlock(body);
- addon.getBlockLimitListener().removeBlock(head);
-
- body.setType(Material.AIR);
- head.setType(Material.AIR);
- return;
- }
- }
- }
-
- }
-
- private void detectWither(Location l) {
- Block legs = l.getBlock();
- // Erase legs
- addon.getBlockLimitListener().removeBlock(legs);
- legs.setType(Material.AIR);
- // Look around for possible constructions
- for (BlockFace bf : CARDINALS) {
- Block body = legs.getRelative(bf);
- if (isWither(body)) {
- // Check for head
- Block head = body.getRelative(bf);
- if (head.getType().equals(Material.WITHER_SKELETON_SKULL) || head.getType().equals(Material.WITHER_SKELETON_WALL_SKULL)) {
- // Check for arms the rule is that they must be opposite and have nothing "beneath" them
- for (BlockFace bf2 : CARDINALS) {
- Block arm1 = body.getRelative(bf2);
- Block arm2 = body.getRelative(bf2.getOppositeFace());
- Block head2 = arm1.getRelative(bf);
- Block head3 = arm2.getRelative(bf);
- if (isWither(arm1)
- && isWither(arm2)
- && arm1.getRelative(bf.getOppositeFace()).isEmpty()
- && arm2.getRelative(bf.getOppositeFace()).isEmpty()
- && (head2.getType().equals(Material.WITHER_SKELETON_SKULL) || head2.getType().equals(Material.WITHER_SKELETON_WALL_SKULL))
- && (head3.getType().equals(Material.WITHER_SKELETON_SKULL) || head3.getType().equals(Material.WITHER_SKELETON_WALL_SKULL))
- ) {
- // Erase!
- addon.getBlockLimitListener().removeBlock(body);
- addon.getBlockLimitListener().removeBlock(arm1);
- addon.getBlockLimitListener().removeBlock(arm2);
- addon.getBlockLimitListener().removeBlock(head);
- addon.getBlockLimitListener().removeBlock(head2);
- addon.getBlockLimitListener().removeBlock(head3);
- body.setType(Material.AIR);
- arm1.setType(Material.AIR);
- arm2.setType(Material.AIR);
- head.setType(Material.AIR);
- head2.setType(Material.AIR);
- head3.setType(Material.AIR);
- return;
- }
- }
- }
- }
- }
- }
-
-
- private boolean isWither(Block body) {
- if (Util.getMinecraftVersion() < 16) {
- return body.getType().equals(Material.SOUL_SAND);
- }
- return Tag.WITHER_SUMMON_BASE_BLOCKS.isTagged(body.getType());
- }
-
- /**
- * Tell players within a 5 x 5 x 5 radius that the spawning was denied. Informing happens 1 tick after event
- * @param l location
- * @param entity entity spawned
- * @param reason reason - some reasons are not reported
- * @param res at limit result
- */
- private void tellPlayers(Location l, Entity entity, SpawnReason reason, AtLimitResult res) {
- if (reason.equals(SpawnReason.SPAWNER) || reason.equals(SpawnReason.NATURAL)
- || reason.equals(SpawnReason.INFECTION) || reason.equals(SpawnReason.NETHER_PORTAL)
- || reason.equals(SpawnReason.REINFORCEMENTS) || reason.equals(SpawnReason.SLIME_SPLIT)) {
- return;
- }
- World w = l.getWorld();
- if (w == null) return;
- Bukkit.getScheduler().runTask(addon.getPlugin(), () -> {
- for (Entity ent : w.getNearbyEntities(l, 5, 5, 5)) {
- if (ent instanceof Player p) {
- p.updateInventory();
- if (res.getTypelimit() != null) {
- User.getInstance(p).notify("entity-limits.hit-limit", "[entity]",
- Util.prettifyText(entity.getType().toString()),
- TextVariables.NUMBER, String.valueOf(res.getTypelimit().getValue()));
- } else {
- User.getInstance(p).notify("entity-limits.hit-limit", "[entity]",
- res.getGrouplimit().getKey().getName() + " (" + res.getGrouplimit().getKey().getTypes().stream().map(x -> Util.prettifyText(x.toString())).collect(Collectors.joining(", ")) + ")",
- TextVariables.NUMBER, String.valueOf(res.getGrouplimit().getValue()));
- }
- }
- }
- });
-
- }
-
- /**
- * Checks if new entities can be added to island
- * @param island - island
- * @param ent - the entity
- * @return true if at the limit, false if not
- */
- AtLimitResult atLimit(Island island, Entity ent) {
- // Check island settings first
- int limitAmount = -1;
- Map groupsLimits = new HashMap<>();
-
- @Nullable
- IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId());
- if (ibc != null) {
- // Get the limit amount for this type
- limitAmount = ibc.getEntityLimit(ent.getType());
- // Handle entity groups
- List groupdefs = addon.getSettings().getGroupLimits().getOrDefault(ent.getType(),
- new ArrayList<>());
- groupdefs.forEach(def -> {
- int limit = ibc.getEntityGroupLimit(def.getName());
- if (limit >= 0)
- groupsLimits.put(def, limit);
- });
- }
- // If no island settings then try global settings
- if (limitAmount < 0 && addon.getSettings().getLimits().containsKey(ent.getType())) {
- limitAmount = addon.getSettings().getLimits().get(ent.getType());
- }
- // Group limits
- if (addon.getSettings().getGroupLimits().containsKey(ent.getType())) {
- addon.getSettings().getGroupLimits().getOrDefault(ent.getType(), new ArrayList<>()).stream()
- .filter(group -> !groupsLimits.containsKey(group) || groupsLimits.get(group) > group.getLimit())
- .forEach(group -> groupsLimits.put(group, group.getLimit()));
- }
- if (limitAmount < 0 && groupsLimits.isEmpty()) {
- return new AtLimitResult();
- }
-
- // We have to count the entities
- if (limitAmount >= 0)
- {
- int count = (int) ent.getWorld().getNearbyEntities(island.getBoundingBox()).stream()
- .filter(e -> e.getType().equals(ent.getType()))
- .count();
- int max = limitAmount + (ibc == null ? 0 : ibc.getEntityLimitOffset(ent.getType()));
- if (count >= max) {
- return new AtLimitResult(ent.getType(), max);
- }
- }
- // Group limits
- if (ibc != null) {
- Map groupbyname = groupsLimits.keySet().stream()
- .collect(Collectors.toMap(EntityGroup::getName, e -> e));
- ibc.getEntityGroupLimits().entrySet().stream()
- .filter(e -> groupbyname.containsKey(e.getKey()))
- .forEach(e -> groupsLimits.put(groupbyname.get(e.getKey()), e.getValue()));
- }
- // Now do the group limits
- for (Map.Entry group : groupsLimits.entrySet()) { //do not use lambda
- if (group.getValue() < 0)
- continue;
- // int count = (int) ent.getWorld().getEntities().stream()
- // .filter(e -> group.getKey().contains(e.getType()))
- // .filter(e -> island.inIslandSpace(e.getLocation())).count();
- int count = (int) ent.getWorld().getNearbyEntities(island.getBoundingBox()).stream()
- .filter(e -> group.getKey().contains(e.getType()))
- .count();
- int max = group.getValue() + + (ibc == null ? 0 : ibc.getEntityGroupLimitOffset(group.getKey().getName()));
- if (count >= max) {
- return new AtLimitResult(group.getKey(), max);
- }
- }
- return new AtLimitResult();
- }
-
- static class AtLimitResult {
- private Map.Entry typelimit;
- private Map.Entry grouplimit;
-
- public AtLimitResult() {}
-
- public AtLimitResult(EntityType type, int limit) {
- typelimit = new AbstractMap.SimpleEntry<>(type, limit);
- }
-
- public AtLimitResult(EntityGroup type, int limit) {
- grouplimit = new AbstractMap.SimpleEntry<>(type, limit);
- }
-
- /**
- * @return true if at limit
- */
- public boolean hit() {
- return typelimit != null || grouplimit != null;
- }
-
- public Map.Entry getTypelimit() {
- return typelimit;
- }
-
- public Map.Entry getGrouplimit() {
- return grouplimit;
- }
- }
-}
-
-
+package world.bentobox.limits.listeners;
+
+import org.bukkit.*;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.entity.*;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
+import org.bukkit.event.entity.EntityBreedEvent;
+import org.bukkit.event.hanging.HangingPlaceEvent;
+import org.bukkit.event.vehicle.VehicleCreateEvent;
+import org.eclipse.jdt.annotation.Nullable;
+import world.bentobox.bentobox.BentoBox;
+import world.bentobox.bentobox.api.localization.TextVariables;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.util.Util;
+import world.bentobox.limits.EntityGroup;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.objects.IslandBlockCount;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class EntityLimitListener implements Listener {
+ private static final String MOD_BYPASS = "mod.bypass";
+ private final Limits addon;
+ private final List justSpawned = new ArrayList<>();
+ private static final List CARDINALS = List.of(BlockFace.UP, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.DOWN);
+
+ /**
+ * Handles entity and natural limitations
+ *
+ * @param addon - Limits object
+ */
+ public EntityLimitListener(Limits addon) {
+ this.addon = addon;
+ }
+
+ /**
+ * Handles minecart placing
+ *
+ * @param e - event
+ */
+ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
+ public void onMinecart(VehicleCreateEvent e) {
+ // Return if not in a known world
+ if (!addon.inGameModeWorld(e.getVehicle().getWorld())) {
+ return;
+ }
+ // Debounce
+ if (justSpawned.contains(e.getVehicle().getUniqueId())) {
+ justSpawned.remove(e.getVehicle().getUniqueId());
+ return;
+ }
+ // Check island
+ addon.getIslands().getProtectedIslandAt(e.getVehicle().getLocation())
+ // Ignore spawn
+ .filter(i -> !i.isSpawn())
+ .ifPresent(island -> {
+ // Check if the player is at the limit
+ AtLimitResult res = atLimit(island, e.getVehicle());
+ if (res.hit()) {
+ e.setCancelled(true);
+ this.tellPlayers(e.getVehicle().getLocation(), e.getVehicle(), SpawnReason.MOUNT, res);
+ }
+ });
+ }
+
+
+ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
+ public void onBreed(final EntityBreedEvent e) {
+ if (addon.inGameModeWorld(e.getEntity().getWorld())
+ && e.getBreeder() != null
+ && (e.getBreeder() instanceof Player p)
+ && !(p.isOp() || p.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS))
+ && !checkLimit(e, e.getEntity(), SpawnReason.BREEDING, false)
+ && e.getFather() instanceof Breedable f && e.getMother() instanceof Breedable m) {
+ f.setBreed(false);
+ m.setBreed(false);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
+ public void onCreatureSpawn(final CreatureSpawnEvent e) {
+ // Return if not in a known world
+ if (!addon.inGameModeWorld(e.getLocation().getWorld())) {
+ return;
+ }
+ if (justSpawned.contains(e.getEntity().getUniqueId())) {
+ justSpawned.remove(e.getEntity().getUniqueId());
+ return;
+ }
+ if (e.getSpawnReason().equals(SpawnReason.SHOULDER_ENTITY) || (!(e.getEntity() instanceof Villager) && e.getSpawnReason().equals(SpawnReason.BREEDING))) {
+ // Special case - do nothing - jumping around spawns parrots as they drop off player's shoulder
+ // Ignore breeding because it's handled in the EntityBreedEvent listener
+ return;
+ }
+ // Some checks can be done async, some not
+ if (e.getSpawnReason().equals(SpawnReason.BUILD_SNOWMAN) || e.getSpawnReason().equals(SpawnReason.BUILD_IRONGOLEM)) {
+ checkLimit(e, e.getEntity(), e.getSpawnReason(), addon.getSettings().isAsyncGolums());
+ } else {
+ // Check limit sync
+ checkLimit(e, e.getEntity(), e.getSpawnReason(), false);
+ }
+
+ }
+
+ /**
+ * handles paintings and item frames
+ *
+ * @param e - event
+ */
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlock(HangingPlaceEvent e) {
+ if (!addon.inGameModeWorld(e.getBlock().getWorld())) {
+ return;
+ }
+ Player player = e.getPlayer();
+ if (player == null) return;
+ addon.getIslands().getIslandAt(e.getEntity().getLocation()).ifPresent(island -> {
+ boolean bypass = Objects.requireNonNull(player).isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS);
+ // Check if entity can be hung
+ AtLimitResult res;
+ if (!bypass && !island.isSpawn() && (res = atLimit(island, e.getEntity())).hit()) {
+ // Not allowed
+ e.setCancelled(true);
+ if (res.getTypelimit() != null) {
+ User.getInstance(player).notify("block-limits.hit-limit", "[material]",
+ Util.prettifyText(e.getEntity().getType().toString()),
+ TextVariables.NUMBER, String.valueOf(res.getTypelimit().getValue()));
+ } else {
+ User.getInstance(player).notify("block-limits.hit-limit", "[material]",
+ res.getGrouplimit().getKey().getName() + " (" + res.getGrouplimit().getKey().getTypes().stream().map(x -> Util.prettifyText(x.toString())).collect(Collectors.joining(", ")) + ")",
+ TextVariables.NUMBER, String.valueOf(res.getGrouplimit().getValue()));
+ }
+ }
+ });
+ }
+
+ /**
+ * Check if a creature is allowed to spawn or not
+ *
+ * @param e - CreatureSpawnEvent
+ * @param async - true if check can be done async, false if not
+ * @return true if allowed or asycn, false if not.
+ */
+ private boolean checkLimit(Cancellable c, LivingEntity e, SpawnReason reason, boolean async) {
+ Location l = e.getLocation();
+ if (async) {
+ c.setCancelled(true);
+ }
+ return processIsland(c, e, l, reason, async);
+ }
+
+ private boolean processIsland(Cancellable c, LivingEntity e, Location l, SpawnReason reason, boolean async) {
+ if (addon.getIslands().getIslandAt(e.getLocation()).isEmpty()) {
+ c.setCancelled(false);
+ return true;
+ }
+ Island island = addon.getIslands().getIslandAt(e.getLocation()).get();
+ // Check if creature is allowed to spawn or not
+ AtLimitResult res = atLimit(island, e);
+ if (island.isSpawn() || !res.hit()) {
+ // Allowed
+ if (async) {
+ Bukkit.getScheduler().runTask(BentoBox.getInstance(), () -> preSpawn(e.getType(), reason, l));
+ } // else do nothing
+ } else {
+ if (async) {
+ e.remove();
+ } else {
+ c.setCancelled(true);
+ }
+ // If the reason is anything but because of a spawner then tell players within range
+ tellPlayers(l, e, reason, res);
+ return false;
+ }
+ return true;
+ }
+
+ private void preSpawn(EntityType entityType, SpawnReason reason, Location l) {
+
+ // Check for entities that need cleanup
+ switch (reason) {
+ case BUILD_IRONGOLEM -> detectIronGolem(l);
+ case BUILD_SNOWMAN -> detectSnowman(l);
+ case BUILD_WITHER -> {
+ detectWither(l);
+ }
+ default -> throw new IllegalArgumentException("Unexpected value: " + reason);
+ }
+ Entity entity = l.getWorld().spawnEntity(l, entityType);
+ justSpawned.add(entity.getUniqueId());
+ if (reason == SpawnReason.BUILD_WITHER) {
+ // Create explosion
+ l.getWorld().createExplosion(l, 7F, true, true, entity);
+ }
+ }
+
+ private void detectIronGolem(Location l) {
+ Block legs = l.getBlock();
+ // Erase legs
+ addon.getBlockLimitListener().removeBlock(legs);
+ legs.setType(Material.AIR);
+ // Look around for possible constructions
+ for (BlockFace bf : CARDINALS) {
+ Block body = legs.getRelative(bf);
+ if (body.getType().equals(Material.IRON_BLOCK)) {
+ // Check for head
+ Block head = body.getRelative(bf);
+ if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) {
+ // Check for arms the rule is that they must be opposite and have nothing "beneath" them
+ for (BlockFace bf2 : CARDINALS) {
+ Block arm1 = body.getRelative(bf2);
+ Block arm2 = body.getRelative(bf2.getOppositeFace());
+ if (arm1.getType() == Material.IRON_BLOCK && arm2.getType() == Material.IRON_BLOCK
+ && arm1.getRelative(bf.getOppositeFace()).isEmpty()
+ && arm2.getRelative(bf.getOppositeFace()).isEmpty()) {
+ // Erase!
+ addon.getBlockLimitListener().removeBlock(body);
+ addon.getBlockLimitListener().removeBlock(arm1);
+ addon.getBlockLimitListener().removeBlock(arm2);
+ addon.getBlockLimitListener().removeBlock(head);
+ body.setType(Material.AIR);
+ arm1.setType(Material.AIR);
+ arm2.setType(Material.AIR);
+ head.setType(Material.AIR);
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void detectSnowman(Location l) {
+ Block legs = l.getBlock();
+ // Erase legs
+ addon.getBlockLimitListener().removeBlock(legs);
+ legs.setType(Material.AIR);
+ // Look around for possible constructions
+ for (BlockFace bf : CARDINALS) {
+ Block body = legs.getRelative(bf);
+ if (body.getType().equals(Material.SNOW_BLOCK)) {
+ // Check for head
+ Block head = body.getRelative(bf);
+ if (head.getType() == Material.CARVED_PUMPKIN || head.getType() == Material.JACK_O_LANTERN) {
+ // Erase
+ addon.getBlockLimitListener().removeBlock(body);
+ addon.getBlockLimitListener().removeBlock(head);
+
+ body.setType(Material.AIR);
+ head.setType(Material.AIR);
+ return;
+ }
+ }
+ }
+
+ }
+
+ private void detectWither(Location l) {
+ Block legs = l.getBlock();
+ // Erase legs
+ addon.getBlockLimitListener().removeBlock(legs);
+ legs.setType(Material.AIR);
+ // Look around for possible constructions
+ for (BlockFace bf : CARDINALS) {
+ Block body = legs.getRelative(bf);
+ if (isWither(body)) {
+ // Check for head
+ Block head = body.getRelative(bf);
+ if (head.getType().equals(Material.WITHER_SKELETON_SKULL) || head.getType().equals(Material.WITHER_SKELETON_WALL_SKULL)) {
+ // Check for arms the rule is that they must be opposite and have nothing "beneath" them
+ for (BlockFace bf2 : CARDINALS) {
+ Block arm1 = body.getRelative(bf2);
+ Block arm2 = body.getRelative(bf2.getOppositeFace());
+ Block head2 = arm1.getRelative(bf);
+ Block head3 = arm2.getRelative(bf);
+ if (isWither(arm1)
+ && isWither(arm2)
+ && arm1.getRelative(bf.getOppositeFace()).isEmpty()
+ && arm2.getRelative(bf.getOppositeFace()).isEmpty()
+ && (head2.getType().equals(Material.WITHER_SKELETON_SKULL) || head2.getType().equals(Material.WITHER_SKELETON_WALL_SKULL))
+ && (head3.getType().equals(Material.WITHER_SKELETON_SKULL) || head3.getType().equals(Material.WITHER_SKELETON_WALL_SKULL))
+ ) {
+ // Erase!
+ addon.getBlockLimitListener().removeBlock(body);
+ addon.getBlockLimitListener().removeBlock(arm1);
+ addon.getBlockLimitListener().removeBlock(arm2);
+ addon.getBlockLimitListener().removeBlock(head);
+ addon.getBlockLimitListener().removeBlock(head2);
+ addon.getBlockLimitListener().removeBlock(head3);
+ body.setType(Material.AIR);
+ arm1.setType(Material.AIR);
+ arm2.setType(Material.AIR);
+ head.setType(Material.AIR);
+ head2.setType(Material.AIR);
+ head3.setType(Material.AIR);
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ private boolean isWither(Block body) {
+ if (Util.getMinecraftVersion() < 16) {
+ return body.getType().equals(Material.SOUL_SAND);
+ }
+ return Tag.WITHER_SUMMON_BASE_BLOCKS.isTagged(body.getType());
+ }
+
+ /**
+ * Tell players within a 5 x 5 x 5 radius that the spawning was denied. Informing happens 1 tick after event
+ *
+ * @param l location
+ * @param entity entity spawned
+ * @param reason reason - some reasons are not reported
+ * @param res at limit result
+ */
+ private void tellPlayers(Location l, Entity entity, SpawnReason reason, AtLimitResult res) {
+ if (reason.equals(SpawnReason.SPAWNER) || reason.equals(SpawnReason.NATURAL)
+ || reason.equals(SpawnReason.INFECTION) || reason.equals(SpawnReason.NETHER_PORTAL)
+ || reason.equals(SpawnReason.REINFORCEMENTS) || reason.equals(SpawnReason.SLIME_SPLIT)) {
+ return;
+ }
+ World w = l.getWorld();
+ if (w == null) return;
+ Bukkit.getScheduler().runTask(addon.getPlugin(), () -> {
+ for (Entity ent : w.getNearbyEntities(l, 5, 5, 5)) {
+ if (ent instanceof Player p) {
+ p.updateInventory();
+ if (res.getTypelimit() != null) {
+ User.getInstance(p).notify("entity-limits.hit-limit", "[entity]",
+ Util.prettifyText(entity.getType().toString()),
+ TextVariables.NUMBER, String.valueOf(res.getTypelimit().getValue()));
+ } else {
+ User.getInstance(p).notify("entity-limits.hit-limit", "[entity]",
+ res.getGrouplimit().getKey().getName() + " (" + res.getGrouplimit().getKey().getTypes().stream().map(x -> Util.prettifyText(x.toString())).collect(Collectors.joining(", ")) + ")",
+ TextVariables.NUMBER, String.valueOf(res.getGrouplimit().getValue()));
+ }
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Checks if new entities can be added to island
+ *
+ * @param island - island
+ * @param ent - the entity
+ * @return true if at the limit, false if not
+ */
+ AtLimitResult atLimit(Island island, Entity ent) {
+ // Check island settings first
+ int limitAmount = -1;
+ Map groupsLimits = new HashMap<>();
+
+ @Nullable
+ IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId());
+ if (ibc != null) {
+ // Get the limit amount for this type
+ limitAmount = ibc.getEntityLimit(ent.getType());
+ // Handle entity groups
+ List groupdefs = addon.getSettings().getGroupLimits().getOrDefault(ent.getType(),
+ new ArrayList<>());
+ groupdefs.forEach(def -> {
+ int limit = ibc.getEntityGroupLimit(def.getName());
+ if (limit >= 0)
+ groupsLimits.put(def, limit);
+ });
+ }
+ // If no island settings then try global settings
+ if (limitAmount < 0 && addon.getSettings().getLimits().containsKey(ent.getType())) {
+ limitAmount = addon.getSettings().getLimits().get(ent.getType());
+ }
+ // Group limits
+ if (addon.getSettings().getGroupLimits().containsKey(ent.getType())) {
+ addon.getSettings().getGroupLimits().getOrDefault(ent.getType(), new ArrayList<>()).stream()
+ .filter(group -> !groupsLimits.containsKey(group) || groupsLimits.get(group) > group.getLimit())
+ .forEach(group -> groupsLimits.put(group, group.getLimit()));
+ }
+ if (limitAmount < 0 && groupsLimits.isEmpty()) {
+ return new AtLimitResult();
+ }
+
+ // We have to count the entities
+ if (limitAmount >= 0) {
+ int count = (int) ent.getWorld().getNearbyEntities(island.getBoundingBox()).stream()
+ .filter(e -> e.getType().equals(ent.getType()))
+ .count();
+ int max = limitAmount + (ibc == null ? 0 : ibc.getEntityLimitOffset(ent.getType()));
+ if (count >= max) {
+ return new AtLimitResult(ent.getType(), max);
+ }
+ }
+ // Group limits
+ if (ibc != null) {
+ Map groupbyname = groupsLimits.keySet().stream()
+ .collect(Collectors.toMap(EntityGroup::getName, e -> e));
+ ibc.getEntityGroupLimits().entrySet().stream()
+ .filter(e -> groupbyname.containsKey(e.getKey()))
+ .forEach(e -> groupsLimits.put(groupbyname.get(e.getKey()), e.getValue()));
+ }
+ // Now do the group limits
+ for (Map.Entry group : groupsLimits.entrySet()) { //do not use lambda
+ if (group.getValue() < 0)
+ continue;
+ // int count = (int) ent.getWorld().getEntities().stream()
+ // .filter(e -> group.getKey().contains(e.getType()))
+ // .filter(e -> island.inIslandSpace(e.getLocation())).count();
+ int count = (int) ent.getWorld().getNearbyEntities(island.getBoundingBox()).stream()
+ .filter(e -> group.getKey().contains(e.getType()))
+ .count();
+ int max = group.getValue() + +(ibc == null ? 0 : ibc.getEntityGroupLimitOffset(group.getKey().getName()));
+ if (count >= max) {
+ return new AtLimitResult(group.getKey(), max);
+ }
+ }
+ return new AtLimitResult();
+ }
+
+ static class AtLimitResult {
+ private Map.Entry typelimit;
+ private Map.Entry grouplimit;
+
+ public AtLimitResult() {
+ }
+
+ public AtLimitResult(EntityType type, int limit) {
+ typelimit = new AbstractMap.SimpleEntry<>(type, limit);
+ }
+
+ public AtLimitResult(EntityGroup type, int limit) {
+ grouplimit = new AbstractMap.SimpleEntry<>(type, limit);
+ }
+
+ /**
+ * @return true if at limit
+ */
+ public boolean hit() {
+ return typelimit != null || grouplimit != null;
+ }
+
+ public Map.Entry getTypelimit() {
+ return typelimit;
+ }
+
+ public Map.Entry getGrouplimit() {
+ return grouplimit;
+ }
+ }
+}
+
+
diff --git a/src/main/java/world/bentobox/limits/listeners/JoinListener.java b/src/main/java/world/bentobox/limits/listeners/JoinListener.java
index 3bc93ae..b163cbd 100644
--- a/src/main/java/world/bentobox/limits/listeners/JoinListener.java
+++ b/src/main/java/world/bentobox/limits/listeners/JoinListener.java
@@ -1,277 +1,277 @@
-package world.bentobox.limits.listeners;
-
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.UUID;
-
-import org.bukkit.Bukkit;
-import org.bukkit.Material;
-import org.bukkit.OfflinePlayer;
-import org.bukkit.World;
-import org.bukkit.entity.EntityType;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.EventPriority;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.permissions.PermissionAttachmentInfo;
-import org.eclipse.jdt.annotation.NonNull;
-
-import world.bentobox.bentobox.api.events.island.IslandEvent;
-import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
-import world.bentobox.bentobox.api.events.team.TeamSetownerEvent;
-import world.bentobox.bentobox.database.objects.Island;
-import world.bentobox.limits.EntityGroup;
-import world.bentobox.limits.Limits;
-import world.bentobox.limits.events.LimitsJoinPermCheckEvent;
-import world.bentobox.limits.events.LimitsPermCheckEvent;
-import world.bentobox.limits.objects.IslandBlockCount;
-
-/**
- * Sets block limits based on player permission
- *
- * @author tastybento
- *
- */
-public class JoinListener implements Listener {
-
- private final Limits addon;
-
- public JoinListener(Limits addon) {
- this.addon = addon;
- }
-
- /**
- * Check and set the permissions of the player and how they affect the island
- * limits
- *
- * @param player - player
- * @param permissionPrefix - permission prefix for this game mode
- * @param islandId - island string id
- * @param gameMode - game mode string doing the checking
- */
- public void checkPerms(Player player, String permissionPrefix, String islandId, String gameMode) {
- IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId);
- // Check permissions
- if (ibc != null) {
- // Clear permission limits
- ibc.getEntityLimits().clear();
- ibc.getEntityGroupLimits().clear();
- ibc.getBlockLimits().clear();
- }
- for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) {
- if (!perms.getValue() || !perms.getPermission().startsWith(permissionPrefix)
- || badSyntaxCheck(perms, player.getName(), permissionPrefix)) {
- continue;
- }
- // Check formatting
- String[] split = perms.getPermission().split("\\.");
- // Entities & materials
- EntityType et = Arrays.stream(EntityType.values()).filter(t -> t.name().equalsIgnoreCase(split[3]))
- .findFirst().orElse(null);
- Material m = Arrays.stream(Material.values()).filter(t -> t.name().equalsIgnoreCase(split[3])).findFirst()
- .orElse(null);
- EntityGroup entgroup = addon.getSettings().getGroupLimitDefinitions().stream()
- .filter(t -> t.getName().equalsIgnoreCase(split[3])).findFirst().orElse(null);
-
- if (entgroup == null && et == null && m == null) {
- logError(player.getName(), perms.getPermission(),
- split[3].toUpperCase(Locale.ENGLISH) + " is not a valid material or entity type/group.");
- break;
- }
- // Make an ibc if required
- if (ibc == null) {
- ibc = new IslandBlockCount(islandId, gameMode);
- }
- // Get the value
- int value = Integer.parseInt(split[4]);
- addon.log("Setting login limit via perm for " + player.getName() + "...");
-
- // Fire perm check event
- LimitsPermCheckEvent l = new LimitsPermCheckEvent(player, islandId, ibc, entgroup, et, m, value);
- Bukkit.getPluginManager().callEvent(l);
- if (l.isCancelled()) {
- addon.log("Permissions not set because another addon/plugin canceled setting.");
- continue;
- }
- // Use event values
- ibc = l.getIbc();
- // Make an ibc if required
- if (ibc == null) {
- ibc = new IslandBlockCount(islandId, gameMode);
- }
- // Run null checks and set ibc
- runNullCheckAndSet(ibc, l);
- }
- // Check removed permissions
- // If any changes have been made then store it - don't make files unless they
- // are needed
- if (ibc != null)
- addon.getBlockLimitListener().setIsland(islandId, ibc);
- }
-
- private boolean badSyntaxCheck(PermissionAttachmentInfo perms, String name, String permissionPrefix) {
- // No wildcards
- if (perms.getPermission().contains(permissionPrefix + "*")) {
- logError(name, perms.getPermission(), "wildcards are not allowed.");
- return true;
- }
- // Check formatting
- String[] split = perms.getPermission().split("\\.");
- if (split.length != 5) {
- logError(name, perms.getPermission(), "format must be '" + permissionPrefix + "MATERIAL.NUMBER', '"
- + permissionPrefix + "ENTITY-TYPE.NUMBER', or '" + permissionPrefix + "ENTITY-GROUP.NUMBER'");
- return true;
- }
- // Check value
- try {
- Integer.parseInt(split[4]);
- } catch (Exception e) {
- logError(name, perms.getPermission(), "the last part MUST be an integer!");
- return true;
- }
- return false;
- }
-
- private void runNullCheckAndSet(@NonNull IslandBlockCount ibc, @NonNull LimitsPermCheckEvent l) {
- EntityGroup entgroup = l.getEntityGroup();
- EntityType et = l.getEntityType();
- Material m = l.getMaterial();
- int value = l.getValue();
- if (entgroup != null) {
- // Entity group limit
- int v = Math.max(ibc.getEntityGroupLimit(entgroup.getName()), value);
- ibc.setEntityGroupLimit(entgroup.getName(), v);
- addon.log("Setting group limit " + entgroup.getName() + " " + v);
- } else if (et != null && m == null) {
- // Entity limit
- int v = Math.max(ibc.getEntityLimit(et), value);
- ibc.setEntityLimit(et, v);
- addon.log("Setting entity limit " + et + " " + v);
- } else if (m != null && et == null) {
- // Block limit
- int v = Math.max(ibc.getBlockLimit(m), value);
- addon.log("Setting block limit " + m + " " + v);
- ibc.setBlockLimit(m, v);
- } else {
- if (m != null && m.isBlock()) {
- int v = Math.max(ibc.getBlockLimit(m), value);
- addon.log("Setting block limit " + m + " " + v);
- // Material limit
- ibc.setBlockLimit(m, v);
- } else if (et != null) {
- int v = Math.max(ibc.getEntityLimit(et), value);
- addon.log("Setting entity limit " + et + " " + v);
- // This is an entity setting
- ibc.setEntityLimit(et, v);
- }
- }
-
- }
-
- private void logError(String name, String perm, String error) {
- addon.logError("Player " + name + " has permission: '" + perm + "' but " + error + " Ignoring...");
- }
-
- /*
- * Event handling
- */
-
- @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
- public void onNewIsland(IslandEvent e) {
- if (!e.getReason().equals(Reason.CREATED) && !e.getReason().equals(Reason.RESETTED)
- && !e.getReason().equals(Reason.REGISTERED)) {
- return;
- }
- setOwnerPerms(e.getIsland(), e.getOwner());
- }
-
- @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
- public void onOwnerChange(TeamSetownerEvent e) {
- removeOwnerPerms(e.getIsland());
- setOwnerPerms(e.getIsland(), e.getNewOwner());
- }
-
- @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
- public void onPlayerJoin(PlayerJoinEvent e) {
- // Check if player has any islands in the game modes
- addon.getGameModes().forEach(gm -> {
- addon.getIslands().getIslands(gm.getOverWorld(), e.getPlayer().getUniqueId()).stream()
- .filter(island -> e.getPlayer().getUniqueId().equals(island.getOwner()))
- .map(Island::getUniqueId).forEach(islandId -> {
- IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId);
- if (!joinEventCheck(e.getPlayer(), islandId, ibc)) {
- checkPerms(e.getPlayer(), gm.getPermissionPrefix() + "island.limit.", islandId,
- gm.getDescription().getName());
- }
- });
- });
- }
-
- /**
- * Fire event so other addons can cancel this permissions change
- *
- * @param player player
- * @param islandId island id
- * @param ibc island block count
- * @return true if canceled
- */
- private boolean joinEventCheck(Player player, String islandId, IslandBlockCount ibc) {
- // Fire event, so other addons can cancel this permissions change
- LimitsJoinPermCheckEvent e = new LimitsJoinPermCheckEvent(player, islandId, ibc);
- Bukkit.getPluginManager().callEvent(e);
- if (e.isCancelled()) {
- return true;
- }
- // Get ibc from event if it has changed
- ibc = e.getIbc();
- // If perms should be ignored, but the IBC given in the event used, then set it
- // and return
- if (e.isIgnorePerms() && ibc != null) {
- addon.getBlockLimitListener().setIsland(islandId, ibc);
- return true;
- }
- return false;
- }
-
- @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
- public void onUnregisterIsland(IslandEvent e) {
- if (!e.getReason().equals(Reason.UNREGISTERED)) {
- return;
- }
- removeOwnerPerms(e.getIsland());
- }
-
- /*
- * Utility methods
- */
-
- private void removeOwnerPerms(Island island) {
- World world = island.getWorld();
- if (addon.inGameModeWorld(world)) {
- IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId());
- if (ibc != null) {
- ibc.getBlockLimits().clear();
- }
- }
- }
-
- private void setOwnerPerms(Island island, UUID ownerUUID) {
- World world = island.getWorld();
- if (addon.inGameModeWorld(world)) {
- // Check if owner is online
- OfflinePlayer owner = Bukkit.getOfflinePlayer(ownerUUID);
- if (owner.isOnline()) {
- // Set perm-based limits
- String prefix = addon.getGameModePermPrefix(world);
- String name = addon.getGameModeName(world);
- if (!prefix.isEmpty() && !name.isEmpty() && owner.getPlayer() != null) {
- checkPerms(Objects.requireNonNull(owner.getPlayer()), prefix + "island.limit.",
- island.getUniqueId(), name);
- }
- }
- }
- }
-
-}
+package world.bentobox.limits.listeners;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.World;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+import org.eclipse.jdt.annotation.NonNull;
+
+import world.bentobox.bentobox.api.events.island.IslandEvent;
+import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
+import world.bentobox.bentobox.api.events.team.TeamSetownerEvent;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.limits.EntityGroup;
+import world.bentobox.limits.Limits;
+import world.bentobox.limits.events.LimitsJoinPermCheckEvent;
+import world.bentobox.limits.events.LimitsPermCheckEvent;
+import world.bentobox.limits.objects.IslandBlockCount;
+
+/**
+ * Sets block limits based on player permission
+ *
+ * @author tastybento
+ *
+ */
+public class JoinListener implements Listener {
+
+ private final Limits addon;
+
+ public JoinListener(Limits addon) {
+ this.addon = addon;
+ }
+
+ /**
+ * Check and set the permissions of the player and how they affect the island
+ * limits
+ *
+ * @param player - player
+ * @param permissionPrefix - permission prefix for this game mode
+ * @param islandId - island string id
+ * @param gameMode - game mode string doing the checking
+ */
+ public void checkPerms(Player player, String permissionPrefix, String islandId, String gameMode) {
+ IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId);
+ // Check permissions
+ if (ibc != null) {
+ // Clear permission limits
+ ibc.getEntityLimits().clear();
+ ibc.getEntityGroupLimits().clear();
+ ibc.getBlockLimits().clear();
+ }
+ for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) {
+ if (!perms.getValue() || !perms.getPermission().startsWith(permissionPrefix)
+ || badSyntaxCheck(perms, player.getName(), permissionPrefix)) {
+ continue;
+ }
+ // Check formatting
+ String[] split = perms.getPermission().split("\\.");
+ // Entities & materials
+ EntityType et = Arrays.stream(EntityType.values()).filter(t -> t.name().equalsIgnoreCase(split[3]))
+ .findFirst().orElse(null);
+ Material m = Arrays.stream(Material.values()).filter(t -> t.name().equalsIgnoreCase(split[3])).findFirst()
+ .orElse(null);
+ EntityGroup entgroup = addon.getSettings().getGroupLimitDefinitions().stream()
+ .filter(t -> t.getName().equalsIgnoreCase(split[3])).findFirst().orElse(null);
+
+ if (entgroup == null && et == null && m == null) {
+ logError(player.getName(), perms.getPermission(),
+ split[3].toUpperCase(Locale.ENGLISH) + " is not a valid material or entity type/group.");
+ break;
+ }
+ // Make an ibc if required
+ if (ibc == null) {
+ ibc = new IslandBlockCount(islandId, gameMode);
+ }
+ // Get the value
+ int value = Integer.parseInt(split[4]);
+ addon.log("Setting login limit via perm for " + player.getName() + "...");
+
+ // Fire perm check event
+ LimitsPermCheckEvent l = new LimitsPermCheckEvent(player, islandId, ibc, entgroup, et, m, value);
+ Bukkit.getPluginManager().callEvent(l);
+ if (l.isCancelled()) {
+ addon.log("Permissions not set because another addon/plugin canceled setting.");
+ continue;
+ }
+ // Use event values
+ ibc = l.getIbc();
+ // Make an ibc if required
+ if (ibc == null) {
+ ibc = new IslandBlockCount(islandId, gameMode);
+ }
+ // Run null checks and set ibc
+ runNullCheckAndSet(ibc, l);
+ }
+ // Check removed permissions
+ // If any changes have been made then store it - don't make files unless they
+ // are needed
+ if (ibc != null)
+ addon.getBlockLimitListener().setIsland(islandId, ibc);
+ }
+
+ private boolean badSyntaxCheck(PermissionAttachmentInfo perms, String name, String permissionPrefix) {
+ // No wildcards
+ if (perms.getPermission().contains(permissionPrefix + "*")) {
+ logError(name, perms.getPermission(), "wildcards are not allowed.");
+ return true;
+ }
+ // Check formatting
+ String[] split = perms.getPermission().split("\\.");
+ if (split.length != 5) {
+ logError(name, perms.getPermission(), "format must be '" + permissionPrefix + "MATERIAL.NUMBER', '"
+ + permissionPrefix + "ENTITY-TYPE.NUMBER', or '" + permissionPrefix + "ENTITY-GROUP.NUMBER'");
+ return true;
+ }
+ // Check value
+ try {
+ Integer.parseInt(split[4]);
+ } catch (Exception e) {
+ logError(name, perms.getPermission(), "the last part MUST be an integer!");
+ return true;
+ }
+ return false;
+ }
+
+ private void runNullCheckAndSet(@NonNull IslandBlockCount ibc, @NonNull LimitsPermCheckEvent l) {
+ EntityGroup entgroup = l.getEntityGroup();
+ EntityType et = l.getEntityType();
+ Material m = l.getMaterial();
+ int value = l.getValue();
+ if (entgroup != null) {
+ // Entity group limit
+ int v = Math.max(ibc.getEntityGroupLimit(entgroup.getName()), value);
+ ibc.setEntityGroupLimit(entgroup.getName(), v);
+ addon.log("Setting group limit " + entgroup.getName() + " " + v);
+ } else if (et != null && m == null) {
+ // Entity limit
+ int v = Math.max(ibc.getEntityLimit(et), value);
+ ibc.setEntityLimit(et, v);
+ addon.log("Setting entity limit " + et + " " + v);
+ } else if (m != null && et == null) {
+ // Block limit
+ int v = Math.max(ibc.getBlockLimit(m), value);
+ addon.log("Setting block limit " + m + " " + v);
+ ibc.setBlockLimit(m, v);
+ } else {
+ if (m != null && m.isBlock()) {
+ int v = Math.max(ibc.getBlockLimit(m), value);
+ addon.log("Setting block limit " + m + " " + v);
+ // Material limit
+ ibc.setBlockLimit(m, v);
+ } else if (et != null) {
+ int v = Math.max(ibc.getEntityLimit(et), value);
+ addon.log("Setting entity limit " + et + " " + v);
+ // This is an entity setting
+ ibc.setEntityLimit(et, v);
+ }
+ }
+
+ }
+
+ private void logError(String name, String perm, String error) {
+ addon.logError("Player " + name + " has permission: '" + perm + "' but " + error + " Ignoring...");
+ }
+
+ /*
+ * Event handling
+ */
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onNewIsland(IslandEvent e) {
+ if (!e.getReason().equals(Reason.CREATED) && !e.getReason().equals(Reason.RESETTED)
+ && !e.getReason().equals(Reason.REGISTERED)) {
+ return;
+ }
+ setOwnerPerms(e.getIsland(), e.getOwner());
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onOwnerChange(TeamSetownerEvent e) {
+ removeOwnerPerms(e.getIsland());
+ setOwnerPerms(e.getIsland(), e.getNewOwner());
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onPlayerJoin(PlayerJoinEvent e) {
+ // Check if player has any islands in the game modes
+ addon.getGameModes().forEach(gm -> {
+ addon.getIslands().getIslands(gm.getOverWorld(), e.getPlayer().getUniqueId()).stream()
+ .filter(island -> e.getPlayer().getUniqueId().equals(island.getOwner()))
+ .map(Island::getUniqueId).forEach(islandId -> {
+ IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(islandId);
+ if (!joinEventCheck(e.getPlayer(), islandId, ibc)) {
+ checkPerms(e.getPlayer(), gm.getPermissionPrefix() + "island.limit.", islandId,
+ gm.getDescription().getName());
+ }
+ });
+ });
+ }
+
+ /**
+ * Fire event so other addons can cancel this permissions change
+ *
+ * @param player player
+ * @param islandId island id
+ * @param ibc island block count
+ * @return true if canceled
+ */
+ private boolean joinEventCheck(Player player, String islandId, IslandBlockCount ibc) {
+ // Fire event, so other addons can cancel this permissions change
+ LimitsJoinPermCheckEvent e = new LimitsJoinPermCheckEvent(player, islandId, ibc);
+ Bukkit.getPluginManager().callEvent(e);
+ if (e.isCancelled()) {
+ return true;
+ }
+ // Get ibc from event if it has changed
+ ibc = e.getIbc();
+ // If perms should be ignored, but the IBC given in the event used, then set it
+ // and return
+ if (e.isIgnorePerms() && ibc != null) {
+ addon.getBlockLimitListener().setIsland(islandId, ibc);
+ return true;
+ }
+ return false;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onUnregisterIsland(IslandEvent e) {
+ if (!e.getReason().equals(Reason.UNREGISTERED)) {
+ return;
+ }
+ removeOwnerPerms(e.getIsland());
+ }
+
+ /*
+ * Utility methods
+ */
+
+ private void removeOwnerPerms(Island island) {
+ World world = island.getWorld();
+ if (addon.inGameModeWorld(world)) {
+ IslandBlockCount ibc = addon.getBlockLimitListener().getIsland(island.getUniqueId());
+ if (ibc != null) {
+ ibc.getBlockLimits().clear();
+ }
+ }
+ }
+
+ private void setOwnerPerms(Island island, UUID ownerUUID) {
+ World world = island.getWorld();
+ if (addon.inGameModeWorld(world)) {
+ // Check if owner is online
+ OfflinePlayer owner = Bukkit.getOfflinePlayer(ownerUUID);
+ if (owner.isOnline()) {
+ // Set perm-based limits
+ String prefix = addon.getGameModePermPrefix(world);
+ String name = addon.getGameModeName(world);
+ if (!prefix.isEmpty() && !name.isEmpty() && owner.getPlayer() != null) {
+ checkPerms(Objects.requireNonNull(owner.getPlayer()), prefix + "island.limit.",
+ island.getUniqueId(), name);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/world/bentobox/limits/objects/EntityLimitsDO.java b/src/main/java/world/bentobox/limits/objects/EntityLimitsDO.java
index 03caac7..b162698 100644
--- a/src/main/java/world/bentobox/limits/objects/EntityLimitsDO.java
+++ b/src/main/java/world/bentobox/limits/objects/EntityLimitsDO.java
@@ -1,92 +1,92 @@
-package world.bentobox.limits.objects;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-import com.google.gson.annotations.Expose;
-
-import world.bentobox.bentobox.database.objects.DataObject;
-import world.bentobox.bentobox.database.objects.Table;
-
-/**
- * @author tastybento
- *
- */
-@Table(name = "EntityLimits")
-public class EntityLimitsDO implements DataObject {
-
- @Expose
- private String uniqueId = "";
- @Expose
- private Map spawnLoc = new HashMap<>();
-
- public EntityLimitsDO() {}
-
- public EntityLimitsDO(String uniqueId) {
- this.uniqueId = uniqueId;
- }
-
- /* (non-Javadoc)
- * @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId()
- */
- @Override
- public String getUniqueId() {
- return uniqueId;
- }
-
- /* (non-Javadoc)
- * @see world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.String)
- */
- @Override
- public void setUniqueId(String uniqueId) {
- this.uniqueId = uniqueId;
-
- }
-
- /**
- * @return the spawnLoc
- */
- public Map getSpawnLoc() {
- return spawnLoc;
- }
-
- /**
- * @param spawnLoc the spawnLoc to set
- */
- public void setSpawnLoc(Map spawnLoc) {
- this.spawnLoc = spawnLoc;
- }
-
- /* (non-Javadoc)
- * @see java.lang.Object#hashCode()
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((uniqueId == null) ? 0 : uniqueId.hashCode());
- return result;
- }
-
- /* (non-Javadoc)
- * @see java.lang.Object#equals(java.lang.Object)
- */
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof EntityLimitsDO other)) {
- return false;
- }
- if (uniqueId == null) {
- return other.uniqueId == null;
- } else return uniqueId.equals(other.uniqueId);
- }
-
-
-}
+package world.bentobox.limits.objects;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import com.google.gson.annotations.Expose;
+
+import world.bentobox.bentobox.database.objects.DataObject;
+import world.bentobox.bentobox.database.objects.Table;
+
+/**
+ * @author tastybento
+ *
+ */
+@Table(name = "EntityLimits")
+public class EntityLimitsDO implements DataObject {
+
+ @Expose
+ private String uniqueId = "";
+ @Expose
+ private Map spawnLoc = new HashMap<>();
+
+ public EntityLimitsDO() {}
+
+ public EntityLimitsDO(String uniqueId) {
+ this.uniqueId = uniqueId;
+ }
+
+ /* (non-Javadoc)
+ * @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId()
+ */
+ @Override
+ public String getUniqueId() {
+ return uniqueId;
+ }
+
+ /* (non-Javadoc)
+ * @see world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.String)
+ */
+ @Override
+ public void setUniqueId(String uniqueId) {
+ this.uniqueId = uniqueId;
+
+ }
+
+ /**
+ * @return the spawnLoc
+ */
+ public Map getSpawnLoc() {
+ return spawnLoc;
+ }
+
+ /**
+ * @param spawnLoc the spawnLoc to set
+ */
+ public void setSpawnLoc(Map spawnLoc) {
+ this.spawnLoc = spawnLoc;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((uniqueId == null) ? 0 : uniqueId.hashCode());
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof EntityLimitsDO other)) {
+ return false;
+ }
+ if (uniqueId == null) {
+ return other.uniqueId == null;
+ } else return uniqueId.equals(other.uniqueId);
+ }
+
+
+}
diff --git a/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java b/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java
index 1ff6cc8..46d6f9f 100644
--- a/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java
+++ b/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java
@@ -1,417 +1,417 @@
-package world.bentobox.limits.objects;
-
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-
-import org.bukkit.Material;
-import org.bukkit.entity.EntityType;
-
-import com.google.gson.annotations.Expose;
-
-import world.bentobox.bentobox.database.objects.DataObject;
-import world.bentobox.bentobox.database.objects.Table;
-
-/**
- * @author tastybento
- *
- */
-@Table(name = "IslandBlockCount")
-public class IslandBlockCount implements DataObject {
-
- @Expose
- private Map blockCounts = new EnumMap<>(Material.class);
-
- /**
- * Permission based limits
- */
- @Expose
- private Map blockLimits = new EnumMap<>(Material.class);
-
- @Expose
- private Map blockLimitsOffset = new EnumMap<>(Material.class);
-
- private boolean changed;
-
- @Expose
- private Map entityGroupLimits = new HashMap<>();
- @Expose
- private Map entityGroupLimitsOffset = new HashMap<>();
- @Expose
- private Map entityLimits = new EnumMap<>(EntityType.class);
- @Expose
- private Map entityLimitsOffset = new EnumMap<>(EntityType.class);
- @Expose
- private String gameMode;
- @Expose
- private String uniqueId;
-
- /**
- * Create an island block count object
- *
- * @param islandId - unique Island ID string
- * @param gameMode - Game mode name from gm.getDescription().getName()
- */
- public IslandBlockCount(String islandId, String gameMode) {
- this.uniqueId = islandId;
- this.gameMode = gameMode;
- setChanged();
- }
-
- /**
- * Add a material to the count
- *
- * @param material - material
- */
- public void add(Material material) {
- getBlockCounts().merge(material, 1, Integer::sum);
- setChanged();
- }
-
- /**
- * Clear all island-specific entity group limits
- */
- public void clearEntityGroupLimits() {
- entityGroupLimits.clear();
- setChanged();
- }
-
- /**
- * Clear all island-specific entity type limits
- */
- public void clearEntityLimits() {
- entityLimits.clear();
- setChanged();
- }
-
- /**
- * Get the block count for this material for this island
- *
- * @param m - material
- * @return count
- */
- public Integer getBlockCount(Material m) {
- return getBlockCounts().getOrDefault(m, 0);
- }
-
- /**
- * @return the blockCount
- */
- public Map