diff --git a/pom.xml b/pom.xml
index e3dbcef..1f8e911 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,334 +1,363 @@
-
- 4.0.0
- world.bentobox
- limits
- Limits
- ${revision}
-
- An add-on for BentoBox that limits blocks and entities on islands.
- https://github.com/BentoBoxWorld/Limits
- 2018
-
-
-
- tastybento
- tastybento@bentobox.world
- -8
-
- Lead Developer
-
-
-
-
-
- scm:git:https://github.com/BentoBoxWorld/Limits.git
- scm:git:git@github.com:BentoBoxWorld/Limits.git
- https://github.com/BentoBoxWorld/Limits
-
-
-
- jenkins
- http://ci.codemc.org/job/BentoBoxWorld/job/Limits
-
-
-
- GitHub
- https://github.com/BentoBoxWorld/Limits/issues
-
-
-
-
- bentoboxworld
- https://repo.codemc.org/repository/bentoboxworld/
-
-
-
-
-
- UTF-8
- UTF-8
- 21
-
- 5.17.0
- 5.12.1
- 1.17.5
-
- 1.21.11-R0.1-SNAPSHOT
- 2.7.1-SNAPSHOT
-
- ${build.version}-SNAPSHOT
-
- -LOCAL
-
- 1.28.4
- BentoBoxWorld_Limits
- bentobox-world
- https://sonarcloud.io
-
-
-
-
-
-
-
- ci
-
-
- env.BUILD_NUMBER
-
-
-
-
- -b${env.BUILD_NUMBER}
-
-
-
-
-
-
-
- master
-
-
- env.GIT_BRANCH
- origin/master
-
-
-
-
- ${build.version}
-
-
-
-
-
-
-
-
-
- jitpack.io
- https://jitpack.io
-
- true
-
-
-
- bentoboxworld
- https://repo.codemc.org/repository/bentoboxworld/
-
-
- codemc
- https://repo.codemc.org/repository/maven-snapshots/
-
-
- papermc
- https://repo.papermc.io/repository/maven-public/
-
-
-
-
-
- org.mockbukkit.mockbukkit
- mockbukkit-v1.21
- 4.110.0
- test
-
-
- org.junit.jupiter
- junit-jupiter-api
-
-
-
-
-
- io.papermc.paper
- paper-api
- ${paper.version}
- provided
-
-
-
- org.junit.jupiter
- junit-jupiter
- ${junit.version}
- test
-
-
-
- org.mockito
- mockito-core
- ${mockito.version}
- test
-
-
- org.mockito
- mockito-junit-jupiter
- ${mockito.version}
- test
-
-
-
- net.bytebuddy
- byte-buddy
- ${byte-buddy.version}
- test
-
-
- net.bytebuddy
- byte-buddy-agent
- ${byte-buddy.version}
- test
-
-
- world.bentobox
- bentobox
- ${bentobox.version}
- provided
-
-
-
-
-
-
-
-
-
-
- ${project.name}-${revision}${build.number}
-
- clean package
-
-
- src/main/resources
- true
-
-
- src/main/resources/locales
- ./locales
- false
-
-
-
-
- org.apache.maven.plugins
- maven-clean-plugin
- 3.1.0
-
-
- org.apache.maven.plugins
- maven-resources-plugin
- 3.1.0
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 3.5.2
-
-
- ${argLine}
- -XX:+EnableDynamicAgentLoading
- --add-opens java.base/java.lang=ALL-UNNAMED
- --add-opens java.base/java.math=ALL-UNNAMED
- --add-opens java.base/java.io=ALL-UNNAMED
- --add-opens java.base/java.util=ALL-UNNAMED
- --add-opens
- java.base/java.util.stream=ALL-UNNAMED
- --add-opens java.base/java.text=ALL-UNNAMED
- --add-opens
- java.base/java.util.regex=ALL-UNNAMED
- --add-opens
- java.base/java.nio.channels.spi=ALL-UNNAMED
- --add-opens java.base/sun.nio.ch=ALL-UNNAMED
- --add-opens java.base/java.net=ALL-UNNAMED
- --add-opens
- java.base/java.util.concurrent=ALL-UNNAMED
- --add-opens java.base/sun.nio.fs=ALL-UNNAMED
- --add-opens java.base/sun.nio.cs=ALL-UNNAMED
- --add-opens java.base/java.nio.file=ALL-UNNAMED
- --add-opens
- java.base/java.nio.charset=ALL-UNNAMED
- --add-opens
- java.base/java.lang.reflect=ALL-UNNAMED
- --add-opens
- java.logging/java.util.logging=ALL-UNNAMED
- --add-opens java.base/java.lang.ref=ALL-UNNAMED
- --add-opens java.base/java.util.jar=ALL-UNNAMED
- --add-opens java.base/java.util.zip=ALL-UNNAMED
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.1.0
-
-
- org.apache.maven.plugins
- maven-install-plugin
- 2.5.2
-
-
- org.apache.maven.plugins
- maven-deploy-plugin
- 2.8.2
-
-
- org.jacoco
- jacoco-maven-plugin
- 0.8.13
-
- true
-
-
- **/*Names*
-
- org/bukkit/Material*
-
-
-
-
- prepare-agent
-
- prepare-agent
-
-
-
- report
-
- report
-
-
-
- XML
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.14.1
-
- ${java.version}
- true
-
- -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
- -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
-
-
-
-
-
-
-
+
+ 4.0.0
+ world.bentobox
+ limits
+ Limits
+ ${revision}
+
+ An add-on for BentoBox that limits blocks and entities on islands.
+ https://github.com/BentoBoxWorld/Limits
+ 2018
+
+
+
+ tastybento
+ tastybento@bentobox.world
+ -8
+
+ Lead Developer
+
+
+
+
+
+ scm:git:https://github.com/BentoBoxWorld/Limits.git
+ scm:git:git@github.com:BentoBoxWorld/Limits.git
+ https://github.com/BentoBoxWorld/Limits
+
+
+
+ jenkins
+ http://ci.codemc.org/job/BentoBoxWorld/job/Limits
+
+
+
+ GitHub
+ https://github.com/BentoBoxWorld/Limits/issues
+
+
+
+
+ bentoboxworld
+ https://repo.codemc.org/repository/bentoboxworld/
+
+
+
+
+
+ UTF-8
+ UTF-8
+ 21
+
+ 5.17.0
+ 5.12.1
+ 1.17.5
+
+ 1.21.11-R0.1-SNAPSHOT
+ 2.7.1-SNAPSHOT
+
+ ${build.version}-SNAPSHOT
+
+ -LOCAL
+
+ 1.28.4
+ BentoBoxWorld_Limits
+ bentobox-world
+ https://sonarcloud.io
+
+
+
+
+
+
+
+ ci
+
+
+ env.BUILD_NUMBER
+
+
+
+
+ -b${env.BUILD_NUMBER}
+
+
+
+
+
+
+
+ master
+
+
+ env.GIT_BRANCH
+ origin/master
+
+
+
+
+ ${build.version}
+
+
+
+
+
+
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+ true
+
+
+
+ bentoboxworld
+ https://repo.codemc.org/repository/bentoboxworld/
+
+
+ codemc
+ https://repo.codemc.org/repository/maven-snapshots/
+
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
+
+
+
+ matteodev
+ https://maven.devs.beer/
+
+
+
+ oraxen
+ https://repo.oraxen.com/releases
+
+
+
+
+
+ org.mockbukkit.mockbukkit
+ mockbukkit-v1.21
+ 4.110.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
+
+
+
+ io.papermc.paper
+ paper-api
+ ${paper.version}
+ provided
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.version}
+ test
+
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito.version}
+ test
+
+
+
+ net.bytebuddy
+ byte-buddy
+ ${byte-buddy.version}
+ test
+
+
+ net.bytebuddy
+ byte-buddy-agent
+ ${byte-buddy.version}
+ test
+
+
+ world.bentobox
+ bentobox
+ ${bentobox.version}
+ provided
+
+
+
+ dev.lone
+ api-itemsadder
+ 4.0.2-beta-release-11
+ provided
+
+
+ io.th0rgal
+ oraxen
+ 1.193.1
+ provided
+
+
+ *
+ *
+
+
+
+
+
+
+
+
+
+
+
+
+ ${project.name}-${revision}${build.number}
+
+ clean package
+
+
+ src/main/resources
+ true
+
+
+ src/main/resources/locales
+ ./locales
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ 3.1.0
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.1.0
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.2
+
+
+ ${argLine}
+ -XX:+EnableDynamicAgentLoading
+ --add-opens java.base/java.lang=ALL-UNNAMED
+ --add-opens java.base/java.math=ALL-UNNAMED
+ --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED
+ --add-opens
+ java.base/java.util.stream=ALL-UNNAMED
+ --add-opens java.base/java.text=ALL-UNNAMED
+ --add-opens
+ java.base/java.util.regex=ALL-UNNAMED
+ --add-opens
+ java.base/java.nio.channels.spi=ALL-UNNAMED
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED
+ --add-opens java.base/java.net=ALL-UNNAMED
+ --add-opens
+ java.base/java.util.concurrent=ALL-UNNAMED
+ --add-opens java.base/sun.nio.fs=ALL-UNNAMED
+ --add-opens java.base/sun.nio.cs=ALL-UNNAMED
+ --add-opens java.base/java.nio.file=ALL-UNNAMED
+ --add-opens
+ java.base/java.nio.charset=ALL-UNNAMED
+ --add-opens
+ java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens
+ java.logging/java.util.logging=ALL-UNNAMED
+ --add-opens java.base/java.lang.ref=ALL-UNNAMED
+ --add-opens java.base/java.util.jar=ALL-UNNAMED
+ --add-opens java.base/java.util.zip=ALL-UNNAMED
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.1.0
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 2.5.2
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 2.8.2
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.13
+
+ true
+
+
+ **/*Names*
+
+ org/bukkit/Material*
+
+
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+
+ report
+
+
+
+ XML
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.14.1
+
+ ${java.version}
+ true
+
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+ -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+
+
+
+
+
+
+
diff --git a/src/main/java/world/bentobox/limits/Limits.java b/src/main/java/world/bentobox/limits/Limits.java
index b8e7b40..e8015c3 100644
--- a/src/main/java/world/bentobox/limits/Limits.java
+++ b/src/main/java/world/bentobox/limits/Limits.java
@@ -69,6 +69,14 @@ public void onEnable() {
registerListener(joinListener);
EntityLimitListener entityLimitListener = new EntityLimitListener(this);
registerListener(entityLimitListener);
+ if (org.bukkit.Bukkit.getPluginManager().getPlugin("ItemsAdder") != null) {
+ registerListener(new world.bentobox.limits.listeners.ItemsAdderListener(this));
+ log("ItemsAdder detected: custom block limits active. Use ItemsAdder ids as blocklimits keys.");
+ }
+ if (org.bukkit.Bukkit.getPluginManager().getPlugin("Oraxen") != null) {
+ registerListener(new world.bentobox.limits.listeners.OraxenListener(this));
+ log("Oraxen detected: custom block limits active. Use \"oraxen:\" blocklimits keys.");
+ }
try {
Class.forName("io.papermc.paper.event.entity.ShulkerDuplicateEvent");
registerListener(new PaperShulkerLimitListener(this, entityLimitListener));
diff --git a/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java b/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java
index ebd8049..9d14f94 100644
--- a/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java
+++ b/src/main/java/world/bentobox/limits/calculators/RecountCalculator.java
@@ -233,9 +233,24 @@ private void scanEntities() {
public void tidyUp() {
ibc = bll.getIsland(island);
+ // Custom-namespace counts (ItemsAdder/Oraxen blocks) are event-tracked and
+ // invisible to the chunk scan, so carry them across the reset untouched.
+ Map> customCounts = new EnumMap<>(Environment.class);
+ for (Environment env : List.of(Environment.NORMAL, Environment.NETHER, Environment.THE_END)) {
+ ibc.getBlockCounts(env).forEach((key, count) -> {
+ if (!NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
+ customCounts.computeIfAbsent(env, e -> new java.util.HashMap<>()).put(key, count);
+ }
+ });
+ }
// Reset and write per-env block counts
ibc.clearAllBlockCounts();
results.getEnvBlockCount().forEach((env, multiset) -> multiset.forEach(key -> ibc.add(env, key)));
+ customCounts.forEach((env, m) -> m.forEach((key, count) -> {
+ for (int i = 0; i < count; i++) {
+ ibc.add(env, key);
+ }
+ }));
// Recount entities (loaded only) and write per-env
scanEntities();
diff --git a/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java b/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java
index 558b94e..9635288 100644
--- a/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java
+++ b/src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java
@@ -9,6 +9,7 @@
import java.util.Objects;
import org.bukkit.Bukkit;
+import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
@@ -209,6 +210,13 @@ private NamespacedKey parseConfigKey(String key) {
/** Resolve a key to a material or block tag and store its limit, warning on any mismatch. */
private void registerLimit(Map limits, NamespacedKey nsKey, String key, int limit) {
+ if (!NamespacedKey.MINECRAFT.equals(nsKey.getNamespace())) {
+ // Custom-namespace key, e.g. an ItemsAdder block id ("iafestivities:christmas/tree")
+ // or an Oraxen id written as "oraxen:". Enforced by the custom block
+ // listeners when the owning plugin is installed.
+ limits.put(nsKey, limit);
+ return;
+ }
Material mat = Registry.MATERIAL.get(nsKey);
if (mat != null) {
if (!mat.isBlock()) {
@@ -445,7 +453,7 @@ private int process(Block b, boolean add) {
* @return limit amount if over limit, or -1 if no limitation
*/
private int process(Block b, BlockData blockData, boolean add) {
- if (DO_NOT_COUNT.contains(fixMaterial(blockData)) || !addon.inGameModeWorld(b.getWorld())) {
+ if (DO_NOT_COUNT.contains(fixMaterial(blockData))) {
return -1;
}
// Stacked-plants-as-one: a segment sitting on the same plant is not counted,
@@ -454,21 +462,34 @@ private int process(Block b, BlockData blockData, boolean add) {
&& isSamePlant(b.getRelative(BlockFace.DOWN).getType(), fixMaterial(blockData))) {
return -1;
}
- Environment env = envOf(b.getWorld());
- return addon.getIslands().getIslandAt(b.getLocation()).map(i -> {
+ return processKey(b.getWorld(), b.getLocation(), fixMaterial(blockData), add);
+ }
+
+ /**
+ * Count and limit-check a namespaced key at a location. Public so custom-block
+ * listeners (ItemsAdder, Oraxen) can run their block ids through the same
+ * counting and limit machinery as vanilla blocks.
+ *
+ * @return limit amount if at/over the limit (nothing was counted), or -1 on success
+ */
+ public int processKey(World world, Location location, NamespacedKey key, boolean add) {
+ if (!addon.inGameModeWorld(world)) {
+ return -1;
+ }
+ Environment env = envOf(world);
+ return addon.getIslands().getIslandAt(location).map(i -> {
String id = i.getUniqueId();
- String gameMode = addon.getGameModeName(b.getWorld());
+ String gameMode = addon.getGameModeName(world);
if (gameMode.isEmpty()) {
return -1;
}
if (addon.getConfig().getBoolean("ignore-center-block", true)
- && i.getCenter().equals(b.getLocation())) {
+ && i.getCenter().equals(location)) {
return -1;
}
islandCountMap.putIfAbsent(id, new IslandBlockCount(id, gameMode));
- NamespacedKey key = fixMaterial(blockData);
if (add) {
- int limit = checkLimit(b.getWorld(), env, key, id);
+ int limit = checkLimit(world, env, key, id);
if (limit > -1) {
return limit;
}
diff --git a/src/main/java/world/bentobox/limits/listeners/CustomBlockHandler.java b/src/main/java/world/bentobox/limits/listeners/CustomBlockHandler.java
new file mode 100644
index 0000000..0d84015
--- /dev/null
+++ b/src/main/java/world/bentobox/limits/listeners/CustomBlockHandler.java
@@ -0,0 +1,64 @@
+package world.bentobox.limits.listeners;
+
+import java.util.Locale;
+
+import org.bukkit.NamespacedKey;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+
+import world.bentobox.bentobox.api.localization.TextVariables;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.util.Util;
+import world.bentobox.limits.Limits;
+
+/**
+ * Shared plumbing for custom-block plugin listeners (ItemsAdder, Oraxen). Runs custom
+ * block ids through the same counting and limit machinery as vanilla blocks via
+ * {@link BlockLimitsListener#processKey}.
+ */
+final class CustomBlockHandler {
+
+ private CustomBlockHandler() {
+ }
+
+ /**
+ * Limit-check and count a custom block placement; cancels the event and notifies
+ * the player when the limit is hit.
+ */
+ static void place(Limits addon, Cancellable event, Player player, Block block, NamespacedKey key) {
+ if (key == null) {
+ return;
+ }
+ int limit = addon.getBlockLimitListener().processKey(block.getWorld(), block.getLocation(), key, true);
+ if (limit > -1) {
+ event.setCancelled(true);
+ if (player != null) {
+ User.getInstance(player).notify("block-limits.hit-limit",
+ "[material]", Util.prettifyText(key.getKey()),
+ TextVariables.NUMBER, String.valueOf(limit));
+ }
+ }
+ }
+
+ /**
+ * Decrement the count for a removed custom block.
+ */
+ static void remove(Limits addon, Block block, NamespacedKey key) {
+ if (key != null) {
+ addon.getBlockLimitListener().processKey(block.getWorld(), block.getLocation(), key, false);
+ }
+ }
+
+ /**
+ * Parse a custom block id into a namespaced key. ItemsAdder ids already carry a
+ * namespace; plain ids get the supplied default namespace (e.g. "oraxen").
+ */
+ static NamespacedKey toKey(String id, String defaultNamespace) {
+ if (id == null) {
+ return null;
+ }
+ String full = id.contains(":") ? id : defaultNamespace + ":" + id;
+ return NamespacedKey.fromString(full.toLowerCase(Locale.ROOT));
+ }
+}
diff --git a/src/main/java/world/bentobox/limits/listeners/ItemsAdderListener.java b/src/main/java/world/bentobox/limits/listeners/ItemsAdderListener.java
new file mode 100644
index 0000000..a5ad038
--- /dev/null
+++ b/src/main/java/world/bentobox/limits/listeners/ItemsAdderListener.java
@@ -0,0 +1,35 @@
+package world.bentobox.limits.listeners;
+
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+
+import dev.lone.itemsadder.api.Events.CustomBlockBreakEvent;
+import dev.lone.itemsadder.api.Events.CustomBlockPlaceEvent;
+import world.bentobox.limits.Limits;
+
+/**
+ * Applies block limits to ItemsAdder custom blocks. Registered only when the
+ * ItemsAdder plugin is present. Config keys use the ItemsAdder namespaced id, e.g.
+ * {@code "iafestivities:christmas/christmas_tree/green_orb": 5} under blocklimits.
+ */
+public class ItemsAdderListener implements Listener {
+
+ private final Limits addon;
+
+ public ItemsAdderListener(Limits addon) {
+ this.addon = addon;
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onPlace(CustomBlockPlaceEvent e) {
+ CustomBlockHandler.place(addon, e, e.getPlayer(), e.getBlock(),
+ CustomBlockHandler.toKey(e.getNamespacedID(), "itemsadder"));
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBreak(CustomBlockBreakEvent e) {
+ CustomBlockHandler.remove(addon, e.getBlock(),
+ CustomBlockHandler.toKey(e.getNamespacedID(), "itemsadder"));
+ }
+}
diff --git a/src/main/java/world/bentobox/limits/listeners/OraxenListener.java b/src/main/java/world/bentobox/limits/listeners/OraxenListener.java
new file mode 100644
index 0000000..75e2ce5
--- /dev/null
+++ b/src/main/java/world/bentobox/limits/listeners/OraxenListener.java
@@ -0,0 +1,51 @@
+package world.bentobox.limits.listeners;
+
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+
+import io.th0rgal.oraxen.api.events.noteblock.OraxenNoteBlockBreakEvent;
+import io.th0rgal.oraxen.api.events.noteblock.OraxenNoteBlockPlaceEvent;
+import io.th0rgal.oraxen.api.events.stringblock.OraxenStringBlockBreakEvent;
+import io.th0rgal.oraxen.api.events.stringblock.OraxenStringBlockPlaceEvent;
+import world.bentobox.limits.Limits;
+
+/**
+ * Applies block limits to Oraxen custom blocks (note-block and string-block
+ * mechanics). Registered only when the Oraxen plugin is present. Config keys use the
+ * {@code oraxen:} namespace, e.g. {@code "oraxen:caveblock": 10} under blocklimits.
+ */
+public class OraxenListener implements Listener {
+
+ private static final String NAMESPACE = "oraxen";
+
+ private final Limits addon;
+
+ public OraxenListener(Limits addon) {
+ this.addon = addon;
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onNoteBlockPlace(OraxenNoteBlockPlaceEvent e) {
+ CustomBlockHandler.place(addon, e, e.getPlayer(), e.getBlock(),
+ CustomBlockHandler.toKey(e.getMechanic().getItemID(), NAMESPACE));
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onNoteBlockBreak(OraxenNoteBlockBreakEvent e) {
+ CustomBlockHandler.remove(addon, e.getBlock(),
+ CustomBlockHandler.toKey(e.getMechanic().getItemID(), NAMESPACE));
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onStringBlockPlace(OraxenStringBlockPlaceEvent e) {
+ CustomBlockHandler.place(addon, e, e.getPlayer(), e.getBlock(),
+ CustomBlockHandler.toKey(e.getMechanic().getItemID(), NAMESPACE));
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onStringBlockBreak(OraxenStringBlockBreakEvent e) {
+ CustomBlockHandler.remove(addon, e.getBlock(),
+ CustomBlockHandler.toKey(e.getMechanic().getItemID(), NAMESPACE));
+ }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index bafb55d..642feaa 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -53,6 +53,12 @@ apply-member-limit-perms: false
# Use blocklimits-nether / blocklimits-end below to override per environment.
# The following general terms can be used: STAIRS, DOORS, SIGNS, LOGS, SNOW, LEAVES,
# SHULKER_BOXES, BANNERS, RAILS, ICE, BUTTONS, CANDLE_CAKES, CAMPFIRES, SLABS, STONE_BRICKS
+# ItemsAdder / Oraxen custom blocks can be limited too (when the plugin is installed):
+# use the ItemsAdder namespaced id, or an Oraxen id prefixed with "oraxen:", as the key.
+# Quote keys containing a colon. Custom blocks are tracked from their place/break
+# events; a recount preserves their stored counts rather than rescanning them.
+# "iafestivities:christmas/christmas_tree/green_orb": 5
+# "oraxen:caveblock": 10
blocklimits:
HOPPER: 10
diff --git a/src/test/java/world/bentobox/limits/listeners/BlockLimitsListenerTest.java b/src/test/java/world/bentobox/limits/listeners/BlockLimitsListenerTest.java
index c03eeee..f6c1941 100644
--- a/src/test/java/world/bentobox/limits/listeners/BlockLimitsListenerTest.java
+++ b/src/test/java/world/bentobox/limits/listeners/BlockLimitsListenerTest.java
@@ -1097,6 +1097,34 @@ void testIndividualLimitStillAppliesInsideGroup() {
assertTrue(event.isCancelled());
}
+ // --- Custom block keys (#176) ---
+
+ @Test
+ void testProcessKeyCustomNamespaceLimitAndCount() {
+ NamespacedKey custom = NamespacedKey.fromString("itemsadder:some_pack/tree");
+ IslandBlockCount ibc = new IslandBlockCount("test-island-id", "BSkyBlock");
+ ibc.setBlockLimit(Environment.NORMAL, custom, 1);
+ listener.setIsland("test-island-id", ibc);
+
+ // First add succeeds and counts
+ assertEquals(-1, listener.processKey(world, blockLocation, custom, true));
+ assertEquals(1, listener.getIsland("test-island-id").getBlockCount(Environment.NORMAL, custom));
+ // Second add hits the limit and must not count
+ assertEquals(1, listener.processKey(world, blockLocation, custom, true));
+ assertEquals(1, listener.getIsland("test-island-id").getBlockCount(Environment.NORMAL, custom));
+ // Removal decrements
+ assertEquals(-1, listener.processKey(world, blockLocation, custom, false));
+ assertEquals(0, listener.getIsland("test-island-id").getBlockCount(Environment.NORMAL, custom));
+ }
+
+ @Test
+ void testCustomNamespaceKeyAcceptedInBlockLimitsConfig() {
+ config.set("blocklimits.oraxen:caveblock", 10);
+ BlockLimitsListener custom = new BlockLimitsListener(addon);
+ assertEquals(10,
+ custom.getMaterialLimits(world, "no-island").get(NamespacedKey.fromString("oraxen:caveblock")));
+ }
+
// --- Block cascade tests ---
@Test
diff --git a/src/test/java/world/bentobox/limits/listeners/CustomBlockHandlerTest.java b/src/test/java/world/bentobox/limits/listeners/CustomBlockHandlerTest.java
new file mode 100644
index 0000000..ced9007
--- /dev/null
+++ b/src/test/java/world/bentobox/limits/listeners/CustomBlockHandlerTest.java
@@ -0,0 +1,37 @@
+package world.bentobox.limits.listeners;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.bukkit.NamespacedKey;
+import org.junit.jupiter.api.Test;
+
+class CustomBlockHandlerTest {
+
+ @Test
+ void testToKeyNamespacedIdPassesThrough() {
+ NamespacedKey key = CustomBlockHandler.toKey("iafestivities:christmas/christmas_tree/green_orb",
+ "itemsadder");
+ assertEquals("iafestivities", key.getNamespace());
+ assertEquals("christmas/christmas_tree/green_orb", key.getKey());
+ }
+
+ @Test
+ void testToKeyPlainIdGetsDefaultNamespace() {
+ NamespacedKey key = CustomBlockHandler.toKey("caveblock", "oraxen");
+ assertEquals("oraxen", key.getNamespace());
+ assertEquals("caveblock", key.getKey());
+ }
+
+ @Test
+ void testToKeyUppercaseIsLowercased() {
+ NamespacedKey key = CustomBlockHandler.toKey("CaveBlock", "oraxen");
+ assertEquals("caveblock", key.getKey());
+ }
+
+ @Test
+ void testToKeyInvalidReturnsNull() {
+ assertNull(CustomBlockHandler.toKey("bad key with spaces", "oraxen"));
+ assertNull(CustomBlockHandler.toKey(null, "oraxen"));
+ }
+}