2222import org .bukkit .ChunkSnapshot ;
2323import org .bukkit .Location ;
2424import org .bukkit .Material ;
25- import org .bukkit .Tag ;
2625import org .bukkit .World ;
2726import org .bukkit .World .Environment ;
2827import org .bukkit .block .Block ;
@@ -277,7 +276,7 @@ private List<String> getReport() {
277276 Integer limit = addon .getBlockConfig ().getLimit (type .getElement ());
278277 String explain = ")" ;
279278 reportLines .add (Util .prettifyText (type .toString ()) + ": " + String .format ("%,d" , type .getCount ())
280- + " blocks (max " + limit + explain );
279+ + " blocks (max " + limit + explain );
281280 }
282281 reportLines .add (LINE_BREAK );
283282 return reportLines ;
@@ -369,14 +368,12 @@ private int limitCountAndValue(Object obj) {
369368 if (!(obj instanceof Material ) && !(obj instanceof EntityType ) && !(obj instanceof String )) {
370369 return 0 ;
371370 }
372-
371+ // Get the limit of any particular material or entity type
373372 Integer limit = addon .getBlockConfig ().getLimit (obj );
374373 if (limit == null ) {
375374 return getValue (obj );
376375 }
377-
378376 int count = limitCount .getOrDefault (obj , 0 );
379-
380377 if (count > limit ) {
381378 // Add block to ofCount
382379 this .results .ofCount .add (obj );
@@ -413,6 +410,19 @@ private void scanChests(Chunk chunk) {
413410 }
414411
415412 private void countItemStack (ItemStack i ) {
413+ // Check Oraxen
414+ if (BentoBox .getInstance ().getHooks ().getHook ("Oraxen" ).isPresent () && OraxenHook .exists (i )) {
415+ String id = OraxenHook .getIdByItem (i );
416+ if (id == null ) {
417+ return ;
418+ }
419+ id = "oraxen:" + id ;
420+ for (int c = 0 ; c < i .getAmount (); c ++) {
421+ checkBlock (id , false );
422+ }
423+ return ;
424+ }
425+
416426 if (i == null || !i .getType ().isBlock ())
417427 return ;
418428
@@ -463,18 +473,21 @@ record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {
463473 }
464474
465475 private void scanAsync (ChunkPair cp ) {
466- int chunkX = cp .chunkSnapshot .getX () * 16 ;
467- int chunkZ = cp .chunkSnapshot .getZ () * 16 ;
476+ // Get the chunk coordinates and island boundaries once per chunk scan
477+ int chunkX = cp .chunk .getX () << 4 ;
478+ int chunkZ = cp .chunk .getZ () << 4 ;
468479 int minX = island .getMinProtectedX ();
469- int maxX = minX + island .getProtectionRange () * 2 ;
480+ int maxX = island .getMaxProtectedX () ;
470481 int minZ = island .getMinProtectedZ ();
471- int maxZ = minZ + island .getProtectionRange () * 2 ;
482+ int maxZ = island .getMaxProtectedZ () ;
472483
473484 for (int x = 0 ; x < 16 ; x ++) {
474485 int globalX = chunkX + x ;
486+ // Check if the block is within the island's X-boundary
475487 if (globalX >= minX && globalX < maxX ) {
476488 for (int z = 0 ; z < 16 ; z ++) {
477489 int globalZ = chunkZ + z ;
490+ // Check if the block is within the island's Z-boundary
478491 if (globalZ >= minZ && globalZ < maxZ ) {
479492 for (int y = cp .world .getMinHeight (); y < cp .world .getMaxHeight (); y ++) {
480493 processBlock (cp , x , y , z , globalX , globalZ );
@@ -485,105 +498,104 @@ private void scanAsync(ChunkPair cp) {
485498 }
486499 }
487500
501+ /**
502+ * Processes a single block from a chunk snapshot to calculate its contribution to the island's level.
503+ * This method is designed to be efficient by minimizing object creation and using direct checks.
504+ *
505+ * @param cp The ChunkPair containing the world, chunk, and snapshot.
506+ * @param x The block's X coordinate within the chunk (0-15).
507+ * @param y The block's Y coordinate.
508+ * @param z The block's Z coordinate within the chunk (0-15).
509+ * @param globalX The block's global X coordinate in the world.
510+ * @param globalZ The block's global Z coordinate in the world.
511+ */
488512 private void processBlock (ChunkPair cp , int x , int y , int z , int globalX , int globalZ ) {
489513 BlockData blockData = cp .chunkSnapshot .getBlockData (x , y , z );
490514 Material m = blockData .getMaterial ();
491515
516+ // Determine if the block is below sea level for potential score multipliers.
492517 boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight ;
493- Location loc = new Location (cp .world , globalX , y , globalZ );
494-
495- String customRegionId = addon .isItemsAdder () ? ItemsAdderHook .getInCustomRegion (loc ) : null ;
496- // Try Oraxen
497- if (customRegionId == null && BentoBox .getInstance ().getHooks ().getHook ("ItemsAdder" ).isPresent ()) {
498- customRegionId = OraxenHook .getOraxenBlockID (loc );
499- if (customRegionId != null ) {
500- BentoBox .getInstance ().logDebug (customRegionId );
518+ // Create a Location object only when needed for more complex checks.
519+ Location loc = null ;
520+
521+ // === Custom Block Hooks (ItemsAdder, Oraxen) ===
522+ // These hooks can define custom blocks that override vanilla behavior.
523+ // They must be checked first.
524+ if (addon .isItemsAdder () || BentoBox .getInstance ().getHooks ().getHook ("Oraxen" ).isPresent ()) {
525+ loc = new Location (cp .world , globalX , y , globalZ );
526+ String customBlockId = null ;
527+ if (addon .isItemsAdder ()) {
528+ customBlockId = ItemsAdderHook .getInCustomRegion (loc );
501529 }
502- }
503- if (customRegionId != null ) {
504- checkBlock (customRegionId , belowSeaLevel );
505- return ;
506- }
507-
508- processSlabs (blockData , m , belowSeaLevel );
509- processStackers (loc , m );
510- processUltimateStacker (m , loc , belowSeaLevel );
511- processChests (cp , cp .chunkSnapshot .getBlockType (x , y , z ));
512- processSpawnerOrBlock (m , loc , belowSeaLevel );
513- }
514-
515- private void processSlabs (BlockData blockData , Material m , boolean belowSeaLevel ) {
516- if (Tag .SLABS .isTagged (m )) {
517- Slab slab = (Slab ) blockData ;
518- if (slab .getType ().equals (Slab .Type .DOUBLE )) {
519- checkBlock (m , belowSeaLevel );
530+ if (customBlockId == null && BentoBox .getInstance ().getHooks ().getHook ("Oraxen" ).isPresent ()) {
531+ String oraxenId = OraxenHook .getOraxenBlockID (loc );
532+ if (oraxenId != null ) {
533+ customBlockId = "oraxen:" + oraxenId ; // Make a namespaced ID
534+ }
520535 }
521- }
522- }
523-
524- private void processStackers (Location loc , Material m ) {
525- if (addon .isStackersEnabled () && (m .equals (Material .CAULDRON ) || m .equals (Material .SPAWNER ))) {
526- stackedBlocks .add (loc );
527- }
528- }
529536
530- private void processUltimateStacker (Material m , Location loc , boolean belowSeaLevel ) {
531- if (addon .isUltimateStackerEnabled () && !m .isAir ()) {
532- UltimateStackerCalc .addStackers (m , loc , results , belowSeaLevel , limitCountAndValue (m ));
537+ if (customBlockId != null ) {
538+ // If a custom block is found, count it and stop further processing for this block.
539+ checkBlock (customBlockId , belowSeaLevel );
540+ return ;
541+ }
533542 }
534- }
535543
536- private void processChests (ChunkPair cp , Material material ) {
537- if (addon .getSettings ().isIncludeChests ()) {
538- switch (material ) {
539- case CHEST :
540- case TRAPPED_CHEST :
541- case BARREL :
542- case HOPPER :
543- case DISPENSER :
544- case DROPPER :
545- case SHULKER_BOX :
546- case WHITE_SHULKER_BOX :
547- case ORANGE_SHULKER_BOX :
548- case MAGENTA_SHULKER_BOX :
549- case LIGHT_BLUE_SHULKER_BOX :
550- case YELLOW_SHULKER_BOX :
551- case LIME_SHULKER_BOX :
552- case PINK_SHULKER_BOX :
553- case GRAY_SHULKER_BOX :
554- case LIGHT_GRAY_SHULKER_BOX :
555- case CYAN_SHULKER_BOX :
556- case PURPLE_SHULKER_BOX :
557- case BLUE_SHULKER_BOX :
558- case BROWN_SHULKER_BOX :
559- case GREEN_SHULKER_BOX :
560- case RED_SHULKER_BOX :
561- case BLACK_SHULKER_BOX :
562- case BREWING_STAND :
563- case FURNACE :
564- case BLAST_FURNACE :
565- case SMOKER :
566- case BEACON : // has an inventory slot
567- case ENCHANTING_TABLE : // technically has an item slot
568- case LECTERN : // stores a book
569- case JUKEBOX : // stores a record
570- // ✅ It's a container
544+ // === Spawner Handling ===
545+ // Spawners are a special case. Their type needs to be read from the block state,
546+ // which requires main thread access. We defer this by adding them to a map to process later.
547+ if (m == Material .SPAWNER ) {
548+ if (loc == null ) loc = new Location (cp .world , globalX , y , globalZ );
549+ spawners .put (loc , belowSeaLevel );
550+ // Spawners are also counted as regular blocks, so we continue processing.
551+ }
552+
553+ // === Container Block Identification ===
554+ // If chest counting is enabled, we identify blocks that can hold items.
555+ // We add the chunk to a set for later scanning, avoiding duplicate chunk processing.
556+ if (addon .getSettings ().isIncludeChests () && m .isInteractable ()) {
557+ switch (m ) {
558+ case CHEST , TRAPPED_CHEST , BARREL , HOPPER , DISPENSER , DROPPER ,
559+ SHULKER_BOX , WHITE_SHULKER_BOX , ORANGE_SHULKER_BOX , MAGENTA_SHULKER_BOX ,
560+ LIGHT_BLUE_SHULKER_BOX , YELLOW_SHULKER_BOX , LIME_SHULKER_BOX , PINK_SHULKER_BOX ,
561+ GRAY_SHULKER_BOX , LIGHT_GRAY_SHULKER_BOX , CYAN_SHULKER_BOX , PURPLE_SHULKER_BOX ,
562+ BLUE_SHULKER_BOX , BROWN_SHULKER_BOX , GREEN_SHULKER_BOX , RED_SHULKER_BOX ,
563+ BLACK_SHULKER_BOX ,
564+ BREWING_STAND , FURNACE , BLAST_FURNACE , SMOKER ,
565+ BEACON , ENCHANTING_TABLE , LECTERN , JUKEBOX :
571566 chestBlocks .add (cp .chunk );
572- break ;
567+ break ;
573568 default :
574- // ❌ Not a container
569+ // Not a container of interest.
575570 break ;
576571 }
572+ }
577573
574+ // === Stacked Block Hooks (WildStacker, RoseStacker, etc.) ===
575+ // These plugins stack blocks like spawners or cauldrons. We identify potential
576+ // stacked blocks and add their locations for later, more detailed checks.
577+ if (addon .isStackersEnabled () && (m == Material .CAULDRON || m == Material .SPAWNER )) {
578+ if (loc == null ) loc = new Location (cp .world , globalX , y , globalZ );
579+ stackedBlocks .add (loc );
580+ }
581+ if (addon .isUltimateStackerEnabled ()) {
582+ if (loc == null ) loc = new Location (cp .world , globalX , y , globalZ );
583+ UltimateStackerCalc .addStackers (m , loc , results , belowSeaLevel , limitCountAndValue (m ));
578584 }
579- }
580585
581- private void processSpawnerOrBlock (Material m , Location loc , boolean belowSeaLevel ) {
582- if (m == Material .SPAWNER ) {
583- spawners .put (loc , belowSeaLevel );
584- } else {
586+ // === Slab Handling ===
587+ // Double slabs are counted as a single, full block. Single slabs are counted
588+ // as regular blocks. This logic prevents double-counting.
589+ if (blockData instanceof Slab slab && slab .getType () == Slab .Type .DOUBLE ) {
590+ // This is a full block, so count it and we are done with it.
585591 checkBlock (m , belowSeaLevel );
592+ return ;
586593 }
594+
595+ // === Default Block Value Calculation ===
596+ // If the block is not a special case handled above (like a double slab),
597+ // count it as a regular block. This includes single slabs.
598+ checkBlock (m , belowSeaLevel );
587599 }
588600
589601 /**
@@ -636,13 +648,13 @@ private Collection<String> sortedReport(int total, Multiset<Object> uwCount) {
636648 name = Util .prettifyText (et .name () + BlockConfig .SPAWNER );
637649 value = Objects .requireNonNullElse (addon .getBlockConfig ().getValue (island .getWorld (), et ), 0 );
638650 } else if (en .getElement () instanceof String str ) {
639- name = str ;
651+ name = Util . prettifyText ( str ) ;
640652 value = Objects .requireNonNullElse (addon .getBlockConfig ().getValue (island .getWorld (), str ), 0 );
641653 }
642-
654+ int limit = addon . getBlockConfig (). getLimit ( en . getElement ()) == null ? en . getCount () : Math . min ( en . getCount (), addon . getBlockConfig (). getLimit ( en . getElement ()));
643655 result .add (name + ": " + String .format ("%,d" , en .getCount ()) + " blocks x " + value + " = "
644- + (value * en . getCount () ));
645- total += (value * en . getCount () );
656+ + (value * limit ));
657+ total += (value * limit );
646658
647659 }
648660 result .add ("Subtotal = " + total );
@@ -656,7 +668,7 @@ private Collection<String> sortedReport(int total, Multiset<Object> uwCount) {
656668 public void tidyUp () {
657669 // Finalize calculations
658670 results .rawBlockCount
659- .addAndGet ((long ) (results .underWaterBlockCount .get () * addon .getSettings ().getUnderWaterMultiplier ()));
671+ .addAndGet ((long ) (results .underWaterBlockCount .get () * addon .getSettings ().getUnderWaterMultiplier ()));
660672
661673 // Set the death penalty
662674 if (this .addon .getSettings ().isSumTeamDeaths ()) {
@@ -729,7 +741,7 @@ public void scanIsland(Pipeliner pipeliner) {
729741 // Chunk finished
730742 // This was the last chunk. Handle stacked blocks, spawners, chests and exit
731743 handleStackedBlocks ().thenCompose (v -> handleSpawners ()).thenCompose (v -> handleChests ())
732- .thenRun (() -> {
744+ .thenRun (() -> {
733745 this .tidyUp ();
734746 this .getR ().complete (getResults ());
735747 });
0 commit comments