Isolate UltimateStacker imports so no errors if US is not installed (#307)

Fixes #306
This commit is contained in:
tastybento 2024-04-20 09:50:17 -07:00 committed by GitHub
parent 1bd6219f94
commit a4f8d12138
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 431 additions and 417 deletions

View File

@ -40,8 +40,6 @@ import com.bgsoftware.wildstacker.api.objects.StackedBarrel;
import com.google.common.collect.Multiset; import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry; import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Multisets; import com.google.common.collect.Multisets;
import com.craftaro.ultimatestacker.api.UltimateStackerApi;
import com.craftaro.ultimatestacker.api.utils.Stackable;
import dev.rosewood.rosestacker.api.RoseStackerAPI; import dev.rosewood.rosestacker.api.RoseStackerAPI;
import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI; import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI;
@ -58,13 +56,13 @@ public class IslandLevelCalculator {
private static final String LINE_BREAK = "=================================="; private static final String LINE_BREAK = "==================================";
public static final long MAX_AMOUNT = 10000000; public static final long MAX_AMOUNT = 10000000;
private static final List<Material> CHESTS = Arrays.asList(Material.CHEST, Material.CHEST_MINECART, private static final List<Material> CHESTS = Arrays.asList(Material.CHEST, Material.CHEST_MINECART,
Material.TRAPPED_CHEST, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX, Material.TRAPPED_CHEST, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX,
Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX,
Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX,
Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX, Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX,
Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.RED_SHULKER_BOX,
Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.COMPOSTER, Material.BARREL, Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.COMPOSTER, Material.BARREL,
Material.DISPENSER, Material.DROPPER, Material.SMOKER, Material.BLAST_FURNACE); Material.DISPENSER, Material.DROPPER, Material.SMOKER, Material.BLAST_FURNACE);
private static final int CHUNKS_TO_SCAN = 100; private static final int CHUNKS_TO_SCAN = 100;
private final Level addon; private final Level addon;
private final Queue<Pair<Integer, Integer>> chunksToCheck; private final Queue<Pair<Integer, Integer>> chunksToCheck;
@ -91,34 +89,34 @@ public class IslandLevelCalculator {
* @param zeroIsland - true if the calculation is due to an island zeroing * @param zeroIsland - true if the calculation is due to an island zeroing
*/ */
public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Results> r, boolean zeroIsland) { public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Results> r, boolean zeroIsland) {
this.addon = addon; this.addon = addon;
this.island = island; this.island = island;
this.r = r; this.r = r;
this.zeroIsland = zeroIsland; this.zeroIsland = zeroIsland;
results = new Results(); results = new Results();
duration = System.currentTimeMillis(); duration = System.currentTimeMillis();
chunksToCheck = getChunksToScan(island); chunksToCheck = getChunksToScan(island);
this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits()); this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits());
// Get the initial island level // Get the initial island level
results.initialLevel.set(addon.getInitialIslandLevel(island)); results.initialLevel.set(addon.getInitialIslandLevel(island));
// Set up the worlds // Set up the worlds
worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld())); worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld()));
// Nether // Nether
if (addon.getSettings().isNether()) { if (addon.getSettings().isNether()) {
World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
if (nether != null) { if (nether != null) {
worlds.put(Environment.NETHER, nether); worlds.put(Environment.NETHER, nether);
} }
} }
// End // End
if (addon.getSettings().isEnd()) { if (addon.getSettings().isEnd()) {
World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld());
if (end != null) { if (end != null) {
worlds.put(Environment.THE_END, end); worlds.put(Environment.THE_END, end);
} }
} }
// Sea Height // Sea Height
seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld()); seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld());
} }
/** /**
@ -128,18 +126,18 @@ public class IslandLevelCalculator {
* @return level of island * @return level of island
*/ */
private long calculateLevel(long blockAndDeathPoints) { private long calculateLevel(long blockAndDeathPoints) {
String calcString = addon.getSettings().getLevelCalc(); String calcString = addon.getSettings().getLevelCalc();
String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost",
String.valueOf(this.addon.getSettings().getLevelCost())); String.valueOf(this.addon.getSettings().getLevelCost()));
long evalWithValues; long evalWithValues;
try { try {
evalWithValues = (long) EquationEvaluator.eval(withValues); evalWithValues = (long) EquationEvaluator.eval(withValues);
return evalWithValues - (addon.getSettings().isZeroNewIslandLevels() ? results.initialLevel.get() : 0); return evalWithValues - (addon.getSettings().isZeroNewIslandLevels() ? results.initialLevel.get() : 0);
} catch (ParseException e) { } catch (ParseException e) {
addon.getPlugin().logStacktrace(e); addon.getPlugin().logStacktrace(e);
return 0L; return 0L;
} }
} }
/** /**
@ -150,14 +148,14 @@ public class IslandLevelCalculator {
* @param belowSeaLevel - true if below sea level * @param belowSeaLevel - true if below sea level
*/ */
private void checkBlock(Material mat, boolean belowSeaLevel) { private void checkBlock(Material mat, boolean belowSeaLevel) {
int count = limitCount(mat); int count = limitCount(mat);
if (belowSeaLevel) { if (belowSeaLevel) {
results.underWaterBlockCount.addAndGet(count); results.underWaterBlockCount.addAndGet(count);
results.uwCount.add(mat); results.uwCount.add(mat);
} else { } else {
results.rawBlockCount.addAndGet(count); results.rawBlockCount.addAndGet(count);
results.mdCount.add(mat); results.mdCount.add(mat);
} }
} }
/** /**
@ -167,22 +165,22 @@ public class IslandLevelCalculator {
* @return - set of pairs of x,z coordinates to check * @return - set of pairs of x,z coordinates to check
*/ */
private Queue<Pair<Integer, Integer>> getChunksToScan(Island island) { private Queue<Pair<Integer, Integer>> getChunksToScan(Island island) {
Queue<Pair<Integer, Integer>> chunkQueue = new ConcurrentLinkedQueue<>(); Queue<Pair<Integer, Integer>> chunkQueue = new ConcurrentLinkedQueue<>();
for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2
+ 16); x += 16) { + 16); x += 16) {
for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2
+ 16); z += 16) { + 16); z += 16) {
chunkQueue.add(new Pair<>(x >> 4, z >> 4)); chunkQueue.add(new Pair<>(x >> 4, z >> 4));
} }
} }
return chunkQueue; return chunkQueue;
} }
/** /**
* @return the island * @return the island
*/ */
public Island getIsland() { public Island getIsland() {
return island; return island;
} }
/** /**
@ -191,7 +189,7 @@ public class IslandLevelCalculator {
* @return the r * @return the r
*/ */
public CompletableFuture<Results> getR() { public CompletableFuture<Results> getR() {
return r; return r;
} }
/** /**
@ -200,67 +198,67 @@ public class IslandLevelCalculator {
* @return a list of lines * @return a list of lines
*/ */
private List<String> getReport() { private List<String> getReport() {
List<String> reportLines = new ArrayList<>(); List<String> reportLines = new ArrayList<>();
// provide counts // provide counts
reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld()) reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld())
+ " at " + Util.xyz(island.getCenter().toVector())); + " at " + Util.xyz(island.getCenter().toVector()));
reportLines.add("Island owner UUID = " + island.getOwner()); reportLines.add("Island owner UUID = " + island.getOwner());
reportLines.add("Total block value count = " + String.format("%,d", results.rawBlockCount.get())); reportLines.add("Total block value count = " + String.format("%,d", results.rawBlockCount.get()));
reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc()); reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc());
reportLines.add("Level cost = " + addon.getSettings().getLevelCost()); reportLines.add("Level cost = " + addon.getSettings().getLevelCost());
reportLines.add("Deaths handicap = " + results.deathHandicap.get()); reportLines.add("Deaths handicap = " + results.deathHandicap.get());
if (addon.getSettings().isZeroNewIslandLevels()) { if (addon.getSettings().isZeroNewIslandLevels()) {
reportLines.add("Initial island level = " + (0L - addon.getManager().getInitialLevel(island))); reportLines.add("Initial island level = " + (0L - addon.getManager().getInitialLevel(island)));
} }
reportLines.add("Previous level = " + addon.getManager().getIslandLevel(island.getWorld(), island.getOwner())); reportLines.add("Previous level = " + addon.getManager().getIslandLevel(island.getWorld(), island.getOwner()));
reportLines.add("New level = " + results.getLevel()); reportLines.add("New level = " + results.getLevel());
reportLines.add(LINE_BREAK); reportLines.add(LINE_BREAK);
int total = 0; int total = 0;
if (!results.uwCount.isEmpty()) { if (!results.uwCount.isEmpty()) {
reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier() reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier()
+ ") value"); + ") value");
reportLines.add("Total number of underwater blocks = " + String.format("%,d", results.uwCount.size())); reportLines.add("Total number of underwater blocks = " + String.format("%,d", results.uwCount.size()));
reportLines.addAll(sortedReport(total, results.uwCount)); reportLines.addAll(sortedReport(total, results.uwCount));
} }
reportLines.add("Regular block count"); reportLines.add("Regular block count");
reportLines.add("Total number of blocks = " + String.format("%,d", results.mdCount.size())); reportLines.add("Total number of blocks = " + String.format("%,d", results.mdCount.size()));
reportLines.addAll(sortedReport(total, results.mdCount)); reportLines.addAll(sortedReport(total, results.mdCount));
reportLines.add( reportLines.add(
"Blocks not counted because they exceeded limits: " + String.format("%,d", results.ofCount.size())); "Blocks not counted because they exceeded limits: " + String.format("%,d", results.ofCount.size()));
Iterable<Multiset.Entry<Material>> entriesSortedByCount = results.ofCount.entrySet(); Iterable<Multiset.Entry<Material>> entriesSortedByCount = results.ofCount.entrySet();
Iterator<Entry<Material>> it = entriesSortedByCount.iterator(); Iterator<Entry<Material>> it = entriesSortedByCount.iterator();
while (it.hasNext()) { while (it.hasNext()) {
Entry<Material> type = it.next(); Entry<Material> type = it.next();
Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement()); Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement());
String explain = ")"; String explain = ")";
if (limit == null) { if (limit == null) {
Material generic = type.getElement(); Material generic = type.getElement();
limit = addon.getBlockConfig().getBlockLimits().get(generic); limit = addon.getBlockConfig().getBlockLimits().get(generic);
explain = " - All types)"; explain = " - All types)";
} }
reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount())
+ " blocks (max " + limit + explain); + " blocks (max " + limit + explain);
} }
reportLines.add(LINE_BREAK); reportLines.add(LINE_BREAK);
reportLines.add("Blocks on island that are not in config.yml"); reportLines.add("Blocks on island that are not in config.yml");
reportLines.add("Total number = " + String.format("%,d", results.ncCount.size())); reportLines.add("Total number = " + String.format("%,d", results.ncCount.size()));
entriesSortedByCount = results.ncCount.entrySet(); entriesSortedByCount = results.ncCount.entrySet();
it = entriesSortedByCount.iterator(); it = entriesSortedByCount.iterator();
while (it.hasNext()) { while (it.hasNext()) {
Entry<Material> type = it.next(); Entry<Material> type = it.next();
reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) + " blocks"); reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) + " blocks");
} }
reportLines.add(LINE_BREAK); reportLines.add(LINE_BREAK);
return reportLines; return reportLines;
} }
/** /**
* @return the results * @return the results
*/ */
public Results getResults() { public Results getResults() {
return results; return results;
} }
/** /**
@ -270,13 +268,13 @@ public class IslandLevelCalculator {
* @return value of a material * @return value of a material
*/ */
private int getValue(Material md) { private int getValue(Material md) {
Integer value = addon.getBlockConfig().getValue(island.getWorld(), md); Integer value = addon.getBlockConfig().getValue(island.getWorld(), md);
if (value == null) { if (value == null) {
// Not in config // Not in config
results.ncCount.add(md); results.ncCount.add(md);
return 0; return 0;
} }
return value; return value;
} }
/** /**
@ -288,44 +286,44 @@ public class IslandLevelCalculator {
* there is no island nether * there is no island nether
*/ */
private CompletableFuture<List<Chunk>> getWorldChunk(Environment env, Queue<Pair<Integer, Integer>> pairList) { private CompletableFuture<List<Chunk>> getWorldChunk(Environment env, Queue<Pair<Integer, Integer>> pairList) {
if (worlds.containsKey(env)) { if (worlds.containsKey(env)) {
CompletableFuture<List<Chunk>> r2 = new CompletableFuture<>(); CompletableFuture<List<Chunk>> r2 = new CompletableFuture<>();
List<Chunk> chunkList = new ArrayList<>(); List<Chunk> chunkList = new ArrayList<>();
World world = worlds.get(env); World world = worlds.get(env);
// Get the chunk, and then coincidentally check the RoseStacker // Get the chunk, and then coincidentally check the RoseStacker
loadChunks(r2, world, pairList, chunkList); loadChunks(r2, world, pairList, chunkList);
return r2; return r2;
} }
return CompletableFuture.completedFuture(Collections.emptyList()); return CompletableFuture.completedFuture(Collections.emptyList());
} }
private void loadChunks(CompletableFuture<List<Chunk>> r2, World world, Queue<Pair<Integer, Integer>> pairList, private void loadChunks(CompletableFuture<List<Chunk>> r2, World world, Queue<Pair<Integer, Integer>> pairList,
List<Chunk> chunkList) { List<Chunk> chunkList) {
if (pairList.isEmpty()) { if (pairList.isEmpty()) {
r2.complete(chunkList); r2.complete(chunkList);
return; return;
} }
Pair<Integer, Integer> p = pairList.poll(); Pair<Integer, Integer> p = pairList.poll();
Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> {
if (chunk != null) { if (chunk != null) {
chunkList.add(chunk); chunkList.add(chunk);
roseStackerCheck(chunk); roseStackerCheck(chunk);
} }
loadChunks(r2, world, pairList, chunkList); // Iteration loadChunks(r2, world, pairList, chunkList); // Iteration
}); });
} }
private void roseStackerCheck(Chunk chunk) { private void roseStackerCheck(Chunk chunk) {
if (addon.isRoseStackersEnabled()) { if (addon.isRoseStackersEnabled()) {
RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> {
// Blocks below sea level can be scored differently // Blocks below sea level can be scored differently
boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight; boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight;
// Check block once because the base block will be counted in the chunk snapshot // Check block once because the base block will be counted in the chunk snapshot
for (int _x = 0; _x < e.getStackSize() - 1; _x++) { for (int _x = 0; _x < e.getStackSize() - 1; _x++) {
checkBlock(e.getBlock().getType(), belowSeaLevel); checkBlock(e.getBlock().getType(), belowSeaLevel);
} }
}); });
} }
} }
/** /**
@ -336,17 +334,17 @@ public class IslandLevelCalculator {
* @return value of the block if can be counted * @return value of the block if can be counted
*/ */
private int limitCount(Material md) { private int limitCount(Material md) {
if (limitCount.containsKey(md)) { if (limitCount.containsKey(md)) {
int count = limitCount.get(md); int count = limitCount.get(md);
if (count > 0) { if (count > 0) {
limitCount.put(md, --count); limitCount.put(md, --count);
return getValue(md); return getValue(md);
} else { } else {
results.ofCount.add(md); results.ofCount.add(md);
return 0; return 0;
} }
} }
return getValue(md); return getValue(md);
} }
/** /**
@ -355,39 +353,39 @@ public class IslandLevelCalculator {
* @param chunk - the chunk to scan * @param chunk - the chunk to scan
*/ */
private void scanChests(Chunk chunk) { private void scanChests(Chunk chunk) {
// Count blocks in chests // Count blocks in chests
for (BlockState bs : chunk.getTileEntities()) { for (BlockState bs : chunk.getTileEntities()) {
if (bs instanceof Container container) { if (bs instanceof Container container) {
if (addon.isAdvChestEnabled()) { if (addon.isAdvChestEnabled()) {
AdvancedChest<?, ?> aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation()); AdvancedChest<?, ?> aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation());
if (aChest != null && aChest.getChestType().getName().equals("NORMAL")) { if (aChest != null && aChest.getChestType().getName().equals("NORMAL")) {
aChest.getPages().stream().map(ChestPage::getItems).forEach(c -> { aChest.getPages().stream().map(ChestPage::getItems).forEach(c -> {
for (Object i : c) { for (Object i : c) {
countItemStack((ItemStack) i); countItemStack((ItemStack) i);
} }
}); });
continue; continue;
} }
} }
// Regular chest // Regular chest
container.getSnapshotInventory().forEach(this::countItemStack); container.getSnapshotInventory().forEach(this::countItemStack);
} }
} }
} }
private void countItemStack(ItemStack i) { private void countItemStack(ItemStack i) {
if (i == null || !i.getType().isBlock()) if (i == null || !i.getType().isBlock())
return; return;
for (int c = 0; c < i.getAmount(); c++) { for (int c = 0; c < i.getAmount(); c++) {
if (addon.getSettings().isIncludeShulkersInChest() if (addon.getSettings().isIncludeShulkersInChest()
&& i.getItemMeta() instanceof BlockStateMeta blockStateMeta && i.getItemMeta() instanceof BlockStateMeta blockStateMeta
&& blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox) { && blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox) {
shulkerBox.getSnapshotInventory().forEach(this::countItemStack); shulkerBox.getSnapshotInventory().forEach(this::countItemStack);
} }
checkBlock(i.getType(), false); checkBlock(i.getType(), false);
} }
} }
/** /**
@ -400,26 +398,26 @@ public class IslandLevelCalculator {
* that will be true if the scan was successful, false if not * that will be true if the scan was successful, false if not
*/ */
private CompletableFuture<Boolean> scanChunk(List<Chunk> chunks) { private CompletableFuture<Boolean> scanChunk(List<Chunk> chunks) {
// If the chunk hasn't been generated, return // If the chunk hasn't been generated, return
if (chunks == null || chunks.isEmpty()) { if (chunks == null || chunks.isEmpty()) {
return CompletableFuture.completedFuture(false); return CompletableFuture.completedFuture(false);
} }
// Count blocks in chunk // Count blocks in chunk
CompletableFuture<Boolean> result = new CompletableFuture<>(); CompletableFuture<Boolean> result = new CompletableFuture<>();
/* /*
* At this point, we need to grab a snapshot of each chunk and then scan it * At this point, we need to grab a snapshot of each chunk and then scan it
* async. At the end, we make the CompletableFuture true to show it is done. I'm * async. At the end, we make the CompletableFuture true to show it is done. I'm
* not sure how much lag this will cause, but as all the chunks are loaded, * not sure how much lag this will cause, but as all the chunks are loaded,
* maybe not that much. * maybe not that much.
*/ */
List<ChunkPair> preLoad = chunks.stream().map(c -> new ChunkPair(c.getWorld(), c, c.getChunkSnapshot())) List<ChunkPair> preLoad = chunks.stream().map(c -> new ChunkPair(c.getWorld(), c, c.getChunkSnapshot()))
.toList(); .toList();
Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> { Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> {
preLoad.forEach(this::scanAsync); preLoad.forEach(this::scanAsync);
// Once they are all done, return to the main thread. // Once they are all done, return to the main thread.
Bukkit.getScheduler().runTask(addon.getPlugin(), () -> result.complete(true)); Bukkit.getScheduler().runTask(addon.getPlugin(), () -> result.complete(true));
}); });
return result; return result;
} }
record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) { record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {
@ -431,66 +429,53 @@ public class IslandLevelCalculator {
* @param cp chunk to scan * @param cp chunk to scan
*/ */
private void scanAsync(ChunkPair cp) { private void scanAsync(ChunkPair cp) {
for (int x = 0; x < 16; x++) { for (int x = 0; x < 16; x++) {
// Check if the block coordinate is inside the protection zone and if not, don't // Check if the block coordinate is inside the protection zone and if not, don't
// count it // count it
if (cp.chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || cp.chunkSnapshot.getX() * 16 if (cp.chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || cp.chunkSnapshot.getX() * 16
+ x >= island.getMinProtectedX() + island.getProtectionRange() * 2) { + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) {
continue; continue;
} }
for (int z = 0; z < 16; z++) { for (int z = 0; z < 16; z++) {
// Check if the block coordinate is inside the protection zone and if not, don't // Check if the block coordinate is inside the protection zone and if not, don't
// count it // count it
if (cp.chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || cp.chunkSnapshot.getZ() * 16 if (cp.chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || cp.chunkSnapshot.getZ() * 16
+ z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) { + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
continue; continue;
} }
// Only count to the highest block in the world for some optimization // Only count to the highest block in the world for some optimization
for (int y = cp.world.getMinHeight(); y < cp.world.getMaxHeight(); y++) { for (int y = cp.world.getMinHeight(); y < cp.world.getMaxHeight(); y++) {
BlockData blockData = cp.chunkSnapshot.getBlockData(x, y, z); BlockData blockData = cp.chunkSnapshot.getBlockData(x, y, z);
boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight; Material m = blockData.getMaterial();
// Slabs can be doubled, so check them twice boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight;
if (Tag.SLABS.isTagged(blockData.getMaterial())) { // Slabs can be doubled, so check them twice
Slab slab = (Slab) blockData; if (Tag.SLABS.isTagged(m)) {
if (slab.getType().equals(Slab.Type.DOUBLE)) { Slab slab = (Slab) blockData;
checkBlock(blockData.getMaterial(), belowSeaLevel); if (slab.getType().equals(Slab.Type.DOUBLE)) {
} checkBlock(m, belowSeaLevel);
} }
// Hook for Wild Stackers (Blocks and Spawners Only) - this has to use the real }
// chunk // Hook for Wild Stackers (Blocks and Spawners Only) - this has to use the real
if (addon.isStackersEnabled() && (blockData.getMaterial().equals(Material.CAULDRON) // chunk
|| blockData.getMaterial().equals(Material.SPAWNER))) { if (addon.isStackersEnabled() && (m.equals(Material.CAULDRON) || m.equals(Material.SPAWNER))) {
stackedBlocks.add(new Location(cp.world, (double) x + cp.chunkSnapshot.getX() * 16, y, stackedBlocks.add(new Location(cp.world, (double) x + cp.chunkSnapshot.getX() * 16, y,
(double) z + cp.chunkSnapshot.getZ() * 16)); (double) z + cp.chunkSnapshot.getZ() * 16));
} }
Block block = cp.chunk.getBlock(x, y, z); if (addon.isUltimateStackerEnabled() && !m.isAir()) {
Location l = new Location(cp.chunk.getWorld(), x, y, z);
UltimateStackerCalc.addStackers(m, l, results, belowSeaLevel, limitCount(m));
}
if (addon.isUltimateStackerEnabled()) { // Scan chests
if (!blockData.getMaterial().equals(Material.AIR)) { if (addon.getSettings().isIncludeChests() && CHESTS.contains(m)) {
Stackable stack = UltimateStackerApi.getBlockStackManager().getBlock(block.getLocation()); chestBlocks.add(cp.chunk);
if (stack != null) { }
int value = limitCount(blockData.getMaterial()); // Add the value of the block's material
if (belowSeaLevel) { checkBlock(m, belowSeaLevel);
results.underWaterBlockCount.addAndGet((long) stack.getAmount() * value); }
results.uwCount.add(blockData.getMaterial()); }
} else { }
results.rawBlockCount.addAndGet((long) stack.getAmount() * value);
results.mdCount.add(blockData.getMaterial());
}
}
}
}
// Scan chests
if (addon.getSettings().isIncludeChests() && CHESTS.contains(blockData.getMaterial())) {
chestBlocks.add(cp.chunk);
}
// Add the value of the block's material
checkBlock(blockData.getMaterial(), belowSeaLevel);
}
}
}
} }
/** /**
@ -500,180 +485,180 @@ public class IslandLevelCalculator {
* to be scanned, and false if not * to be scanned, and false if not
*/ */
public CompletableFuture<Boolean> scanNextChunk() { public CompletableFuture<Boolean> scanNextChunk() {
if (chunksToCheck.isEmpty()) { if (chunksToCheck.isEmpty()) {
addon.logError("Unexpected: no chunks to scan!"); addon.logError("Unexpected: no chunks to scan!");
// This should not be needed, but just in case // This should not be needed, but just in case
return CompletableFuture.completedFuture(false); return CompletableFuture.completedFuture(false);
} }
// Retrieve and remove from the queue // Retrieve and remove from the queue
Queue<Pair<Integer, Integer>> pairList = new ConcurrentLinkedQueue<>(); Queue<Pair<Integer, Integer>> pairList = new ConcurrentLinkedQueue<>();
int i = 0; int i = 0;
while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) { while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) {
pairList.add(chunksToCheck.poll()); pairList.add(chunksToCheck.poll());
} }
Queue<Pair<Integer, Integer>> endPairList = new ConcurrentLinkedQueue<>(pairList); Queue<Pair<Integer, Integer>> endPairList = new ConcurrentLinkedQueue<>(pairList);
Queue<Pair<Integer, Integer>> netherPairList = new ConcurrentLinkedQueue<>(pairList); Queue<Pair<Integer, Integer>> netherPairList = new ConcurrentLinkedQueue<>(pairList);
// Set up the result // Set up the result
CompletableFuture<Boolean> result = new CompletableFuture<>(); CompletableFuture<Boolean> result = new CompletableFuture<>();
// Get chunks and scan // Get chunks and scan
// Get chunks and scan // Get chunks and scan
getWorldChunk(Environment.THE_END, endPairList).thenAccept( getWorldChunk(Environment.THE_END, endPairList).thenAccept(
endChunks -> scanChunk(endChunks).thenAccept(b -> getWorldChunk(Environment.NETHER, netherPairList) endChunks -> scanChunk(endChunks).thenAccept(b -> getWorldChunk(Environment.NETHER, netherPairList)
.thenAccept(netherChunks -> scanChunk(netherChunks) .thenAccept(netherChunks -> scanChunk(netherChunks)
.thenAccept(b2 -> getWorldChunk(Environment.NORMAL, pairList) .thenAccept(b2 -> getWorldChunk(Environment.NORMAL, pairList)
.thenAccept(normalChunks -> scanChunk(normalChunks).thenAccept(b3 -> .thenAccept(normalChunks -> scanChunk(normalChunks).thenAccept(b3 ->
// Complete the result now that all chunks have been scanned // Complete the result now that all chunks have been scanned
result.complete(!chunksToCheck.isEmpty()))))))); result.complete(!chunksToCheck.isEmpty())))))));
return result; return result;
} }
private Collection<String> sortedReport(int total, Multiset<Material> materialCount) { private Collection<String> sortedReport(int total, Multiset<Material> materialCount) {
Collection<String> result = new ArrayList<>(); Collection<String> result = new ArrayList<>();
Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount) Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount)
.entrySet(); .entrySet();
for (Entry<Material> en : entriesSortedByCount) { for (Entry<Material> en : entriesSortedByCount) {
Material type = en.getElement(); Material type = en.getElement();
int value = getValue(type); int value = getValue(type);
result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = " result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = "
+ (value * en.getCount())); + (value * en.getCount()));
total += (value * en.getCount()); total += (value * en.getCount());
} }
result.add("Subtotal = " + total); result.add("Subtotal = " + total);
result.add(LINE_BREAK); result.add(LINE_BREAK);
return result; return result;
} }
/** /**
* Finalizes the calculations and makes the report * Finalizes the calculations and makes the report
*/ */
public void tidyUp() { public void tidyUp() {
// Finalize calculations // Finalize calculations
results.rawBlockCount results.rawBlockCount
.addAndGet((long) (results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier())); .addAndGet((long) (results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
// Set the death penalty // Set the death penalty
if (this.addon.getSettings().isSumTeamDeaths()) { if (this.addon.getSettings().isSumTeamDeaths()) {
for (UUID uuid : this.island.getMemberSet()) { for (UUID uuid : this.island.getMemberSet()) {
this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid)); this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid));
} }
} else { } else {
// At this point, it may be that the island has become unowned. // At this point, it may be that the island has become unowned.
this.results.deathHandicap.set(this.island.getOwner() == null ? 0 this.results.deathHandicap.set(this.island.getOwner() == null ? 0
: this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner())); : this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner()));
} }
long blockAndDeathPoints = this.results.rawBlockCount.get(); long blockAndDeathPoints = this.results.rawBlockCount.get();
this.results.totalPoints.set(blockAndDeathPoints); this.results.totalPoints.set(blockAndDeathPoints);
if (this.addon.getSettings().getDeathPenalty() > 0) { if (this.addon.getSettings().getDeathPenalty() > 0) {
// Proper death penalty calculation. // Proper death penalty calculation.
blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty(); blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty();
} }
this.results.level.set(calculateLevel(blockAndDeathPoints)); this.results.level.set(calculateLevel(blockAndDeathPoints));
// Calculate how many points are required to get to the next level // Calculate how many points are required to get to the next level
long nextLevel = this.results.level.get(); long nextLevel = this.results.level.get();
long blocks = blockAndDeathPoints; long blocks = blockAndDeathPoints;
while (nextLevel < this.results.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) { while (nextLevel < this.results.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) {
nextLevel = calculateLevel(++blocks); nextLevel = calculateLevel(++blocks);
} }
this.results.pointsToNextLevel.set(blocks - blockAndDeathPoints); this.results.pointsToNextLevel.set(blocks - blockAndDeathPoints);
// Report // Report
results.report = getReport(); results.report = getReport();
// Set the duration // Set the duration
addon.getPipeliner().setTime(System.currentTimeMillis() - duration); addon.getPipeliner().setTime(System.currentTimeMillis() - duration);
// All done. // All done.
} }
/** /**
* @return the zeroIsland * @return the zeroIsland
*/ */
boolean isNotZeroIsland() { boolean isNotZeroIsland() {
return !zeroIsland; return !zeroIsland;
} }
public void scanIsland(Pipeliner pipeliner) { public void scanIsland(Pipeliner pipeliner) {
// Scan the next chunk // Scan the next chunk
scanNextChunk().thenAccept(result -> { scanNextChunk().thenAccept(result -> {
if (!Bukkit.isPrimaryThread()) { if (!Bukkit.isPrimaryThread()) {
addon.getPlugin().logError("scanChunk not on Primary Thread!"); addon.getPlugin().logError("scanChunk not on Primary Thread!");
} }
// Timeout check // Timeout check
if (System.currentTimeMillis() if (System.currentTimeMillis()
- pipeliner.getInProcessQueue().get(this) > addon.getSettings().getCalculationTimeout() * 60000) { - pipeliner.getInProcessQueue().get(this) > addon.getSettings().getCalculationTimeout() * 60000) {
// Done // Done
pipeliner.getInProcessQueue().remove(this); pipeliner.getInProcessQueue().remove(this);
getR().complete(new Results(Result.TIMEOUT)); getR().complete(new Results(Result.TIMEOUT));
addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout() addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout()
+ "m for island: " + getIsland()); + "m for island: " + getIsland());
if (!isNotZeroIsland()) { if (!isNotZeroIsland()) {
addon.logError("Island level was being zeroed."); addon.logError("Island level was being zeroed.");
} }
return; return;
} }
if (Boolean.TRUE.equals(result) && !pipeliner.getTask().isCancelled()) { if (Boolean.TRUE.equals(result) && !pipeliner.getTask().isCancelled()) {
// scanNextChunk returns true if there are more chunks to scan // scanNextChunk returns true if there are more chunks to scan
scanIsland(pipeliner); scanIsland(pipeliner);
} else { } else {
// Done // Done
pipeliner.getInProcessQueue().remove(this); pipeliner.getInProcessQueue().remove(this);
// Chunk finished // Chunk finished
// This was the last chunk // This was the last chunk
handleStackedBlocks(); handleStackedBlocks();
handleChests(); handleChests();
long checkTime = System.currentTimeMillis(); long checkTime = System.currentTimeMillis();
finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> { finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> {
// Check every half second if all the chests and stacks have been cleared // Check every half second if all the chests and stacks have been cleared
if ((chestBlocks.isEmpty() && stackedBlocks.isEmpty()) if ((chestBlocks.isEmpty() && stackedBlocks.isEmpty())
|| System.currentTimeMillis() - checkTime > MAX_AMOUNT) { || System.currentTimeMillis() - checkTime > MAX_AMOUNT) {
this.tidyUp(); this.tidyUp();
this.getR().complete(getResults()); this.getR().complete(getResults());
finishTask.cancel(); finishTask.cancel();
} }
}, 0, 10L); }, 0, 10L);
} }
}); });
} }
private void handleChests() { private void handleChests() {
Iterator<Chunk> it = chestBlocks.iterator(); Iterator<Chunk> it = chestBlocks.iterator();
while (it.hasNext()) { while (it.hasNext()) {
Chunk v = it.next(); Chunk v = it.next();
Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> { Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> {
scanChests(c); scanChests(c);
it.remove(); it.remove();
}); });
} }
} }
private void handleStackedBlocks() { private void handleStackedBlocks() {
// Deal with any stacked blocks // Deal with any stacked blocks
Iterator<Location> it = stackedBlocks.iterator(); Iterator<Location> it = stackedBlocks.iterator();
while (it.hasNext()) { while (it.hasNext()) {
Location v = it.next(); Location v = it.next();
Util.getChunkAtAsync(v).thenAccept(c -> { Util.getChunkAtAsync(v).thenAccept(c -> {
Block stackedBlock = v.getBlock(); Block stackedBlock = v.getBlock();
boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight;
if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) {
StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock);
int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock);
for (int _x = 0; _x < barrelAmt; _x++) { for (int _x = 0; _x < barrelAmt; _x++) {
checkBlock(barrel.getType(), belowSeaLevel); checkBlock(barrel.getType(), belowSeaLevel);
} }
} else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) {
int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState());
for (int _x = 0; _x < spawnerAmt; _x++) { for (int _x = 0; _x < spawnerAmt; _x++) {
checkBlock(stackedBlock.getType(), belowSeaLevel); checkBlock(stackedBlock.getType(), belowSeaLevel);
} }
} }
it.remove(); it.remove();
}); });
} }
} }
} }

View File

@ -0,0 +1,29 @@
package world.bentobox.level.calculators;
import org.bukkit.Location;
import org.bukkit.Material;
import com.craftaro.ultimatestacker.api.UltimateStackerApi;
import com.craftaro.ultimatestacker.api.utils.Stackable;
import world.bentobox.bentobox.BentoBox;
/**
* Isolates UltimateStacker imports so that they are only loaded if the plugin exists
*/
public class UltimateStackerCalc {
public static void addStackers(Material material, Location location, Results results, boolean belowSeaLevel,
int value) {
Stackable stack = UltimateStackerApi.getBlockStackManager().getBlock(location);
if (stack != null) {
if (belowSeaLevel) {
results.underWaterBlockCount.addAndGet((long) stack.getAmount() * value);
results.uwCount.add(material);
} else {
results.rawBlockCount.addAndGet((long) stack.getAmount() * value);
results.mdCount.add(material);
}
}
}
}