4343import com .google .common .collect .Multiset ;
4444import com .google .common .collect .Multiset .Entry ;
4545import com .google .common .collect .Multisets ;
46+ import com .nexomc .nexo .api .NexoBlocks ;
47+ import com .nexomc .nexo .api .NexoFurniture ;
48+ import com .nexomc .nexo .api .NexoItems ;
49+ import com .nexomc .nexo .mechanics .custom_block .CustomBlockMechanic ;
4650
4751import dev .rosewood .rosestacker .api .RoseStackerAPI ;
4852import us .lynuxcraft .deadsilenceiv .advancedchests .AdvancedChestsAPI ;
@@ -426,7 +430,19 @@ private void countItemStack(ItemStack i) {
426430 }
427431 return ;
428432 }
429-
433+ // Check Nexo
434+ if (addon .isNexo () && NexoItems .exists (i )) {
435+ String id = NexoItems .idFromItem (i );
436+ if (id == null ) {
437+ return ;
438+ }
439+ id = "nexo:" + id ;
440+ for (int c = 0 ; c < i .getAmount (); c ++) {
441+ checkBlock (id , false );
442+ }
443+ return ;
444+ }
445+
430446 if (i == null || !i .getType ().isBlock ())
431447 return ;
432448
@@ -477,8 +493,8 @@ record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {
477493 }
478494
479495 private void scanAsync (ChunkPair cp ) {
480- // Track chunks for Oraxen furniture entity scanning (done on main thread later )
481- if (BentoBox .getInstance ().getHooks ().getHook ("Oraxen" ).isPresent ()) {
496+ // Track chunks for furniture entity scanning (Oraxen and Nexo are entity-based )
497+ if (BentoBox .getInstance ().getHooks ().getHook ("Oraxen" ).isPresent () || addon . isNexo () ) {
482498 furnitureChunks .add (cp .chunk );
483499 }
484500 // 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
526542 // Create a Location object only when needed for more complex checks.
527543 Location loc = null ;
528544
529- // === Custom Block Hooks (ItemsAdder, Oraxen) ===
545+ // === Custom Block Hooks (ItemsAdder, Oraxen, Nexo ) ===
530546 // These hooks can define custom blocks that override vanilla behavior.
531547 // They must be checked first.
532- if (addon .isItemsAdder () || BentoBox .getInstance ().getHooks ().getHook ("Oraxen" ).isPresent ()) {
548+ if (addon .isItemsAdder () || BentoBox .getInstance ().getHooks ().getHook ("Oraxen" ).isPresent ()
549+ || addon .isNexo ()) {
533550 loc = new Location (cp .world , globalX , y , globalZ );
534551 String customBlockId = null ;
535552 if (addon .isItemsAdder ()) {
@@ -541,6 +558,12 @@ private void processBlock(ChunkPair cp, int x, int y, int z, int globalX, int gl
541558 customBlockId = "oraxen:" + oraxenId ; // Make a namespaced ID
542559 }
543560 }
561+ if (customBlockId == null && addon .isNexo ()) {
562+ CustomBlockMechanic nexoMechanic = NexoBlocks .customBlockMechanic (loc );
563+ if (nexoMechanic != null ) {
564+ customBlockId = "nexo:" + nexoMechanic .getItemID ();
565+ }
566+ }
544567
545568 if (customBlockId != null ) {
546569 // If a custom block is found, count it and stop further processing for this block.
@@ -750,6 +773,7 @@ public void scanIsland(Pipeliner pipeliner) {
750773 // This was the last chunk. Handle stacked blocks, spawners, chests and exit
751774 handleStackedBlocks ().thenCompose (v -> handleSpawners ()).thenCompose (v -> handleChests ())
752775 .thenCompose (v -> handleOraxenFurniture ())
776+ .thenCompose (v -> handleNexoFurniture ())
753777 .thenRun (() -> {
754778 this .tidyUp ();
755779 this .getR ().complete (getResults ());
@@ -822,7 +846,7 @@ private CompletableFuture<Void> handleOraxenFurniture() {
822846 || loc .getBlockZ () < minZ || loc .getBlockZ () >= maxZ ) {
823847 continue ;
824848 }
825- FurnitureMechanic mechanic = OraxenHook .getFurnitureMechanic (entity );
849+ var mechanic = OraxenHook .getFurnitureMechanic (entity );
826850 if (mechanic == null ) {
827851 continue ;
828852 }
@@ -835,6 +859,47 @@ private CompletableFuture<Void> handleOraxenFurniture() {
835859 return CompletableFuture .allOf (futures .toArray (new CompletableFuture [0 ]));
836860 }
837861
862+ /**
863+ * Scans entities in each island chunk for Nexo furniture and counts them toward the island level.
864+ * Nexo furniture is entity-based (ItemDisplay entities), so it is invisible to the normal block
865+ * scanner. Only entities for which a FurnitureMechanic can be resolved are counted, which
866+ * naturally filters to base furniture entities.
867+ *
868+ * @return a CompletableFuture that completes when all chunks have been checked
869+ */
870+ private CompletableFuture <Void > handleNexoFurniture () {
871+ if (!addon .isNexo () || furnitureChunks .isEmpty ()) {
872+ return CompletableFuture .completedFuture (null );
873+ }
874+ int minX = island .getMinProtectedX ();
875+ int maxX = island .getMaxProtectedX ();
876+ int minZ = island .getMinProtectedZ ();
877+ int maxZ = island .getMaxProtectedZ ();
878+ List <CompletableFuture <Void >> futures = new ArrayList <>();
879+ for (Chunk chunk : furnitureChunks ) {
880+ CompletableFuture <Void > future = Util .getChunkAtAsync (chunk .getWorld (), chunk .getX (), chunk .getZ ())
881+ .thenAccept (c -> {
882+ for (Entity entity : c .getEntities ()) {
883+ Location loc = entity .getLocation ();
884+ // Confirm entity is within the island's protected bounds
885+ if (loc .getBlockX () < minX || loc .getBlockX () >= maxX
886+ || loc .getBlockZ () < minZ || loc .getBlockZ () >= maxZ ) {
887+ continue ;
888+ }
889+ // getFurnitureMechanic returns non-null only for furniture base entities
890+ var mechanic = NexoFurniture .furnitureMechanic (entity );
891+ if (mechanic == null ) {
892+ continue ;
893+ }
894+ boolean belowSeaLevel = seaHeight > 0 && loc .getBlockY () <= seaHeight ;
895+ checkBlock ("nexo:" + mechanic .getItemID (), belowSeaLevel );
896+ }
897+ });
898+ futures .add (future );
899+ }
900+ return CompletableFuture .allOf (futures .toArray (new CompletableFuture [0 ]));
901+ }
902+
838903 private CompletableFuture <Void > handleStackedBlocks () {
839904 // Deal with any stacked blocks
840905 List <CompletableFuture <Void >> futures = new ArrayList <>();
0 commit comments