diff --git a/pom.xml b/pom.xml
index 961b3e7..a2b10dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -171,6 +171,12 @@
Oraxen Repository
https://repo.oraxen.com/releases
+
+
+ nexo
+ Nexo Repository
+ https://repo.nexomc.com/releases
+
@@ -277,6 +283,19 @@
4.0.10
provided
+
+
+ com.nexomc
+ nexo
+ 1.19.1
+
+
+ dev.triumphteam
+ triumph-gui
+
+
+ provided
+
io.th0rgal
diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java
index b9fe809..ae91392 100644
--- a/src/main/java/world/bentobox/level/Level.java
+++ b/src/main/java/world/bentobox/level/Level.java
@@ -488,4 +488,12 @@ public boolean isItemsAdder() {
return !getSettings().isDisableItemsAdder() && getPlugin().getHooks().getHook("ItemsAdder").isPresent();
}
+ /**
+ * @return true if the Nexo plugin is enabled and not disabled in config
+ */
+ public boolean isNexo() {
+ return !getSettings().getDisabledPluginHooks().contains("Nexo")
+ && Bukkit.getPluginManager().isPluginEnabled("Nexo");
+ }
+
}
diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java
index 59fbd2a..a7425eb 100644
--- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java
+++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java
@@ -43,6 +43,10 @@
import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Multisets;
+import com.nexomc.nexo.api.NexoBlocks;
+import com.nexomc.nexo.api.NexoFurniture;
+import com.nexomc.nexo.api.NexoItems;
+import com.nexomc.nexo.mechanics.custom_block.CustomBlockMechanic;
import dev.rosewood.rosestacker.api.RoseStackerAPI;
import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI;
@@ -426,7 +430,19 @@ private void countItemStack(ItemStack i) {
}
return;
}
-
+ // Check Nexo
+ if (addon.isNexo() && NexoItems.exists(i)) {
+ String id = NexoItems.idFromItem(i);
+ if (id == null) {
+ return;
+ }
+ id = "nexo:" + id;
+ for (int c = 0; c < i.getAmount(); c++) {
+ checkBlock(id, false);
+ }
+ return;
+ }
+
if (i == null || !i.getType().isBlock())
return;
@@ -477,8 +493,8 @@ record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {
}
private void scanAsync(ChunkPair cp) {
- // Track chunks for Oraxen furniture entity scanning (done on main thread later)
- if (BentoBox.getInstance().getHooks().getHook("Oraxen").isPresent()) {
+// Track chunks for furniture entity scanning (Oraxen and Nexo are entity-based)
+ if (BentoBox.getInstance().getHooks().getHook("Oraxen").isPresent() || addon.isNexo()) {
furnitureChunks.add(cp.chunk);
}
// Get the chunk coordinates and island boundaries once per chunk scan
@@ -526,10 +542,11 @@ private void processBlock(ChunkPair cp, int x, int y, int z, int globalX, int gl
// Create a Location object only when needed for more complex checks.
Location loc = null;
- // === Custom Block Hooks (ItemsAdder, Oraxen) ===
+ // === Custom Block Hooks (ItemsAdder, Oraxen, Nexo) ===
// These hooks can define custom blocks that override vanilla behavior.
// They must be checked first.
- if (addon.isItemsAdder() || BentoBox.getInstance().getHooks().getHook("Oraxen").isPresent()) {
+ if (addon.isItemsAdder() || BentoBox.getInstance().getHooks().getHook("Oraxen").isPresent()
+ || addon.isNexo()) {
loc = new Location(cp.world, globalX, y, globalZ);
String customBlockId = null;
if (addon.isItemsAdder()) {
@@ -541,6 +558,12 @@ private void processBlock(ChunkPair cp, int x, int y, int z, int globalX, int gl
customBlockId = "oraxen:" + oraxenId; // Make a namespaced ID
}
}
+ if (customBlockId == null && addon.isNexo()) {
+ CustomBlockMechanic nexoMechanic = NexoBlocks.customBlockMechanic(loc);
+ if (nexoMechanic != null) {
+ customBlockId = "nexo:" + nexoMechanic.getItemID();
+ }
+ }
if (customBlockId != null) {
// If a custom block is found, count it and stop further processing for this block.
@@ -750,6 +773,7 @@ public void scanIsland(Pipeliner pipeliner) {
// This was the last chunk. Handle stacked blocks, spawners, chests and exit
handleStackedBlocks().thenCompose(v -> handleSpawners()).thenCompose(v -> handleChests())
.thenCompose(v -> handleOraxenFurniture())
+ .thenCompose(v -> handleNexoFurniture())
.thenRun(() -> {
this.tidyUp();
this.getR().complete(getResults());
@@ -822,7 +846,7 @@ private CompletableFuture handleOraxenFurniture() {
|| loc.getBlockZ() < minZ || loc.getBlockZ() >= maxZ) {
continue;
}
- FurnitureMechanic mechanic = OraxenHook.getFurnitureMechanic(entity);
+ var mechanic = OraxenHook.getFurnitureMechanic(entity);
if (mechanic == null) {
continue;
}
@@ -835,6 +859,47 @@ private CompletableFuture handleOraxenFurniture() {
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
+ /**
+ * Scans entities in each island chunk for Nexo furniture and counts them toward the island level.
+ * Nexo furniture is entity-based (ItemDisplay entities), so it is invisible to the normal block
+ * scanner. Only entities for which a FurnitureMechanic can be resolved are counted, which
+ * naturally filters to base furniture entities.
+ *
+ * @return a CompletableFuture that completes when all chunks have been checked
+ */
+ private CompletableFuture handleNexoFurniture() {
+ if (!addon.isNexo() || furnitureChunks.isEmpty()) {
+ return CompletableFuture.completedFuture(null);
+ }
+ int minX = island.getMinProtectedX();
+ int maxX = island.getMaxProtectedX();
+ int minZ = island.getMinProtectedZ();
+ int maxZ = island.getMaxProtectedZ();
+ List> futures = new ArrayList<>();
+ for (Chunk chunk : furnitureChunks) {
+ CompletableFuture future = Util.getChunkAtAsync(chunk.getWorld(), chunk.getX(), chunk.getZ())
+ .thenAccept(c -> {
+ for (Entity entity : c.getEntities()) {
+ Location loc = entity.getLocation();
+ // Confirm entity is within the island's protected bounds
+ if (loc.getBlockX() < minX || loc.getBlockX() >= maxX
+ || loc.getBlockZ() < minZ || loc.getBlockZ() >= maxZ) {
+ continue;
+ }
+ // getFurnitureMechanic returns non-null only for furniture base entities
+ var mechanic = NexoFurniture.furnitureMechanic(entity);
+ if (mechanic == null) {
+ continue;
+ }
+ boolean belowSeaLevel = seaHeight > 0 && loc.getBlockY() <= seaHeight;
+ checkBlock("nexo:" + mechanic.getItemID(), belowSeaLevel);
+ }
+ });
+ futures.add(future);
+ }
+ return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
+ }
+
private CompletableFuture handleStackedBlocks() {
// Deal with any stacked blocks
List> futures = new ArrayList<>();
diff --git a/src/main/java/world/bentobox/level/config/BlockConfig.java b/src/main/java/world/bentobox/level/config/BlockConfig.java
index b554264..2c9793a 100644
--- a/src/main/java/world/bentobox/level/config/BlockConfig.java
+++ b/src/main/java/world/bentobox/level/config/BlockConfig.java
@@ -19,6 +19,8 @@
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.EntityType;
+import com.nexomc.nexo.api.NexoItems;
+
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.OraxenHook;
@@ -105,6 +107,9 @@ private boolean isOther(String key) {
if (key.startsWith("oraxen:") && BentoBox.getInstance().getHooks().getHook("Oraxen").isPresent()) {
return OraxenHook.exists(key.substring(7));
}
+ if (key.startsWith("nexo:") && addon.isNexo()) {
+ return NexoItems.exists(key.substring(5));
+ }
// Check ItemsAdder
return addon.isItemsAdder() && ItemsAdderHook.isInRegistry(key);
}
diff --git a/src/main/java/world/bentobox/level/util/Utils.java b/src/main/java/world/bentobox/level/util/Utils.java
index dff4a02..5c23c0b 100644
--- a/src/main/java/world/bentobox/level/util/Utils.java
+++ b/src/main/java/world/bentobox/level/util/Utils.java
@@ -161,6 +161,8 @@ public static String prettifyObject(Object object, User user) {
// Remove prefix
if (key.startsWith("oraxen:")) {
key = key.substring(7);
+ } else if (key.startsWith("nexo:")) {
+ key = key.substring(5);
}
}