From 114655305c056de663cb5985d3f582585b778aa7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 10 Jul 2026 09:07:08 -0700 Subject: [PATCH] Add ItemsAdder and Oraxen custom block limits via BentoBox hooks Custom block ids can now be used directly as blocklimits keys (all sections: blocklimits, -nether, -end, worlds): ItemsAdder ids as-is (namespace:id), Oraxen ids prefixed "oraxen:". The config parser already produced NamespacedKeys; registerLimit now accepts non-minecraft namespaces instead of rejecting them. Enforcement listens to the plugins' own place/break events (fixing the 2023 event-ordering problem where the alert fired but the block still placed) through new soft-registered listeners that only load when the respective plugin is present. Both feed processKey, a public extraction of the existing count/limit core, so island lookup, per-env limits, offsets, batching, and the GUI all work unchanged - custom keys render with a paper icon and prettified name. Custom counts are event-tracked and invisible to the recount chunk scan, so the recount now carries non-minecraft counts across the reset instead of zeroing them. Fixes #176 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_015dbJyrv2uxHfTmiWkqGUJB --- pom.xml | 697 +++++++++--------- .../java/world/bentobox/limits/Limits.java | 8 + .../limits/calculators/RecountCalculator.java | 15 + .../limits/listeners/BlockLimitsListener.java | 35 +- .../limits/listeners/CustomBlockHandler.java | 64 ++ .../limits/listeners/ItemsAdderListener.java | 35 + .../limits/listeners/OraxenListener.java | 51 ++ src/main/resources/config.yml | 6 + .../listeners/BlockLimitsListenerTest.java | 28 + .../listeners/CustomBlockHandlerTest.java | 37 + 10 files changed, 635 insertions(+), 341 deletions(-) create mode 100644 src/main/java/world/bentobox/limits/listeners/CustomBlockHandler.java create mode 100644 src/main/java/world/bentobox/limits/listeners/ItemsAdderListener.java create mode 100644 src/main/java/world/bentobox/limits/listeners/OraxenListener.java create mode 100644 src/test/java/world/bentobox/limits/listeners/CustomBlockHandlerTest.java 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")); + } +}