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 ;
4948import world .bentobox .bentobox .BentoBox ;
5049import world .bentobox .bentobox .database .objects .Island ;
5150import world .bentobox .bentobox .hooks .ItemsAdderHook ;
51+ import world .bentobox .bentobox .hooks .OraxenHook ;
5252import world .bentobox .bentobox .util .Pair ;
5353import world .bentobox .bentobox .util .Util ;
5454import world .bentobox .level .Level ;
@@ -276,7 +276,7 @@ private List<String> getReport() {
276276 Integer limit = addon .getBlockConfig ().getLimit (type .getElement ());
277277 String explain = ")" ;
278278 reportLines .add (Util .prettifyText (type .toString ()) + ": " + String .format ("%,d" , type .getCount ())
279- + " blocks (max " + limit + explain );
279+ + " blocks (max " + limit + explain );
280280 }
281281 reportLines .add (LINE_BREAK );
282282 return reportLines ;
@@ -368,14 +368,12 @@ private int limitCountAndValue(Object obj) {
368368 if (!(obj instanceof Material ) && !(obj instanceof EntityType ) && !(obj instanceof String )) {
369369 return 0 ;
370370 }
371-
371+ // Get the limit of any particular material or entity type
372372 Integer limit = addon .getBlockConfig ().getLimit (obj );
373373 if (limit == null ) {
374374 return getValue (obj );
375375 }
376-
377376 int count = limitCount .getOrDefault (obj , 0 );
378-
379377 if (count > limit ) {
380378 // Add block to ofCount
381379 this .results .ofCount .add (obj );
@@ -412,6 +410,19 @@ private void scanChests(Chunk chunk) {
412410 }
413411
414412 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+
415426 if (i == null || !i .getType ().isBlock ())
416427 return ;
417428
@@ -462,18 +473,21 @@ record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {
462473 }
463474
464475 private void scanAsync (ChunkPair cp ) {
465- int chunkX = cp .chunkSnapshot .getX () * 16 ;
466- 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 ;
467479 int minX = island .getMinProtectedX ();
468- int maxX = minX + island .getProtectionRange () * 2 ;
480+ int maxX = island .getMaxProtectedX () ;
469481 int minZ = island .getMinProtectedZ ();
470- int maxZ = minZ + island .getProtectionRange () * 2 ;
482+ int maxZ = island .getMaxProtectedZ () ;
471483
472484 for (int x = 0 ; x < 16 ; x ++) {
473485 int globalX = chunkX + x ;
486+ // Check if the block is within the island's X-boundary
474487 if (globalX >= minX && globalX < maxX ) {
475488 for (int z = 0 ; z < 16 ; z ++) {
476489 int globalZ = chunkZ + z ;
490+ // Check if the block is within the island's Z-boundary
477491 if (globalZ >= minZ && globalZ < maxZ ) {
478492 for (int y = cp .world .getMinHeight (); y < cp .world .getMaxHeight (); y ++) {
479493 processBlock (cp , x , y , z , globalX , globalZ );
@@ -484,98 +498,104 @@ private void scanAsync(ChunkPair cp) {
484498 }
485499 }
486500
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+ */
487512 private void processBlock (ChunkPair cp , int x , int y , int z , int globalX , int globalZ ) {
488513 BlockData blockData = cp .chunkSnapshot .getBlockData (x , y , z );
489514 Material m = blockData .getMaterial ();
490515
516+ // Determine if the block is below sea level for potential score multipliers.
491517 boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight ;
492- Location loc = new Location (cp .world , globalX , y , globalZ );
493-
494- String customRegionId = addon .isItemsAdder () ? ItemsAdderHook .getInCustomRegion (loc ) : null ;
495- if (customRegionId != null ) {
496- checkBlock (customRegionId , belowSeaLevel );
497- return ;
498- }
499-
500- processSlabs (blockData , m , belowSeaLevel );
501- processStackers (loc , m );
502- processUltimateStacker (m , loc , belowSeaLevel );
503- processChests (cp , cp .chunkSnapshot .getBlockType (x , y , z ));
504- processSpawnerOrBlock (m , loc , belowSeaLevel );
505- }
506-
507- private void processSlabs (BlockData blockData , Material m , boolean belowSeaLevel ) {
508- if (Tag .SLABS .isTagged (m )) {
509- Slab slab = (Slab ) blockData ;
510- if (slab .getType ().equals (Slab .Type .DOUBLE )) {
511- checkBlock (m , belowSeaLevel );
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 );
529+ }
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+ }
512535 }
513- }
514- }
515-
516- private void processStackers (Location loc , Material m ) {
517- if (addon .isStackersEnabled () && (m .equals (Material .CAULDRON ) || m .equals (Material .SPAWNER ))) {
518- stackedBlocks .add (loc );
519- }
520- }
521536
522- private void processUltimateStacker (Material m , Location loc , boolean belowSeaLevel ) {
523- if (addon .isUltimateStackerEnabled () && !m .isAir ()) {
524- 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+ }
525542 }
526- }
527543
528- private void processChests (ChunkPair cp , Material material ) {
529- if (addon .getSettings ().isIncludeChests ()) {
530- switch (material ) {
531- case CHEST :
532- case TRAPPED_CHEST :
533- case BARREL :
534- case HOPPER :
535- case DISPENSER :
536- case DROPPER :
537- case SHULKER_BOX :
538- case WHITE_SHULKER_BOX :
539- case ORANGE_SHULKER_BOX :
540- case MAGENTA_SHULKER_BOX :
541- case LIGHT_BLUE_SHULKER_BOX :
542- case YELLOW_SHULKER_BOX :
543- case LIME_SHULKER_BOX :
544- case PINK_SHULKER_BOX :
545- case GRAY_SHULKER_BOX :
546- case LIGHT_GRAY_SHULKER_BOX :
547- case CYAN_SHULKER_BOX :
548- case PURPLE_SHULKER_BOX :
549- case BLUE_SHULKER_BOX :
550- case BROWN_SHULKER_BOX :
551- case GREEN_SHULKER_BOX :
552- case RED_SHULKER_BOX :
553- case BLACK_SHULKER_BOX :
554- case BREWING_STAND :
555- case FURNACE :
556- case BLAST_FURNACE :
557- case SMOKER :
558- case BEACON : // has an inventory slot
559- case ENCHANTING_TABLE : // technically has an item slot
560- case LECTERN : // stores a book
561- case JUKEBOX : // stores a record
562- // ✅ 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 :
563566 chestBlocks .add (cp .chunk );
564- break ;
567+ break ;
565568 default :
566- // ❌ Not a container
569+ // Not a container of interest.
567570 break ;
568571 }
572+ }
569573
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 ));
570584 }
571- }
572585
573- private void processSpawnerOrBlock (Material m , Location loc , boolean belowSeaLevel ) {
574- if (m == Material .SPAWNER ) {
575- spawners .put (loc , belowSeaLevel );
576- } 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.
577591 checkBlock (m , belowSeaLevel );
592+ return ;
578593 }
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 );
579599 }
580600
581601 /**
@@ -628,13 +648,13 @@ private Collection<String> sortedReport(int total, Multiset<Object> uwCount) {
628648 name = Util .prettifyText (et .name () + BlockConfig .SPAWNER );
629649 value = Objects .requireNonNullElse (addon .getBlockConfig ().getValue (island .getWorld (), et ), 0 );
630650 } else if (en .getElement () instanceof String str ) {
631- name = str ;
651+ name = Util . prettifyText ( str ) ;
632652 value = Objects .requireNonNullElse (addon .getBlockConfig ().getValue (island .getWorld (), str ), 0 );
633653 }
634-
654+ int limit = addon . getBlockConfig (). getLimit ( en . getElement ()) == null ? en . getCount () : Math . min ( en . getCount (), addon . getBlockConfig (). getLimit ( en . getElement ()));
635655 result .add (name + ": " + String .format ("%,d" , en .getCount ()) + " blocks x " + value + " = "
636- + (value * en . getCount () ));
637- total += (value * en . getCount () );
656+ + (value * limit ));
657+ total += (value * limit );
638658
639659 }
640660 result .add ("Subtotal = " + total );
@@ -648,7 +668,7 @@ private Collection<String> sortedReport(int total, Multiset<Object> uwCount) {
648668 public void tidyUp () {
649669 // Finalize calculations
650670 results .rawBlockCount
651- .addAndGet ((long ) (results .underWaterBlockCount .get () * addon .getSettings ().getUnderWaterMultiplier ()));
671+ .addAndGet ((long ) (results .underWaterBlockCount .get () * addon .getSettings ().getUnderWaterMultiplier ()));
652672
653673 // Set the death penalty
654674 if (this .addon .getSettings ().isSumTeamDeaths ()) {
@@ -721,7 +741,7 @@ public void scanIsland(Pipeliner pipeliner) {
721741 // Chunk finished
722742 // This was the last chunk. Handle stacked blocks, spawners, chests and exit
723743 handleStackedBlocks ().thenCompose (v -> handleSpawners ()).thenCompose (v -> handleChests ())
724- .thenRun (() -> {
744+ .thenRun (() -> {
725745 this .tidyUp ();
726746 this .getR ().complete (getResults ());
727747 });
0 commit comments