1
1
package world .bentobox .level .calculators ;
2
2
3
3
import java .util .ArrayList ;
4
+ import java .util .Arrays ;
4
5
import java .util .Collection ;
5
6
import java .util .Collections ;
6
7
import java .util .EnumMap ;
7
8
import java .util .HashMap ;
9
+ import java .util .HashSet ;
8
10
import java .util .Iterator ;
9
11
import java .util .List ;
10
12
import java .util .Map ;
11
13
import java .util .Queue ;
14
+ import java .util .Set ;
12
15
import java .util .UUID ;
13
16
import java .util .concurrent .CompletableFuture ;
14
17
import java .util .concurrent .ConcurrentLinkedQueue ;
15
18
16
19
import org .bukkit .Bukkit ;
17
20
import org .bukkit .Chunk ;
18
21
import org .bukkit .ChunkSnapshot ;
22
+ import org .bukkit .Location ;
19
23
import org .bukkit .Material ;
20
24
import org .bukkit .Tag ;
21
25
import org .bukkit .World ;
26
30
import org .bukkit .block .data .BlockData ;
27
31
import org .bukkit .block .data .type .Slab ;
28
32
import org .bukkit .inventory .ItemStack ;
29
- import org .bukkit .util .Vector ;
30
- import org .eclipse .jdt .annotation .Nullable ;
33
+ import org .bukkit .scheduler .BukkitTask ;
31
34
32
35
import com .bgsoftware .wildstacker .api .WildStackerAPI ;
33
36
import com .bgsoftware .wildstacker .api .objects .StackedBarrel ;
44
47
import world .bentobox .bentobox .util .Pair ;
45
48
import world .bentobox .bentobox .util .Util ;
46
49
import world .bentobox .level .Level ;
50
+ import world .bentobox .level .calculators .Results .Result ;
47
51
48
52
public class IslandLevelCalculator {
49
53
private static final String LINE_BREAK = "==================================" ;
50
54
public static final long MAX_AMOUNT = 10000 ;
55
+ private static final List <Material > CHESTS = Arrays .asList (Material .CHEST , Material .CHEST_MINECART , Material .TRAPPED_CHEST ,
56
+ Material .SHULKER_BOX , Material .BLACK_SHULKER_BOX , Material .BLUE_SHULKER_BOX , Material .BROWN_SHULKER_BOX ,
57
+ Material .CYAN_SHULKER_BOX , Material .GRAY_SHULKER_BOX , Material .GREEN_SHULKER_BOX , Material .LIGHT_BLUE_SHULKER_BOX ,
58
+ Material .LIGHT_GRAY_SHULKER_BOX , Material .LIME_SHULKER_BOX , Material .MAGENTA_SHULKER_BOX , Material .ORANGE_SHULKER_BOX ,
59
+ Material .PINK_SHULKER_BOX , Material .PURPLE_SHULKER_BOX , Material .RED_SHULKER_BOX , Material .RED_SHULKER_BOX ,
60
+ Material .WHITE_SHULKER_BOX , Material .YELLOW_SHULKER_BOX , Material .COMPOSTER , Material .BARREL , Material .DISPENSER ,
61
+ Material .DROPPER , Material .SMOKER , Material .BLAST_FURNACE );
62
+ private static final int CHUNKS_TO_SCAN = 100 ;
51
63
52
64
/**
53
65
* Method to evaluate a mathematical equation
@@ -156,6 +168,10 @@ void nextChar() {
156
168
private final boolean zeroIsland ;
157
169
private final Map <Environment , World > worlds = new EnumMap <>(Environment .class );
158
170
private final int seaHeight ;
171
+ private final List <Location > stackedBlocks = new ArrayList <>();
172
+ private final Set <Chunk > chestBlocks = new HashSet <>();
173
+ private BukkitTask finishTask ;
174
+
159
175
160
176
/**
161
177
* Constructor to get the level for an island
@@ -339,17 +355,35 @@ private int getValue(Material md) {
339
355
* @param z - chunk z coordinate
340
356
* @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether
341
357
*/
342
- private CompletableFuture <Chunk > getWorldChunk (Environment env , int x , int z ) {
358
+ private CompletableFuture <List < Chunk >> getWorldChunk (Environment env , Queue < Pair < Integer , Integer >> pairList ) {
343
359
if (worlds .containsKey (env )) {
344
- CompletableFuture <Chunk > r2 = new CompletableFuture <>();
360
+ CompletableFuture <List <Chunk >> r2 = new CompletableFuture <>();
361
+ List <Chunk > chunkList = new ArrayList <>();
362
+ World world = worlds .get (env );
345
363
// Get the chunk, and then coincidentally check the RoseStacker
346
- Util . getChunkAtAsync ( worlds . get ( env ), x , z , true ). thenAccept ( chunk -> roseStackerCheck ( r2 , chunk ) );
364
+ loadChunks ( r2 , world , pairList , chunkList );
347
365
return r2 ;
348
366
}
349
- return CompletableFuture .completedFuture (null );
367
+ return CompletableFuture .completedFuture (Collections . emptyList () );
350
368
}
351
369
352
- private void roseStackerCheck (CompletableFuture <Chunk > r2 , Chunk chunk ) {
370
+ private void loadChunks (CompletableFuture <List <Chunk >> r2 , World world , Queue <Pair <Integer , Integer >> pairList ,
371
+ List <Chunk > chunkList ) {
372
+ if (pairList .isEmpty ()) {
373
+ r2 .complete (chunkList );
374
+ return ;
375
+ }
376
+ Pair <Integer , Integer > p = pairList .poll ();
377
+ Util .getChunkAtAsync (world , p .x , p .z , world .getEnvironment ().equals (Environment .NETHER )).thenAccept (chunk -> {
378
+ if (chunk != null ) {
379
+ chunkList .add (chunk );
380
+ roseStackerCheck (chunk );
381
+ }
382
+ loadChunks (r2 , world , pairList , chunkList ); // Iteration
383
+ });
384
+ }
385
+
386
+ private void roseStackerCheck (Chunk chunk ) {
353
387
if (addon .isRoseStackersEnabled ()) {
354
388
RoseStackerAPI .getInstance ().getStackedBlocks (Collections .singletonList (chunk )).forEach (e -> {
355
389
// Blocks below sea level can be scored differently
@@ -360,7 +394,6 @@ private void roseStackerCheck(CompletableFuture<Chunk> r2, Chunk chunk) {
360
394
}
361
395
});
362
396
}
363
- r2 .complete (chunk );
364
397
}
365
398
366
399
/**
@@ -385,11 +418,10 @@ private int limitCount(Material md) {
385
418
386
419
/**
387
420
* Count the blocks on the island
388
- * @param result - the CompletableFuture that should be completed when this scan is done
389
- * @param chunkSnapshot - the chunk to scan
421
+ * @param chunk chunk to scan
390
422
*/
391
- private void scanAsync (CompletableFuture < Boolean > result , ChunkSnapshot chunkSnapshot , Chunk chunk ) {
392
- List < Vector > stackedBlocks = new ArrayList <> ();
423
+ private void scanAsync (Chunk chunk ) {
424
+ ChunkSnapshot chunkSnapshot = chunk . getChunkSnapshot ();
393
425
for (int x = 0 ; x < 16 ; x ++) {
394
426
// Check if the block coordinate is inside the protection zone and if not, don't count it
395
427
if (chunkSnapshot .getX () * 16 + x < island .getMinProtectedX () || chunkSnapshot .getX () * 16 + x >= island .getMinProtectedX () + island .getProtectionRange () * 2 ) {
@@ -413,29 +445,17 @@ private void scanAsync(CompletableFuture<Boolean> result, ChunkSnapshot chunkSna
413
445
}
414
446
// Hook for Wild Stackers (Blocks Only) - this has to use the real chunk
415
447
if (addon .isStackersEnabled () && blockData .getMaterial () == Material .CAULDRON ) {
416
- stackedBlocks .add (new Vector (x ,y ,z ));
448
+ stackedBlocks .add (new Location (chunk .getWorld (), x + chunkSnapshot .getX () * 16 ,y ,z + chunkSnapshot .getZ () * 16 ));
449
+ }
450
+ // Scan chests
451
+ if (addon .getSettings ().isIncludeChests () && CHESTS .contains (blockData .getMaterial ())) {
452
+ chestBlocks .add (chunk );
417
453
}
418
454
// Add the value of the block's material
419
455
checkBlock (blockData .getMaterial (), belowSeaLevel );
420
456
}
421
457
}
422
458
}
423
- // Complete the future - this must go back onto the primary thread to exit async otherwise subsequent actions will be async
424
- Bukkit .getScheduler ().runTask (addon .getPlugin (),() -> {
425
- // Deal with any stacked blocks
426
- stackedBlocks .forEach (v -> {
427
- Block cauldronBlock = chunk .getBlock (v .getBlockX (), v .getBlockY (), v .getBlockZ ());
428
- boolean belowSeaLevel = seaHeight > 0 && v .getBlockY () <= seaHeight ;
429
- if (WildStackerAPI .getWildStacker ().getSystemManager ().isStackedBarrel (cauldronBlock )) {
430
- StackedBarrel barrel = WildStackerAPI .getStackedBarrel (cauldronBlock );
431
- int barrelAmt = WildStackerAPI .getBarrelAmount (cauldronBlock );
432
- for (int _x = 0 ; _x < barrelAmt ; _x ++) {
433
- checkBlock (barrel .getType (), belowSeaLevel );
434
- }
435
- }
436
- });
437
- result .complete (true );
438
- });
439
459
}
440
460
441
461
/**
@@ -473,20 +493,21 @@ private void countItemStack(ItemStack i) {
473
493
474
494
/**
475
495
* Scan the chunk chests and count the blocks
476
- * @param chunk - the chunk to scan
496
+ * @param chunks - the chunk to scan
477
497
* @return future that completes when the scan is done and supplies a boolean that will be true if the scan was successful, false if not
478
498
*/
479
- private CompletableFuture <Boolean > scanChunk (@ Nullable Chunk chunk ) {
499
+ private CompletableFuture <Boolean > scanChunk (List < Chunk > chunks ) {
480
500
// If the chunk hasn't been generated, return
481
- if (chunk == null ) return CompletableFuture .completedFuture (false );
482
- // Scan chests
483
- if (addon .getSettings ().isIncludeChests ()) {
484
- scanChests (chunk );
501
+ if (chunks == null || chunks .isEmpty ()) {
502
+ return CompletableFuture .completedFuture (false );
485
503
}
486
504
// Count blocks in chunk
487
505
CompletableFuture <Boolean > result = new CompletableFuture <>();
488
- ChunkSnapshot snapshot = chunk .getChunkSnapshot ();
489
- Bukkit .getScheduler ().runTaskAsynchronously (BentoBox .getInstance (), () -> scanAsync (result , snapshot , chunk ));
506
+
507
+ Bukkit .getScheduler ().runTaskAsynchronously (BentoBox .getInstance (), () -> {
508
+ chunks .forEach (chunk -> scanAsync (chunk ));
509
+ Bukkit .getScheduler ().runTask (addon .getPlugin (),() -> result .complete (true ));
510
+ });
490
511
return result ;
491
512
}
492
513
@@ -501,16 +522,23 @@ public CompletableFuture<Boolean> scanNextChunk() {
501
522
return CompletableFuture .completedFuture (false );
502
523
}
503
524
// Retrieve and remove from the queue
504
- Pair <Integer , Integer > p = chunksToCheck .poll ();
525
+ Queue <Pair <Integer , Integer >> pairList = new ConcurrentLinkedQueue <>();
526
+ int i = 0 ;
527
+ while (!chunksToCheck .isEmpty () && i ++ < CHUNKS_TO_SCAN ) {
528
+ pairList .add (chunksToCheck .poll ());
529
+ }
530
+ Queue <Pair <Integer , Integer >> endPairList = new ConcurrentLinkedQueue <>(pairList );
531
+ Queue <Pair <Integer , Integer >> netherPairList = new ConcurrentLinkedQueue <>(pairList );
505
532
// Set up the result
506
533
CompletableFuture <Boolean > result = new CompletableFuture <>();
507
534
// Get chunks and scan
508
- getWorldChunk (Environment .THE_END , p .x , p .z ).thenAccept (endChunk ->
509
- scanChunk (endChunk ).thenAccept (b ->
510
- getWorldChunk (Environment .NETHER , p .x , p .z ).thenAccept (netherChunk ->
511
- scanChunk (netherChunk ).thenAccept (b2 ->
512
- getWorldChunk (Environment .NORMAL , p .x , p .z ).thenAccept (normalChunk ->
513
- scanChunk (normalChunk ).thenAccept (b3 ->
535
+ // Get chunks and scan
536
+ getWorldChunk (Environment .THE_END , endPairList ).thenAccept (endChunks ->
537
+ scanChunk (endChunks ).thenAccept (b ->
538
+ getWorldChunk (Environment .NETHER , netherPairList ).thenAccept (netherChunks ->
539
+ scanChunk (netherChunks ).thenAccept (b2 ->
540
+ getWorldChunk (Environment .NORMAL , pairList ).thenAccept (normalChunks ->
541
+ scanChunk (normalChunks ).thenAccept (b3 ->
514
542
// Complete the result now that all chunks have been scanned
515
543
result .complete (!chunksToCheck .isEmpty ()))))
516
544
)
@@ -591,4 +619,76 @@ public void tidyUp() {
591
619
boolean isNotZeroIsland () {
592
620
return !zeroIsland ;
593
621
}
622
+
623
+ public void scanIsland (Pipeliner pipeliner ) {
624
+ // Scan the next chunk
625
+ scanNextChunk ().thenAccept (r -> {
626
+ if (!Bukkit .isPrimaryThread ()) {
627
+ addon .getPlugin ().logError ("scanChunk not on Primary Thread!" );
628
+ }
629
+ // Timeout check
630
+ if (System .currentTimeMillis () - pipeliner .getInProcessQueue ().get (this ) > addon .getSettings ().getCalculationTimeout () * 60000 ) {
631
+ // Done
632
+ pipeliner .getInProcessQueue ().remove (this );
633
+ getR ().complete (new Results (Result .TIMEOUT ));
634
+ addon .logError ("Level calculation timed out after " + addon .getSettings ().getCalculationTimeout () + "m for island: " + getIsland ());
635
+ if (!isNotZeroIsland ()) {
636
+ addon .logError ("Island level was being zeroed." );
637
+ }
638
+ return ;
639
+ }
640
+ if (Boolean .TRUE .equals (r ) && !pipeliner .getTask ().isCancelled ()) {
641
+ // scanNextChunk returns true if there are more chunks to scan
642
+ scanIsland (pipeliner );
643
+ } else {
644
+ // Done
645
+ pipeliner .getInProcessQueue ().remove (this );
646
+ // Chunk finished
647
+ // This was the last chunk
648
+ handleStackedBlocks ();
649
+ handleChests ();
650
+ long checkTime = System .currentTimeMillis ();
651
+ finishTask = Bukkit .getScheduler ().runTaskTimer (addon .getPlugin (), () -> {
652
+ // Check every half second if all the chests and stacks have been cleared
653
+ if ((chestBlocks .isEmpty () && stackedBlocks .isEmpty ()) || System .currentTimeMillis () - checkTime > MAX_AMOUNT ) {
654
+ this .tidyUp ();
655
+ this .getR ().complete (getResults ());
656
+ finishTask .cancel ();
657
+ }
658
+ }, 0 , 10L );
659
+
660
+ }
661
+ });
662
+ }
663
+
664
+ private void handleChests () {
665
+ Iterator <Chunk > it = chestBlocks .iterator ();
666
+ while (it .hasNext ()) {
667
+ Chunk v = it .next ();
668
+ Util .getChunkAtAsync (v .getWorld (), v .getX (), v .getZ ()).thenAccept (c -> {
669
+ scanChests (c );
670
+ it .remove ();
671
+ });
672
+ }
673
+ }
674
+
675
+ private void handleStackedBlocks () {
676
+ // Deal with any stacked blocks
677
+ Iterator <Location > it = stackedBlocks .iterator ();
678
+ while (it .hasNext ()) {
679
+ Location v = it .next ();
680
+ Util .getChunkAtAsync (v ).thenAccept (c -> {
681
+ Block cauldronBlock = v .getBlock ();
682
+ boolean belowSeaLevel = seaHeight > 0 && v .getBlockY () <= seaHeight ;
683
+ if (WildStackerAPI .getWildStacker ().getSystemManager ().isStackedBarrel (cauldronBlock )) {
684
+ StackedBarrel barrel = WildStackerAPI .getStackedBarrel (cauldronBlock );
685
+ int barrelAmt = WildStackerAPI .getBarrelAmount (cauldronBlock );
686
+ for (int _x = 0 ; _x < barrelAmt ; _x ++) {
687
+ checkBlock (barrel .getType (), belowSeaLevel );
688
+ }
689
+ }
690
+ it .remove ();
691
+ });
692
+ }
693
+ }
594
694
}
0 commit comments