Skip to content

Commit f428441

Browse files
authored
Merge pull request #288 from BentoBoxWorld/feat/176-itemsadder-oraxen
Add ItemsAdder and Oraxen custom block limits
2 parents 6cbca12 + 1146553 commit f428441

10 files changed

Lines changed: 635 additions & 341 deletions

File tree

pom.xml

Lines changed: 363 additions & 334 deletions
Large diffs are not rendered by default.

src/main/java/world/bentobox/limits/Limits.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ public void onEnable() {
6969
registerListener(joinListener);
7070
EntityLimitListener entityLimitListener = new EntityLimitListener(this);
7171
registerListener(entityLimitListener);
72+
if (org.bukkit.Bukkit.getPluginManager().getPlugin("ItemsAdder") != null) {
73+
registerListener(new world.bentobox.limits.listeners.ItemsAdderListener(this));
74+
log("ItemsAdder detected: custom block limits active. Use ItemsAdder ids as blocklimits keys.");
75+
}
76+
if (org.bukkit.Bukkit.getPluginManager().getPlugin("Oraxen") != null) {
77+
registerListener(new world.bentobox.limits.listeners.OraxenListener(this));
78+
log("Oraxen detected: custom block limits active. Use \"oraxen:<itemid>\" blocklimits keys.");
79+
}
7280
try {
7381
Class.forName("io.papermc.paper.event.entity.ShulkerDuplicateEvent");
7482
registerListener(new PaperShulkerLimitListener(this, entityLimitListener));

src/main/java/world/bentobox/limits/calculators/RecountCalculator.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,24 @@ private void scanEntities() {
233233

234234
public void tidyUp() {
235235
ibc = bll.getIsland(island);
236+
// Custom-namespace counts (ItemsAdder/Oraxen blocks) are event-tracked and
237+
// invisible to the chunk scan, so carry them across the reset untouched.
238+
Map<Environment, Map<NamespacedKey, Integer>> customCounts = new EnumMap<>(Environment.class);
239+
for (Environment env : List.of(Environment.NORMAL, Environment.NETHER, Environment.THE_END)) {
240+
ibc.getBlockCounts(env).forEach((key, count) -> {
241+
if (!NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
242+
customCounts.computeIfAbsent(env, e -> new java.util.HashMap<>()).put(key, count);
243+
}
244+
});
245+
}
236246
// Reset and write per-env block counts
237247
ibc.clearAllBlockCounts();
238248
results.getEnvBlockCount().forEach((env, multiset) -> multiset.forEach(key -> ibc.add(env, key)));
249+
customCounts.forEach((env, m) -> m.forEach((key, count) -> {
250+
for (int i = 0; i < count; i++) {
251+
ibc.add(env, key);
252+
}
253+
}));
239254

240255
// Recount entities (loaded only) and write per-env
241256
scanEntities();

src/main/java/world/bentobox/limits/listeners/BlockLimitsListener.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.Objects;
1010

1111
import org.bukkit.Bukkit;
12+
import org.bukkit.Location;
1213
import org.bukkit.Material;
1314
import org.bukkit.NamespacedKey;
1415
import org.bukkit.Registry;
@@ -209,6 +210,13 @@ private NamespacedKey parseConfigKey(String key) {
209210

210211
/** Resolve a key to a material or block tag and store its limit, warning on any mismatch. */
211212
private void registerLimit(Map<NamespacedKey, Integer> limits, NamespacedKey nsKey, String key, int limit) {
213+
if (!NamespacedKey.MINECRAFT.equals(nsKey.getNamespace())) {
214+
// Custom-namespace key, e.g. an ItemsAdder block id ("iafestivities:christmas/tree")
215+
// or an Oraxen id written as "oraxen:<itemid>". Enforced by the custom block
216+
// listeners when the owning plugin is installed.
217+
limits.put(nsKey, limit);
218+
return;
219+
}
212220
Material mat = Registry.MATERIAL.get(nsKey);
213221
if (mat != null) {
214222
if (!mat.isBlock()) {
@@ -445,7 +453,7 @@ private int process(Block b, boolean add) {
445453
* @return limit amount if over limit, or -1 if no limitation
446454
*/
447455
private int process(Block b, BlockData blockData, boolean add) {
448-
if (DO_NOT_COUNT.contains(fixMaterial(blockData)) || !addon.inGameModeWorld(b.getWorld())) {
456+
if (DO_NOT_COUNT.contains(fixMaterial(blockData))) {
449457
return -1;
450458
}
451459
// 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) {
454462
&& isSamePlant(b.getRelative(BlockFace.DOWN).getType(), fixMaterial(blockData))) {
455463
return -1;
456464
}
457-
Environment env = envOf(b.getWorld());
458-
return addon.getIslands().getIslandAt(b.getLocation()).map(i -> {
465+
return processKey(b.getWorld(), b.getLocation(), fixMaterial(blockData), add);
466+
}
467+
468+
/**
469+
* Count and limit-check a namespaced key at a location. Public so custom-block
470+
* listeners (ItemsAdder, Oraxen) can run their block ids through the same
471+
* counting and limit machinery as vanilla blocks.
472+
*
473+
* @return limit amount if at/over the limit (nothing was counted), or -1 on success
474+
*/
475+
public int processKey(World world, Location location, NamespacedKey key, boolean add) {
476+
if (!addon.inGameModeWorld(world)) {
477+
return -1;
478+
}
479+
Environment env = envOf(world);
480+
return addon.getIslands().getIslandAt(location).map(i -> {
459481
String id = i.getUniqueId();
460-
String gameMode = addon.getGameModeName(b.getWorld());
482+
String gameMode = addon.getGameModeName(world);
461483
if (gameMode.isEmpty()) {
462484
return -1;
463485
}
464486
if (addon.getConfig().getBoolean("ignore-center-block", true)
465-
&& i.getCenter().equals(b.getLocation())) {
487+
&& i.getCenter().equals(location)) {
466488
return -1;
467489
}
468490
islandCountMap.putIfAbsent(id, new IslandBlockCount(id, gameMode));
469-
NamespacedKey key = fixMaterial(blockData);
470491
if (add) {
471-
int limit = checkLimit(b.getWorld(), env, key, id);
492+
int limit = checkLimit(world, env, key, id);
472493
if (limit > -1) {
473494
return limit;
474495
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package world.bentobox.limits.listeners;
2+
3+
import java.util.Locale;
4+
5+
import org.bukkit.NamespacedKey;
6+
import org.bukkit.block.Block;
7+
import org.bukkit.entity.Player;
8+
import org.bukkit.event.Cancellable;
9+
10+
import world.bentobox.bentobox.api.localization.TextVariables;
11+
import world.bentobox.bentobox.api.user.User;
12+
import world.bentobox.bentobox.util.Util;
13+
import world.bentobox.limits.Limits;
14+
15+
/**
16+
* Shared plumbing for custom-block plugin listeners (ItemsAdder, Oraxen). Runs custom
17+
* block ids through the same counting and limit machinery as vanilla blocks via
18+
* {@link BlockLimitsListener#processKey}.
19+
*/
20+
final class CustomBlockHandler {
21+
22+
private CustomBlockHandler() {
23+
}
24+
25+
/**
26+
* Limit-check and count a custom block placement; cancels the event and notifies
27+
* the player when the limit is hit.
28+
*/
29+
static void place(Limits addon, Cancellable event, Player player, Block block, NamespacedKey key) {
30+
if (key == null) {
31+
return;
32+
}
33+
int limit = addon.getBlockLimitListener().processKey(block.getWorld(), block.getLocation(), key, true);
34+
if (limit > -1) {
35+
event.setCancelled(true);
36+
if (player != null) {
37+
User.getInstance(player).notify("block-limits.hit-limit",
38+
"[material]", Util.prettifyText(key.getKey()),
39+
TextVariables.NUMBER, String.valueOf(limit));
40+
}
41+
}
42+
}
43+
44+
/**
45+
* Decrement the count for a removed custom block.
46+
*/
47+
static void remove(Limits addon, Block block, NamespacedKey key) {
48+
if (key != null) {
49+
addon.getBlockLimitListener().processKey(block.getWorld(), block.getLocation(), key, false);
50+
}
51+
}
52+
53+
/**
54+
* Parse a custom block id into a namespaced key. ItemsAdder ids already carry a
55+
* namespace; plain ids get the supplied default namespace (e.g. "oraxen").
56+
*/
57+
static NamespacedKey toKey(String id, String defaultNamespace) {
58+
if (id == null) {
59+
return null;
60+
}
61+
String full = id.contains(":") ? id : defaultNamespace + ":" + id;
62+
return NamespacedKey.fromString(full.toLowerCase(Locale.ROOT));
63+
}
64+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package world.bentobox.limits.listeners;
2+
3+
import org.bukkit.event.EventHandler;
4+
import org.bukkit.event.EventPriority;
5+
import org.bukkit.event.Listener;
6+
7+
import dev.lone.itemsadder.api.Events.CustomBlockBreakEvent;
8+
import dev.lone.itemsadder.api.Events.CustomBlockPlaceEvent;
9+
import world.bentobox.limits.Limits;
10+
11+
/**
12+
* Applies block limits to ItemsAdder custom blocks. Registered only when the
13+
* ItemsAdder plugin is present. Config keys use the ItemsAdder namespaced id, e.g.
14+
* {@code "iafestivities:christmas/christmas_tree/green_orb": 5} under blocklimits.
15+
*/
16+
public class ItemsAdderListener implements Listener {
17+
18+
private final Limits addon;
19+
20+
public ItemsAdderListener(Limits addon) {
21+
this.addon = addon;
22+
}
23+
24+
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
25+
public void onPlace(CustomBlockPlaceEvent e) {
26+
CustomBlockHandler.place(addon, e, e.getPlayer(), e.getBlock(),
27+
CustomBlockHandler.toKey(e.getNamespacedID(), "itemsadder"));
28+
}
29+
30+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
31+
public void onBreak(CustomBlockBreakEvent e) {
32+
CustomBlockHandler.remove(addon, e.getBlock(),
33+
CustomBlockHandler.toKey(e.getNamespacedID(), "itemsadder"));
34+
}
35+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package world.bentobox.limits.listeners;
2+
3+
import org.bukkit.event.EventHandler;
4+
import org.bukkit.event.EventPriority;
5+
import org.bukkit.event.Listener;
6+
7+
import io.th0rgal.oraxen.api.events.noteblock.OraxenNoteBlockBreakEvent;
8+
import io.th0rgal.oraxen.api.events.noteblock.OraxenNoteBlockPlaceEvent;
9+
import io.th0rgal.oraxen.api.events.stringblock.OraxenStringBlockBreakEvent;
10+
import io.th0rgal.oraxen.api.events.stringblock.OraxenStringBlockPlaceEvent;
11+
import world.bentobox.limits.Limits;
12+
13+
/**
14+
* Applies block limits to Oraxen custom blocks (note-block and string-block
15+
* mechanics). Registered only when the Oraxen plugin is present. Config keys use the
16+
* {@code oraxen:} namespace, e.g. {@code "oraxen:caveblock": 10} under blocklimits.
17+
*/
18+
public class OraxenListener implements Listener {
19+
20+
private static final String NAMESPACE = "oraxen";
21+
22+
private final Limits addon;
23+
24+
public OraxenListener(Limits addon) {
25+
this.addon = addon;
26+
}
27+
28+
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
29+
public void onNoteBlockPlace(OraxenNoteBlockPlaceEvent e) {
30+
CustomBlockHandler.place(addon, e, e.getPlayer(), e.getBlock(),
31+
CustomBlockHandler.toKey(e.getMechanic().getItemID(), NAMESPACE));
32+
}
33+
34+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
35+
public void onNoteBlockBreak(OraxenNoteBlockBreakEvent e) {
36+
CustomBlockHandler.remove(addon, e.getBlock(),
37+
CustomBlockHandler.toKey(e.getMechanic().getItemID(), NAMESPACE));
38+
}
39+
40+
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
41+
public void onStringBlockPlace(OraxenStringBlockPlaceEvent e) {
42+
CustomBlockHandler.place(addon, e, e.getPlayer(), e.getBlock(),
43+
CustomBlockHandler.toKey(e.getMechanic().getItemID(), NAMESPACE));
44+
}
45+
46+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
47+
public void onStringBlockBreak(OraxenStringBlockBreakEvent e) {
48+
CustomBlockHandler.remove(addon, e.getBlock(),
49+
CustomBlockHandler.toKey(e.getMechanic().getItemID(), NAMESPACE));
50+
}
51+
}

src/main/resources/config.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ apply-member-limit-perms: false
5353
# Use blocklimits-nether / blocklimits-end below to override per environment.
5454
# The following general terms can be used: STAIRS, DOORS, SIGNS, LOGS, SNOW, LEAVES,
5555
# SHULKER_BOXES, BANNERS, RAILS, ICE, BUTTONS, CANDLE_CAKES, CAMPFIRES, SLABS, STONE_BRICKS
56+
# ItemsAdder / Oraxen custom blocks can be limited too (when the plugin is installed):
57+
# use the ItemsAdder namespaced id, or an Oraxen id prefixed with "oraxen:", as the key.
58+
# Quote keys containing a colon. Custom blocks are tracked from their place/break
59+
# events; a recount preserves their stored counts rather than rescanning them.
60+
# "iafestivities:christmas/christmas_tree/green_orb": 5
61+
# "oraxen:caveblock": 10
5662
blocklimits:
5763
HOPPER: 10
5864

src/test/java/world/bentobox/limits/listeners/BlockLimitsListenerTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,34 @@ void testIndividualLimitStillAppliesInsideGroup() {
10971097
assertTrue(event.isCancelled());
10981098
}
10991099

1100+
// --- Custom block keys (#176) ---
1101+
1102+
@Test
1103+
void testProcessKeyCustomNamespaceLimitAndCount() {
1104+
NamespacedKey custom = NamespacedKey.fromString("itemsadder:some_pack/tree");
1105+
IslandBlockCount ibc = new IslandBlockCount("test-island-id", "BSkyBlock");
1106+
ibc.setBlockLimit(Environment.NORMAL, custom, 1);
1107+
listener.setIsland("test-island-id", ibc);
1108+
1109+
// First add succeeds and counts
1110+
assertEquals(-1, listener.processKey(world, blockLocation, custom, true));
1111+
assertEquals(1, listener.getIsland("test-island-id").getBlockCount(Environment.NORMAL, custom));
1112+
// Second add hits the limit and must not count
1113+
assertEquals(1, listener.processKey(world, blockLocation, custom, true));
1114+
assertEquals(1, listener.getIsland("test-island-id").getBlockCount(Environment.NORMAL, custom));
1115+
// Removal decrements
1116+
assertEquals(-1, listener.processKey(world, blockLocation, custom, false));
1117+
assertEquals(0, listener.getIsland("test-island-id").getBlockCount(Environment.NORMAL, custom));
1118+
}
1119+
1120+
@Test
1121+
void testCustomNamespaceKeyAcceptedInBlockLimitsConfig() {
1122+
config.set("blocklimits.oraxen:caveblock", 10);
1123+
BlockLimitsListener custom = new BlockLimitsListener(addon);
1124+
assertEquals(10,
1125+
custom.getMaterialLimits(world, "no-island").get(NamespacedKey.fromString("oraxen:caveblock")));
1126+
}
1127+
11001128
// --- Block cascade tests ---
11011129

11021130
@Test
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package world.bentobox.limits.listeners;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNull;
5+
6+
import org.bukkit.NamespacedKey;
7+
import org.junit.jupiter.api.Test;
8+
9+
class CustomBlockHandlerTest {
10+
11+
@Test
12+
void testToKeyNamespacedIdPassesThrough() {
13+
NamespacedKey key = CustomBlockHandler.toKey("iafestivities:christmas/christmas_tree/green_orb",
14+
"itemsadder");
15+
assertEquals("iafestivities", key.getNamespace());
16+
assertEquals("christmas/christmas_tree/green_orb", key.getKey());
17+
}
18+
19+
@Test
20+
void testToKeyPlainIdGetsDefaultNamespace() {
21+
NamespacedKey key = CustomBlockHandler.toKey("caveblock", "oraxen");
22+
assertEquals("oraxen", key.getNamespace());
23+
assertEquals("caveblock", key.getKey());
24+
}
25+
26+
@Test
27+
void testToKeyUppercaseIsLowercased() {
28+
NamespacedKey key = CustomBlockHandler.toKey("CaveBlock", "oraxen");
29+
assertEquals("caveblock", key.getKey());
30+
}
31+
32+
@Test
33+
void testToKeyInvalidReturnsNull() {
34+
assertNull(CustomBlockHandler.toKey("bad key with spaces", "oraxen"));
35+
assertNull(CustomBlockHandler.toKey(null, "oraxen"));
36+
}
37+
}

0 commit comments

Comments
 (0)