Move to 1.20.4

Refactored the calculator code for clarity.

Added Jacoco line to prvent issues with the bigger Material class.
This commit is contained in:
tastybento 2023-12-10 10:06:33 -08:00
parent cfb35909f0
commit cec620162b
3 changed files with 585 additions and 527 deletions

View File

@ -58,7 +58,7 @@
<!-- Non-minecraft related dependencies --> <!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version> <powermock.version>2.0.9</powermock.version>
<!-- More visible way how to change dependency versions --> <!-- More visible way how to change dependency versions -->
<spigot.version>1.19.4-R0.1-SNAPSHOT</spigot.version> <spigot.version>1.20.4-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>2.0.0-SNAPSHOT</bentobox.version> <bentobox.version>2.0.0-SNAPSHOT</bentobox.version>
<!-- Warps addon version --> <!-- Warps addon version -->
<warps.version>1.12.0</warps.version> <warps.version>1.12.0</warps.version>
@ -410,6 +410,8 @@
<!-- This is required to prevent Jacoco from adding <!-- This is required to prevent Jacoco from adding
synthetic fields to a JavaBean class (causes errors in testing) --> synthetic fields to a JavaBean class (causes errors in testing) -->
<exclude>**/*Names*</exclude> <exclude>**/*Names*</exclude>
<!-- Prevents the Material is too large to mock error -->
<exclude>org/bukkit/Material*</exclude>
</excludes> </excludes>
</configuration> </configuration>
<executions> <executions>

View File

@ -0,0 +1,121 @@
package world.bentobox.level.calculators;
import java.text.ParseException;
/**
* @author tastybento
*/
public class EquationEvaluator {
private static class Parser {
private final String input;
private int pos = -1;
private int currentChar;
public Parser(String input) {
this.input = input;
moveToNextChar();
}
private void moveToNextChar() {
currentChar = (++pos < input.length()) ? input.charAt(pos) : -1;
}
private boolean tryToEat(int charToEat) {
while (currentChar == ' ')
moveToNextChar();
if (currentChar == charToEat) {
moveToNextChar();
return true;
}
return false;
}
public double evaluate() throws ParseException {
double result = parseExpression();
if (pos < input.length()) {
throw new ParseException("Unexpected character: " + (char) currentChar, pos);
}
return result;
}
private double parseExpression() throws ParseException {
double result = parseTerm();
while (true) {
if (tryToEat('+'))
result += parseTerm();
else if (tryToEat('-'))
result -= parseTerm();
else
return result;
}
}
private double parseFactor() throws ParseException {
if (tryToEat('+'))
return parseFactor(); // unary plus
if (tryToEat('-'))
return -parseFactor(); // unary minus
double x;
int startPos = this.pos;
if (tryToEat('(')) { // parentheses
x = parseExpression();
tryToEat(')');
} else if ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') { // numbers
while ((currentChar >= '0' && currentChar <= '9') || currentChar == '.')
moveToNextChar();
x = Double.parseDouble(input.substring(startPos, this.pos));
} else if (currentChar >= 'a' && currentChar <= 'z') { // functions
while (currentChar >= 'a' && currentChar <= 'z')
moveToNextChar();
String func = input.substring(startPos, this.pos);
x = parseFactor();
switch (func) {
case "sqrt":
x = Math.sqrt(x);
break;
case "sin":
x = Math.sin(Math.toRadians(x));
break;
case "cos":
x = Math.cos(Math.toRadians(x));
break;
case "tan":
x = Math.tan(Math.toRadians(x));
break;
case "log":
x = Math.log(x);
break;
default:
throw new ParseException("Unknown function: " + func, startPos);
}
} else {
throw new ParseException("Unexpected: " + (char) currentChar, startPos);
}
if (tryToEat('^'))
x = Math.pow(x, parseFactor()); // exponentiation
return x;
}
private double parseTerm() throws ParseException {
double x = parseFactor();
for (;;) {
if (tryToEat('*'))
x *= parseFactor(); // multiplication
else if (tryToEat('/'))
x /= parseFactor(); // division
else
return x;
}
}
}
public static double eval(final String equation) throws ParseException {
return new Parser(equation).evaluate();
}
}

View File

@ -1,6 +1,6 @@
package world.bentobox.level.calculators; package world.bentobox.level.calculators;
import java.io.IOException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -16,9 +16,6 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.core.compatibility.CompatibleMaterial;
import com.songoda.ultimatestacker.stackable.block.BlockStack;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot; import org.bukkit.ChunkSnapshot;
@ -27,7 +24,11 @@ import org.bukkit.Material;
import org.bukkit.Tag; import org.bukkit.Tag;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.World.Environment; import org.bukkit.World.Environment;
import org.bukkit.block.*; import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Container;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.ShulkerBox;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Slab; import org.bukkit.block.data.type.Slab;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -39,6 +40,9 @@ 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.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.core.compatibility.CompatibleMaterial;
import com.songoda.ultimatestacker.stackable.block.BlockStack;
import dev.rosewood.rosestacker.api.RoseStackerAPI; import dev.rosewood.rosestacker.api.RoseStackerAPI;
import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI; import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI;
@ -54,121 +58,21 @@ import world.bentobox.level.calculators.Results.Result;
public class IslandLevelCalculator { 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, Material.TRAPPED_CHEST, private static final List<Material> CHESTS = Arrays.asList(Material.CHEST, Material.CHEST_MINECART,
Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX, Material.BROWN_SHULKER_BOX, Material.TRAPPED_CHEST, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX,
Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX,
Material.LIGHT_GRAY_SHULKER_BOX, Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_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.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.DISPENSER, Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.COMPOSTER, Material.BARREL,
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;
/**
* Method to evaluate a mathematical equation
* @param str - equation to evaluate
* @return value of equation
*/
private static double eval(final String str) throws IOException {
return new Object() {
int pos = -1;
int ch;
boolean eat(int charToEat) {
while (ch == ' ') nextChar();
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
void nextChar() {
ch = (++pos < str.length()) ? str.charAt(pos) : -1;
}
double parse() throws IOException {
nextChar();
double x = parseExpression();
if (pos < str.length()) throw new IOException("Unexpected: " + (char)ch);
return x;
}
// Grammar:
// expression = term | expression `+` term | expression `-` term
// term = factor | term `*` factor | term `/` factor
// factor = `+` factor | `-` factor | `(` expression `)`
// | number | functionName factor | factor `^` factor
double parseExpression() throws IOException {
double x = parseTerm();
for (;;) {
if (eat('+')) x += parseTerm(); // addition
else if (eat('-')) x -= parseTerm(); // subtraction
else return x;
}
}
double parseFactor() throws IOException {
if (eat('+')) return parseFactor(); // unary plus
if (eat('-')) return -parseFactor(); // unary minus
double x;
int startPos = this.pos;
if (eat('(')) { // parentheses
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
x = Double.parseDouble(str.substring(startPos, this.pos));
} else if (ch >= 'a' && ch <= 'z') { // functions
while (ch >= 'a' && ch <= 'z') nextChar();
String func = str.substring(startPos, this.pos);
x = parseFactor();
switch (func) {
case "sqrt":
x = Math.sqrt(x);
break;
case "sin":
x = Math.sin(Math.toRadians(x));
break;
case "cos":
x = Math.cos(Math.toRadians(x));
break;
case "tan":
x = Math.tan(Math.toRadians(x));
break;
case "log":
x = Math.log(x);
break;
default:
throw new IOException("Unknown function: " + func);
}
} else {
throw new IOException("Unexpected: " + (char)ch);
}
if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation
return x;
}
double parseTerm() throws IOException {
double x = parseFactor();
for (;;) {
if (eat('*')) x *= parseFactor(); // multiplication
else if (eat('/')) x /= parseFactor(); // division
else return x;
}
}
}.parse();
}
private final Level addon; private final Level addon;
private final Queue<Pair<Integer, Integer>> chunksToCheck; private final Queue<Pair<Integer, Integer>> chunksToCheck;
private final Island island; private final Island island;
private final Map<Material, Integer> limitCount; private final Map<Material, Integer> limitCount;
private final CompletableFuture<Results> r; private final CompletableFuture<Results> r;
private final Results results; private final Results results;
private long duration; private long duration;
private final boolean zeroIsland; private final boolean zeroIsland;
@ -178,12 +82,13 @@ public class IslandLevelCalculator {
private final Set<Chunk> chestBlocks = new HashSet<>(); private final Set<Chunk> chestBlocks = new HashSet<>();
private BukkitTask finishTask; private BukkitTask finishTask;
/** /**
* Constructor to get the level for an island * Constructor to get the level for an island
*
* @param addon - Level addon * @param addon - Level addon
* @param island - the island to scan * @param island - the island to scan
* @param r - completable result that will be completed when the calculation is complete * @param r - completable result that will be completed when the
* calculation is complete
* @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) {
@ -219,25 +124,29 @@ public class IslandLevelCalculator {
/** /**
* Calculate the level based on the raw points * Calculate the level based on the raw points
*
* @param blockAndDeathPoints - raw points counted on island * @param blockAndDeathPoints - raw points counted on island
* @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.valueOf(this.addon.getSettings().getLevelCost())); String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost",
String.valueOf(this.addon.getSettings().getLevelCost()));
long evalWithValues; long evalWithValues;
try { try {
evalWithValues = (long)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 (IOException e) { } catch (ParseException e) {
addon.getPlugin().logStacktrace(e); addon.getPlugin().logStacktrace(e);
return 0L; return 0L;
} }
} }
/** /**
* Adds value to the results based on the material and whether the block is below sea level or not * Adds value to the results based on the material and whether the block is
* below sea level or not
*
* @param mat - material of the block * @param mat - material of the block
* @param belowSeaLevel - true if below sea level * @param belowSeaLevel - true if below sea level
*/ */
@ -254,20 +163,22 @@ public class IslandLevelCalculator {
/** /**
* Get a set of all the chunks in island * Get a set of all the chunks in island
*
* @param island - island * @param island - island
* @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 + 16); x += 16) { for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2
for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 + 16); z += 16) { + 16); x += 16) {
for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2
+ 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
*/ */
@ -277,6 +188,7 @@ public class IslandLevelCalculator {
/** /**
* Get the completable result for this calculation * Get the completable result for this calculation
*
* @return the r * @return the r
*/ */
public CompletableFuture<Results> getR() { public CompletableFuture<Results> getR() {
@ -285,12 +197,14 @@ public class IslandLevelCalculator {
/** /**
* Get the full analysis report * Get the full analysis report
*
* @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()) + " at " + Util.xyz(island.getCenter().toVector())); reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld())
+ " 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());
@ -304,7 +218,8 @@ public class IslandLevelCalculator {
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() + ") value"); reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier()
+ ") 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));
} }
@ -312,7 +227,8 @@ public class IslandLevelCalculator {
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("Blocks not counted because they exceeded limits: " + String.format("%,d",results.ofCount.size())); reportLines.add(
"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()) {
@ -324,7 +240,8 @@ public class IslandLevelCalculator {
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()) + " blocks (max " + limit + explain); reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount())
+ " 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");
@ -346,9 +263,10 @@ public class IslandLevelCalculator {
public Results getResults() { public Results getResults() {
return results; return results;
} }
/** /**
* Get value of a material * Get value of a material World blocks trump regular block values
* World blocks trump regular block values *
* @param md - Material to check * @param md - Material to check
* @return value of a material * @return value of a material
*/ */
@ -364,9 +282,11 @@ public class IslandLevelCalculator {
/** /**
* Get a chunk async * Get a chunk async
*
* @param env - the environment * @param env - the environment
* @param pairList - chunk coordinate * @param pairList - chunk coordinate
* @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether * @return a future chunk or future null if there is no chunk to load, e.g.,
* 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)) {
@ -410,7 +330,9 @@ public class IslandLevelCalculator {
} }
/** /**
* Checks if a block has been limited or not and whether a block has any value or not * Checks if a block has been limited or not and whether a block has any value
* or not
*
* @param md Material * @param md Material
* @return value of the block if can be counted * @return value of the block if can be counted
*/ */
@ -428,9 +350,9 @@ public class IslandLevelCalculator {
return getValue(md); return getValue(md);
} }
/** /**
* Scan all containers in a chunk and count their blocks * Scan all containers in a chunk and count their blocks
*
* @param chunk - the chunk to scan * @param chunk - the chunk to scan
*/ */
private void scanChests(Chunk chunk) { private void scanChests(Chunk chunk) {
@ -455,7 +377,8 @@ public class IslandLevelCalculator {
} }
private void countItemStack(ItemStack i) { private void countItemStack(ItemStack i) {
if (i == null || !i.getType().isBlock()) return; if (i == null || !i.getType().isBlock())
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()
@ -469,10 +392,13 @@ public class IslandLevelCalculator {
} }
/** /**
* Scan the chunk chests and count the blocks. Note that the chunks are a list of all the island chunks * Scan the chunk chests and count the blocks. Note that the chunks are a list
* in a particular world, so the memory usage is high, but I think most servers can handle it. * of all the island chunks in a particular world, so the memory usage is high,
* but I think most servers can handle it.
*
* @param chunks - a list of chunks to scan * @param chunks - a list of chunks to scan
* @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 * @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
*/ */
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
@ -482,11 +408,13 @@ public class IslandLevelCalculator {
// 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 async. * At this point, we need to grab a snapshot of each chunk and then scan it
* At the end, we make the CompletableFuture true to show it is done. * async. At the end, we make the CompletableFuture true to show it is done. I'm
* I'm not sure how much lag this will cause, but as all the chunks are loaded, maybe not that much. * not sure how much lag this will cause, but as all the chunks are loaded,
* maybe not that much.
*/ */
List<ChunkPair> preLoad = chunks.stream().map(c -> new ChunkPair(c.getWorld(), c, c.getChunkSnapshot())).toList(); List<ChunkPair> preLoad = chunks.stream().map(c -> new ChunkPair(c.getWorld(), c, c.getChunkSnapshot()))
.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.
@ -495,21 +423,27 @@ public class IslandLevelCalculator {
return result; return result;
} }
record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {} record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {
}
/** /**
* Count the blocks on the island * Count the blocks on the island
*
* @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 count it // Check if the block coordinate is inside the protection zone and if not, don't
if (cp.chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || cp.chunkSnapshot.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) { // count it
if (cp.chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || cp.chunkSnapshot.getX() * 16
+ 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 count it // Check if the block coordinate is inside the protection zone and if not, don't
if (cp.chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || cp.chunkSnapshot.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) { // count it
if (cp.chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || cp.chunkSnapshot.getZ() * 16
+ 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
@ -523,16 +457,20 @@ public class IslandLevelCalculator {
checkBlock(blockData.getMaterial(), belowSeaLevel); checkBlock(blockData.getMaterial(), 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) || blockData.getMaterial().equals(Material.SPAWNER))) { // chunk
stackedBlocks.add(new Location(cp.world, (double)x + cp.chunkSnapshot.getX() * 16, y, (double)z + cp.chunkSnapshot.getZ() * 16)); if (addon.isStackersEnabled() && (blockData.getMaterial().equals(Material.CAULDRON)
|| blockData.getMaterial().equals(Material.SPAWNER))) {
stackedBlocks.add(new Location(cp.world, (double) x + cp.chunkSnapshot.getX() * 16, y,
(double) z + cp.chunkSnapshot.getZ() * 16));
} }
Block block = cp.chunk.getBlock(x, y, z); Block block = cp.chunk.getBlock(x, y, z);
if (addon.isUltimateStackerEnabled()) { if (addon.isUltimateStackerEnabled()) {
if (!blockData.getMaterial().equals(Material.AIR)) { if (!blockData.getMaterial().equals(Material.AIR)) {
BlockStack stack = UltimateStacker.getInstance().getBlockStackManager().getBlock(block, CompatibleMaterial.getMaterial(block)); BlockStack stack = UltimateStacker.getInstance().getBlockStackManager().getBlock(block,
CompatibleMaterial.getMaterial(block));
if (stack != null) { if (stack != null) {
int value = limitCount(blockData.getMaterial()); int value = limitCount(blockData.getMaterial());
if (belowSeaLevel) { if (belowSeaLevel) {
@ -559,7 +497,9 @@ public class IslandLevelCalculator {
/** /**
* Scan the next chunk on the island * Scan the next chunk on the island
* @return completable boolean future that will be true if more chunks are left to be scanned, and false if not *
* @return completable boolean future that will be true if more chunks are left
* to be scanned, and false if not
*/ */
public CompletableFuture<Boolean> scanNextChunk() { public CompletableFuture<Boolean> scanNextChunk() {
if (chunksToCheck.isEmpty()) { if (chunksToCheck.isEmpty()) {
@ -579,31 +519,28 @@ public class IslandLevelCalculator {
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(endChunks -> getWorldChunk(Environment.THE_END, endPairList).thenAccept(
scanChunk(endChunks).thenAccept(b -> endChunks -> scanChunk(endChunks).thenAccept(b -> getWorldChunk(Environment.NETHER, netherPairList)
getWorldChunk(Environment.NETHER, netherPairList).thenAccept(netherChunks -> .thenAccept(netherChunks -> scanChunk(netherChunks)
scanChunk(netherChunks).thenAccept(b2 -> .thenAccept(b2 -> getWorldChunk(Environment.NORMAL, pairList)
getWorldChunk(Environment.NORMAL, pairList).thenAccept(normalChunks -> .thenAccept(normalChunks -> scanChunk(normalChunks).thenAccept(b3 ->
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).entrySet(); Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount)
.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() + ":" result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = "
+ String.format("%,d", en.getCount()) + " blocks x " + value + " = " + (value * en.getCount())); + (value * en.getCount()));
total += (value * en.getCount()); total += (value * en.getCount());
} }
@ -612,34 +549,29 @@ public class IslandLevelCalculator {
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.addAndGet((long)(results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier())); results.rawBlockCount
.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();
} }
@ -674,11 +606,13 @@ public class IslandLevelCalculator {
addon.getPlugin().logError("scanChunk not on Primary Thread!"); addon.getPlugin().logError("scanChunk not on Primary Thread!");
} }
// Timeout check // Timeout check
if (System.currentTimeMillis() - pipeliner.getInProcessQueue().get(this) > addon.getSettings().getCalculationTimeout() * 60000) { if (System.currentTimeMillis()
- 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() + "m for island: " + getIsland()); addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout()
+ "m for island: " + getIsland());
if (!isNotZeroIsland()) { if (!isNotZeroIsland()) {
addon.logError("Island level was being zeroed."); addon.logError("Island level was being zeroed.");
} }
@ -697,7 +631,8 @@ public class IslandLevelCalculator {
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()) || System.currentTimeMillis() - checkTime > MAX_AMOUNT) { if ((chestBlocks.isEmpty() && stackedBlocks.isEmpty())
|| System.currentTimeMillis() - checkTime > MAX_AMOUNT) {
this.tidyUp(); this.tidyUp();
this.getR().complete(getResults()); this.getR().complete(getResults());
finishTask.cancel(); finishTask.cancel();