diff --git a/pom.xml b/pom.xml
index a35d5b3..a6ed8b8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -125,6 +125,10 @@
codemc-repo
https://repo.codemc.org/repository/maven-public/
+
+ jitpack-repo
+ https://jitpack.io
+
@@ -160,6 +164,12 @@
${bentobox.version}
provided
+
+ com.github.LoneDev6
+ api-itemsadder
+ 3.4.1-r4
+ provided
+
diff --git a/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java b/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java
index 5d64bd4..8507ab9 100644
--- a/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java
+++ b/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java
@@ -1,6 +1,14 @@
package world.bentobox.limits.listeners;
-import java.util.*;
+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;
@@ -15,13 +23,27 @@ 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.*;
+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.bukkit.plugin.Plugin;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
+import dev.lone.itemsadder.api.CustomBlock;
import world.bentobox.bentobox.api.events.island.IslandDeleteEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
@@ -59,7 +81,10 @@ public class BlockLimitsListener implements Listener {
private final Map saveMap = new HashMap<>();
private final Database handler;
private final Map> worldLimitMap = new HashMap<>();
+ //private final Map> customWorldLimitMap = new HashMap<>();
private Map defaultLimitMap = new EnumMap<>(Material.class);
+ private Map defaultCustomLimitMap = new HashMap<>();
+ private Plugin itemsAdder;
public BlockLimitsListener(Limits addon) {
this.addon = addon;
@@ -89,6 +114,11 @@ public class BlockLimitsListener implements Listener {
ConfigurationSection limitConfig = addon.getConfig().getConfigurationSection("blocklimits");
defaultLimitMap = loadLimits(Objects.requireNonNull(limitConfig));
}
+ // Load custom blocks
+ if (addon.getConfig().isConfigurationSection("customblocklimits")) {
+ ConfigurationSection limitConfig = addon.getConfig().getConfigurationSection("customblocklimits");
+ defaultCustomLimitMap = loadCustomLimits(Objects.requireNonNull(limitConfig));
+ }
// Load specific worlds
if (addon.getConfig().isConfigurationSection("worlds")) {
@@ -126,6 +156,21 @@ public class BlockLimitsListener implements Listener {
return mats;
}
+ /**
+ * Loads custom limit map from configuration section
+ *
+ * @param cs - configuration section
+ * @return limit map
+ */
+ private Map loadCustomLimits(ConfigurationSection cs) {
+ Map mats = new HashMap<>();
+ for (String material : cs.getKeys(false)) {
+ mats.put(material, cs.getInt(material));
+ addon.log("Limit " + material + " to " + cs.getInt(material));
+ }
+ return mats;
+ }
+
/**
* Save the count database completely
@@ -137,7 +182,7 @@ public class BlockLimitsListener implements Listener {
// 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());
+ notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
@@ -187,7 +232,7 @@ public class BlockLimitsListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBlock(BlockMultiPlaceEvent e) {
- notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock().getType());
+ notify(e, User.getInstance(e.getPlayer()), process(e.getBlock(), true), e.getBlock());
}
/**
@@ -197,10 +242,15 @@ public class BlockLimitsListener implements Listener {
* @param limit maximum limit allowed
* @param m material
*/
- private void notify(Cancellable e, User user, int limit, Material m) {
+ private void notify(Cancellable e, User user, int limit, Block block) {
if (limit > -1) {
+ String name = Util.prettifyText(block.getType().toString());
+ if (this.itemsAdder != null) {
+ CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block);
+ name = customBlock.getDisplayName();
+ }
user.notify("block-limits.hit-limit",
- "[material]", Util.prettifyText(m.toString()),
+ "[material]", name,
TextVariables.NUMBER, String.valueOf(limit));
e.setCancelled(true);
}
@@ -318,6 +368,8 @@ public class BlockLimitsListener implements Listener {
* @return limit amount if over limit, or -1 if no limitation
*/
private int process(Block b, boolean add) {
+ // Check for custom block handlers
+ checkCustom();
if (DO_NOT_COUNT.contains(fixMaterial(b.getBlockData())) || !addon.inGameModeWorld(b.getWorld())) {
return -1;
}
@@ -335,12 +387,27 @@ public class BlockLimitsListener implements Listener {
}
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;
+ // Check if custom block
+ if (itemsAdder != null ) {
+ CustomBlock customBlock = CustomBlock.byAlreadyPlaced(b);
+ if(customBlock != null) {
+ // Custom block
+ // Check limit
+ int limit = checkCustomLimit(b.getWorld(), b, id);
+ if (limit > -1) {
+ return limit;
+ }
+ islandCountMap.get(id).add(b);
+ }
+
+ } else {
+ // Check limit
+ int limit = checkLimit(b.getWorld(), fixMaterial(b.getBlockData()), id);
+ if (limit > -1) {
+ return limit;
+ }
+ islandCountMap.get(id).add(fixMaterial(b.getBlockData()));
}
- islandCountMap.get(id).add(fixMaterial(b.getBlockData()));
} else {
if (islandCountMap.containsKey(id)) {
islandCountMap.get(id).remove(fixMaterial(b.getBlockData()));
@@ -351,6 +418,11 @@ public class BlockLimitsListener implements Listener {
}).orElse(-1);
}
+ private void checkCustom() {
+ itemsAdder = Bukkit.getPluginManager().getPlugin("ItemsAdder");
+
+ }
+
/**
* Removed a block from any island limit count
* @param b - block to remove
@@ -364,10 +436,17 @@ public class BlockLimitsListener implements Listener {
// Invalid world
return;
}
- islandCountMap.computeIfAbsent(id, k -> new IslandBlockCount(id, gameMode)).remove(fixMaterial(b.getBlockData()));
- updateSaveMap(id);
+ // Check for custom block
+ if (this.itemsAdder != null && CustomBlock.byAlreadyPlaced(b) != null) {
+ islandCountMap.computeIfAbsent(id, k -> new IslandBlockCount(id, gameMode)).remove(b);
+ updateSaveMap(id);
+ } else {
+ 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) {
@@ -404,6 +483,37 @@ public class BlockLimitsListener implements Listener {
return -1;
}
+ /**
+ * Check if this custom block is at its limit for world on this island
+ *
+ * @param w - world
+ * @param block - custom block
+ * @param id - island id
+ * @return limit amount if at limit or -1 if no limit
+ */
+ private int checkCustomLimit(World w, Block block, String islandId) {
+ // Check island limits
+ IslandBlockCount ibc = islandCountMap.get(islandId);
+ if (ibc.isCustomBlockLimited(block)) {
+ return ibc.isAtLimit(block) ? ibc.getBlockLimit(block) : -1;
+ }
+ CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block);
+ String id = Objects.requireNonNull(customBlock).getNamespacedID();
+ /* NOT SUPPORTED YET
+ // Check specific world limits
+ if (customWorldLimitMap.containsKey(w) && customWorldLimitMap.get(w).containsKey(id)) {
+ // Material is overridden in world
+ return ibc.isAtLimit(block, worldLimitMap.get(w).get(id)) ? worldLimitMap.get(w).get(id) : -1; // TODO Add perm offset
+ }
+ */
+ // Check default limit map
+ if (defaultCustomLimitMap.containsKey(id) && ibc.isAtLimit(block, defaultCustomLimitMap.get(id))) {
+ return defaultCustomLimitMap.get(id);// TODO add perm offset
+ }
+ // No limit
+ return -1;
+ }
+
/**
* Gets an aggregate map of the limits for this island
*
@@ -427,7 +537,7 @@ public class BlockLimitsListener implements Listener {
// Add offsets to the every limit.
islandBlockCount.getBlockLimitsOffset().forEach((material, offset) ->
- result.put(material, result.getOrDefault(material, 0) + offset));
+ result.put(material, result.getOrDefault(material, 0) + offset));
}
return result;
}
diff --git a/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java b/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java
index 8cc137e..2cb8751 100644
--- a/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java
+++ b/src/main/java/world/bentobox/limits/objects/IslandBlockCount.java
@@ -3,12 +3,15 @@ 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.block.Block;
import org.bukkit.entity.EntityType;
import com.google.gson.annotations.Expose;
+import dev.lone.itemsadder.api.CustomBlock;
import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
@@ -28,6 +31,9 @@ public class IslandBlockCount implements DataObject {
@Expose
private Map blockCounts = new EnumMap<>(Material.class);
+ @Expose
+ private Map customBlockCounts = new HashMap<>();
+
private boolean changed;
/**
@@ -36,6 +42,8 @@ public class IslandBlockCount implements DataObject {
@Expose
private Map blockLimits = new EnumMap<>(Material.class);
@Expose
+ private Map customBlockLimits = new HashMap<>();
+ @Expose
private Map entityLimits = new EnumMap<>(EntityType.class);
@Expose
private Map entityGroupLimits = new HashMap<>();
@@ -120,6 +128,28 @@ public class IslandBlockCount implements DataObject {
setChanged();
}
+ /**
+ * Add a custom block to the count
+ * @param block - custom block
+ */
+ public void add(Block block) {
+ CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block);
+ customBlockCounts.merge(Objects.requireNonNull(customBlock).getNamespacedID(), 1, Integer::sum);
+ setChanged();
+ }
+
+ /**
+ * Remove a custom block from the count
+ * @param block - block
+ */
+ public void remove(Block block) {
+ CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block);
+ String id = Objects.requireNonNull(customBlock).getNamespacedID();
+ customBlockCounts.put(id, blockCounts.getOrDefault(id, 0) - 1);
+ blockCounts.values().removeIf(v -> v <= 0);
+ setChanged();
+ }
+
/**
* Check if this material is at or over a limit
* @param material - block material
@@ -129,6 +159,18 @@ public class IslandBlockCount implements DataObject {
public boolean isAtLimit(Material material, int limit) {
return blockCounts.getOrDefault(material, 0) >= limit + this.getBlockLimitOffset(material);
}
+
+ /**
+ * Check if this custom block is at or over a limit
+ * @param block - block
+ * @param limit - limit to check
+ * @return true if count is >= limit
+ */
+ public boolean isAtLimit(Block block, int limit) {
+ CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block);
+ String id = Objects.requireNonNull(customBlock).getNamespacedID();
+ return customBlockCounts.getOrDefault(id, 0) >= limit; // TODO Add a permission offset
+ }
/**
* Check if no more of this material can be added to this island
@@ -139,10 +181,28 @@ public class IslandBlockCount implements DataObject {
// Check island limits first
return blockLimits.containsKey(m) && blockCounts.getOrDefault(m, 0) >= getBlockLimit(m) + this.getBlockLimitOffset(m);
}
+
+ /**
+ * Check if no more of this custom block can be added to this island
+ * @param block - block
+ * @return true if no more block can be added
+ */
+ public boolean isAtLimit(Block block) {
+ CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block);
+ String id = Objects.requireNonNull(customBlock).getNamespacedID();
+ // Check island limits first
+ return customBlockLimits.containsKey(id) && customBlockCounts.getOrDefault(id, 0) >= getBlockLimit(block); // TODO add perm offset
+ }
public boolean isBlockLimited(Material m) {
return blockLimits.containsKey(m);
}
+
+ public boolean isCustomBlockLimited(Block block) {
+ CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block);
+ String id = Objects.requireNonNull(customBlock).getNamespacedID();
+ return customBlockLimits.containsKey(id);
+ }
/**
* @return the blockLimits
@@ -167,6 +227,17 @@ public class IslandBlockCount implements DataObject {
public int getBlockLimit(Material m) {
return blockLimits.getOrDefault(m, -1);
}
+
+ /**
+ * Get the custom block limit for this material for this island
+ * @param block - block
+ * @return limit or -1 for unlimited
+ */
+ public int getBlockLimit(Block block) {
+ CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block);
+ String id = Objects.requireNonNull(customBlock).getNamespacedID();
+ return customBlockLimits.getOrDefault(id, -1);
+ }
/**
* Get the block offset for this material for this island
@@ -186,6 +257,16 @@ public class IslandBlockCount implements DataObject {
blockLimits.put(m, limit);
setChanged();
}
+
+ /**
+ * Set the custom block limit for this island
+ * @param id - namespaced id
+ * @param limit - maximum number allowed
+ */
+ public void setCustomBlockLimit(String id, int limit) {
+ customBlockLimits.put(id, limit);
+ setChanged();
+ }
/**
* @return the gameMode
@@ -385,4 +466,32 @@ public class IslandBlockCount implements DataObject {
public void setEntityGroupLimitsOffset(String name, Integer entityGroupLimitsOffset) {
getEntityGroupLimitsOffset().put(name, entityGroupLimitsOffset);
}
+
+ /**
+ * @return the customBlockCounts
+ */
+ public Map getCustomBlockCounts() {
+ return customBlockCounts;
+ }
+
+ /**
+ * @param customBlockCounts the customBlockCounts to set
+ */
+ public void setCustomBlockCounts(Map customBlockCounts) {
+ this.customBlockCounts = customBlockCounts;
+ }
+
+ /**
+ * @return the customBlockLimits
+ */
+ public Map getCustomBlockLimits() {
+ return customBlockLimits;
+ }
+
+ /**
+ * @param customBlockLimits the customBlockLimits to set
+ */
+ public void setCustomBlockLimits(Map customBlockLimits) {
+ this.customBlockLimits = customBlockLimits;
+ }
}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 30f8c61..f872393 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -26,6 +26,13 @@ async-golums: true
# These limits apply to every game mode world
blocklimits:
HOPPER: 10
+
+# Custom block limits
+# Requires use of the ItemsAdder plugin. Custom blocks are currently only supported globally and not on a per-world basis.
+# Limits require the full namespace name of the custom block material
+customblocklimits:
+ # "iafestivities:christmas/christmas_tree/green_orb": 5
+
# This section is for world-specific limits and overrides the general limit
# Specify each world you want to limit individually (including nether and end worlds)
# If these worlds are not covered by the game modes above, nothing will happen
diff --git a/src/test/java/bentobox/addon/limits/listeners/JoinListenerTest.java b/src/test/java/bentobox/addon/limits/listeners/JoinListenerTest.java
index c0ca01a..109d1b1 100644
--- a/src/test/java/bentobox/addon/limits/listeners/JoinListenerTest.java
+++ b/src/test/java/bentobox/addon/limits/listeners/JoinListenerTest.java
@@ -400,7 +400,7 @@ public class JoinListenerTest {
@Test
public void testOnPlayerJoinWithPermLimitsMultiPermsSameMaterial() {
// IBC - set the block limit for STONE to be 25 already
- when(ibc.getBlockLimit(any())).thenReturn(25);
+ when(ibc.getBlockLimit(any(Material.class))).thenReturn(25);
Set perms = new HashSet<>();
PermissionAttachmentInfo permAtt = mock(PermissionAttachmentInfo.class);
when(permAtt.getPermission()).thenReturn("bskyblock.island.limit.STONE.24");