From 39837643531264e021170d56fc069f5f1274eca7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 5 May 2024 13:40:14 -0700 Subject: [PATCH] Release 2.13.0 (#310) * Update tipped arrows in GUI Panel * Version 2.13.0 * Add more string replacements for /is level output (#303) * Update hooks and fix UltimateStacker API (#305) * Isolate UltimateStacker imports so no errors if US is not installed (#307) Fixes #306 * Implement a cache for top tens (#309) --- pom.xml | 25 +- .../world/bentobox/level/LevelsManager.java | 388 ++++++------ .../calculators/IslandLevelCalculator.java | 597 +++++++++--------- .../calculators/UltimateStackerCalc.java | 29 + .../level/commands/IslandLevelCommand.java | 6 +- .../world/bentobox/level/util/CachedData.java | 30 + src/main/resources/locales/en-US.yml | 4 +- src/main/resources/panels/detail_panel.yml | 4 +- src/main/resources/panels/value_panel.yml | 4 +- 9 files changed, 577 insertions(+), 510 deletions(-) create mode 100644 src/main/java/world/bentobox/level/calculators/UltimateStackerCalc.java create mode 100644 src/main/java/world/bentobox/level/util/CachedData.java diff --git a/pom.xml b/pom.xml index 881d55e..4adb4dd 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ -LOCAL - 2.12.0 + 2.13.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io @@ -129,8 +129,8 @@ - jitpack.io - https://jitpack.io + bg-repo + https://repo.bg-software.com/repository/api/ @@ -139,8 +139,8 @@ - songoda-public - https://repo.songoda.com/repository/public/ + songoda-plugins + https://repo.songoda.com/repository/minecraft-plugins/ spigot-repo @@ -154,6 +154,10 @@ codemc-public https://repo.codemc.org/repository/maven-public/ + + jitpack.io + https://jitpack.io + @@ -208,9 +212,9 @@ - com.github.OmerBenGera + com.bgsoftware WildStackerAPI - b18 + 2023.3 provided @@ -234,10 +238,11 @@ 1.3.0 provided + - com.songoda - UltimateStacker - 2.4.0 + com.craftaro + UltimateStacker-API + 1.0.0-20240329.173606-35 provided diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index f3532aa..f0680c5 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -2,6 +2,7 @@ package world.bentobox.level; import java.math.BigInteger; import java.text.DecimalFormat; +import java.time.Instant; import java.util.AbstractMap; import java.util.Collections; import java.util.HashMap; @@ -31,6 +32,7 @@ import world.bentobox.level.events.IslandPreLevelEvent; import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.objects.LevelsData; import world.bentobox.level.objects.TopTenData; +import world.bentobox.level.util.CachedData; public class LevelsManager { private static final String INTOPTEN = "intopten"; @@ -52,6 +54,8 @@ public class LevelsManager { private final Map levelsCache; // Top ten lists private final Map topTenLists; + // Cache for top tens + private Map cache = new HashMap<>(); public LevelsManager(Level addon) { this.addon = addon; @@ -66,35 +70,35 @@ public class LevelsManager { } public void migrate() { - Database oldDb = new Database<>(addon, LevelsData.class); - oldDb.loadObjects().forEach(ld -> { - try { - UUID owner = UUID.fromString(ld.getUniqueId()); - // Step through each world - ld.getLevels().keySet().stream() - // World - .map(Bukkit::getWorld).filter(Objects::nonNull) - // Island - .map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull).forEach(i -> { - // Make new database entry - World w = i.getWorld(); - IslandLevels il = new IslandLevels(i.getUniqueId()); - il.setInitialLevel(ld.getInitialLevel(w)); - il.setLevel(ld.getLevel(w)); - il.setMdCount(ld.getMdCount(w)); - il.setPointsToNextLevel(ld.getPointsToNextLevel(w)); - il.setUwCount(ld.getUwCount(w)); - // Save it - handler.saveObjectAsync(il); - }); - // Now delete the old database entry - oldDb.deleteID(ld.getUniqueId()); - } catch (Exception e) { - addon.logError("Could not migrate level data database! " + e.getMessage()); - e.printStackTrace(); - return; - } - }); + Database oldDb = new Database<>(addon, LevelsData.class); + oldDb.loadObjects().forEach(ld -> { + try { + UUID owner = UUID.fromString(ld.getUniqueId()); + // Step through each world + ld.getLevels().keySet().stream() + // World + .map(Bukkit::getWorld).filter(Objects::nonNull) + // Island + .map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull).forEach(i -> { + // Make new database entry + World w = i.getWorld(); + IslandLevels il = new IslandLevels(i.getUniqueId()); + il.setInitialLevel(ld.getInitialLevel(w)); + il.setLevel(ld.getLevel(w)); + il.setMdCount(ld.getMdCount(w)); + il.setPointsToNextLevel(ld.getPointsToNextLevel(w)); + il.setUwCount(ld.getUwCount(w)); + // Save it + handler.saveObjectAsync(il); + }); + // Now delete the old database entry + oldDb.deleteID(ld.getUniqueId()); + } catch (Exception e) { + addon.logError("Could not migrate level data database! " + e.getMessage()); + e.printStackTrace(); + return; + } + }); } /** @@ -105,12 +109,12 @@ public class LevelsManager { * @return true if successful, false if not added */ private boolean addToTopTen(Island island, long lv) { - if (island != null && island.getOwner() != null && hasTopTenPerm(island.getWorld(), island.getOwner())) { - topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())).getTopTen() - .put(island.getUniqueId(), lv); - return true; - } - return false; + if (island != null && island.getOwner() != null && hasTopTenPerm(island.getWorld(), island.getOwner())) { + topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())).getTopTen() + .put(island.getUniqueId(), lv); + return true; + } + return false; } /** @@ -122,26 +126,26 @@ public class LevelsManager { * @return completable future with the results of the calculation */ public CompletableFuture calculateLevel(UUID targetPlayer, Island island) { - CompletableFuture result = new CompletableFuture<>(); - // Fire pre-level calc event - IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island); - Bukkit.getPluginManager().callEvent(e); - if (e.isCancelled()) { - return CompletableFuture.completedFuture(null); - } - // Add island to the pipeline - addon.getPipeliner().addIsland(island).thenAccept(r -> { - // Results are irrelevant because the island is unowned or deleted, or - // IslandLevelCalcEvent is cancelled - if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) { - result.complete(null); - } - // Save result - setIslandResults(island, r); - // Save the island scan details - result.complete(r); - }); - return result; + CompletableFuture result = new CompletableFuture<>(); + // Fire pre-level calc event + IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island); + Bukkit.getPluginManager().callEvent(e); + if (e.isCancelled()) { + return CompletableFuture.completedFuture(null); + } + // Add island to the pipeline + addon.getPipeliner().addIsland(island).thenAccept(r -> { + // Results are irrelevant because the island is unowned or deleted, or + // IslandLevelCalcEvent is cancelled + if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) { + result.complete(null); + } + // Save result + setIslandResults(island, r); + // Save the island scan details + result.complete(r); + }); + return result; } /** @@ -153,19 +157,19 @@ public class LevelsManager { * @return true if canceled */ private boolean fireIslandLevelCalcEvent(UUID targetPlayer, Island island, Results results) { - // Fire post calculation event - IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results); - Bukkit.getPluginManager().callEvent(ilce); - if (ilce.isCancelled()) - return true; - // Set the values if they were altered - results.setLevel((Long) ilce.getKeyValues().getOrDefault("level", results.getLevel())); - results.setInitialLevel((Long) ilce.getKeyValues().getOrDefault("initialLevel", results.getInitialLevel())); - results.setDeathHandicap((int) ilce.getKeyValues().getOrDefault("deathHandicap", results.getDeathHandicap())); - results.setPointsToNextLevel( - (Long) ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel())); - results.setTotalPoints((Long) ilce.getKeyValues().getOrDefault("totalPoints", results.getTotalPoints())); - return ((Boolean) ilce.getKeyValues().getOrDefault("isCancelled", false)); + // Fire post calculation event + IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results); + Bukkit.getPluginManager().callEvent(ilce); + if (ilce.isCancelled()) + return true; + // Set the values if they were altered + results.setLevel((Long) ilce.getKeyValues().getOrDefault("level", results.getLevel())); + results.setInitialLevel((Long) ilce.getKeyValues().getOrDefault("initialLevel", results.getInitialLevel())); + results.setDeathHandicap((int) ilce.getKeyValues().getOrDefault("deathHandicap", results.getDeathHandicap())); + results.setPointsToNextLevel( + (Long) ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel())); + results.setTotalPoints((Long) ilce.getKeyValues().getOrDefault("totalPoints", results.getTotalPoints())); + return ((Boolean) ilce.getKeyValues().getOrDefault("isCancelled", false)); } /** @@ -176,25 +180,25 @@ public class LevelsManager { * @return string of the level. */ public String formatLevel(@Nullable Long lvl) { - if (lvl == null) - return ""; - String level = String.valueOf(lvl); - // Asking for the level of another player - if (addon.getSettings().isShorthand()) { - BigInteger levelValue = BigInteger.valueOf(lvl); + if (lvl == null) + return ""; + String level = String.valueOf(lvl); + // Asking for the level of another player + if (addon.getSettings().isShorthand()) { + BigInteger levelValue = BigInteger.valueOf(lvl); - Map.Entry stage = LEVELS.floorEntry(levelValue); + Map.Entry stage = LEVELS.floorEntry(levelValue); - if (stage != null) { // level > 1000 - // 1 052 -> 1.0k - // 1 527 314 -> 1.5M - // 3 874 130 021 -> 3.8G - // 4 002 317 889 -> 4.0T - level = new DecimalFormat("#.#").format( - levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue() / 1000.0) + stage.getValue(); - } - } - return level; + if (stage != null) { // level > 1000 + // 1 052 -> 1.0k + // 1 527 314 -> 1.5M + // 3 874 130 021 -> 3.8G + // 4 002 317 889 -> 4.0T + level = new DecimalFormat("#.#").format( + levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue() / 1000.0) + stage.getValue(); + } + } + return level; } /** @@ -216,11 +220,11 @@ public class LevelsManager { * null */ public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) { - if (targetPlayer == null) - return 0L; - // Get the island - Island island = addon.getIslands().getIsland(world, targetPlayer); - return island == null ? 0L : getLevelsData(island).getLevel(); + if (targetPlayer == null) + return 0L; + // Get the island + Island island = addon.getIslands().getIsland(world, targetPlayer); + return island == null ? 0L : getLevelsData(island).getLevel(); } /** @@ -232,11 +236,11 @@ public class LevelsManager { * is null */ public long getIslandMaxLevel(@NonNull World world, @Nullable UUID targetPlayer) { - if (targetPlayer == null) - return 0L; - // Get the island - Island island = addon.getIslands().getIsland(world, targetPlayer); - return island == null ? 0L : getLevelsData(island).getMaxLevel(); + if (targetPlayer == null) + return 0L; + // Get the island + Island island = addon.getIslands().getIsland(world, targetPlayer); + return island == null ? 0L : getLevelsData(island).getMaxLevel(); } /** @@ -288,10 +292,10 @@ public class LevelsManager { * @return string with the number required or blank if the player is unknown */ public String getPointsToNextString(@NonNull World world, @Nullable UUID targetPlayer) { - if (targetPlayer == null) - return ""; - Island island = addon.getIslands().getIsland(world, targetPlayer); - return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel()); + if (targetPlayer == null) + return ""; + Island island = addon.getIslands().getIsland(world, targetPlayer); + return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel()); } /** @@ -304,28 +308,27 @@ public class LevelsManager { */ @NonNull public Map getWeightedTopTen(@NonNull World world, int size) { - createAndCleanRankings(world); - Map weightedTopTen = topTenLists.get(world).getTopTen().entrySet().stream() - .map(en -> addon.getIslands().getIslandById(en.getKey()).map(island -> { + createAndCleanRankings(world); + Map weightedTopTen = topTenLists.get(world).getTopTen().entrySet().stream() + .map(en -> addon.getIslands().getIslandById(en.getKey()).map(island -> { - long value = (long) (en.getValue() / (double) Math.max(1, island.getMemberSet().size())); // Calculate - // weighted - // value - return new AbstractMap.SimpleEntry<>(island, value); - }).orElse(null)) // Handle islands that do not exist according to this ID - old deleted ones - .filter(Objects::nonNull) // Filter out null entries - .filter(en -> en.getValue() > 0) // Filter out entries with non-positive values - .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) // Sort in descending order of values - .limit(size) // Limit to the top 'size' entries - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, // In case of key - // collision, choose - // the first one - LinkedHashMap::new // Preserves the order of entries - )); - - // Return the unmodifiable map - return Collections.unmodifiableMap(weightedTopTen); + long value = (long) (en.getValue() / (double) Math.max(1, island.getMemberSet().size())); // Calculate + // weighted + // value + return new AbstractMap.SimpleEntry<>(island, value); + }).orElse(null)) // Handle islands that do not exist according to this ID - old deleted ones + .filter(Objects::nonNull) // Filter out null entries + .filter(en -> en.getValue() > 0) // Filter out entries with non-positive values + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) // Sort in descending order of values + .limit(size) // Limit to the top 'size' entries + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, // In case of key + // collision, choose + // the first one + LinkedHashMap::new // Preserves the order of entries + )); + // Return the unmodifiable map + return Collections.unmodifiableMap(weightedTopTen); } /** @@ -338,26 +341,38 @@ public class LevelsManager { */ @NonNull public Map getTopTen(@NonNull World world, int size) { - createAndCleanRankings(world); - // Return the sorted map - return Collections.unmodifiableMap(topTenLists.get(world).getTopTen().entrySet().stream() - .filter(l -> l.getValue() > 0).sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) - .limit(size) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new))); + createAndCleanRankings(world); + CachedData cachedData = cache.get(world); + Instant now = Instant.now(); + + if (cachedData != null && cachedData.getLastUpdated().plusSeconds(1).isAfter(now)) { + return cachedData.getCachedMap(); + } else { + Map newTopTen = calculateTopTen(world, size); + cache.put(world, new CachedData(newTopTen, now)); + return newTopTen; + } + } + + private Map calculateTopTen(@NonNull World world, int size) { + return Collections.unmodifiableMap(topTenLists.get(world).getTopTen().entrySet().stream() + .filter(l -> l.getValue() > 0).sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) + .limit(size) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new))); } void createAndCleanRankings(@NonNull World world) { - topTenLists.computeIfAbsent(world, TopTenData::new); - // Remove player from top ten if they are online and do not have the perm - topTenLists.get(world).getTopTen().keySet().removeIf(u -> addon.getIslands().getIslandById(u) - .filter(i -> i.getOwner() == null || !hasTopTenPerm(world, i.getOwner())).isPresent()); + topTenLists.computeIfAbsent(world, TopTenData::new); + // Remove player from top ten if they are online and do not have the perm + topTenLists.get(world).getTopTen().keySet().removeIf(u -> addon.getIslands().getIslandById(u) + .filter(i -> i.getOwner() == null || !hasTopTenPerm(world, i.getOwner())).isPresent()); } /** * @return the topTenLists */ public Map getTopTenLists() { - return topTenLists; + return topTenLists; } /** @@ -368,13 +383,13 @@ public class LevelsManager { * @return rank placing - note - placing of 1 means top ranked */ public int getRank(@NonNull World world, UUID uuid) { - createAndCleanRankings(world); - Stream> stream = topTenLists.get(world).getTopTen().entrySet().stream() - .filter(l -> l.getValue() > 0).sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); - // Get player's current island - Island island = addon.getIslands().getIsland(world, uuid); - String id = island == null ? null : island.getUniqueId(); - return (int) (stream.takeWhile(x -> !x.getKey().equals(id)).map(Map.Entry::getKey).count() + 1); + createAndCleanRankings(world); + Stream> stream = topTenLists.get(world).getTopTen().entrySet().stream() + .filter(l -> l.getValue() > 0).sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); + // Get player's current island + Island island = addon.getIslands().getIsland(world, uuid); + String id = island == null ? null : island.getUniqueId(); + return (int) (stream.takeWhile(x -> !x.getKey().equals(id)).map(Map.Entry::getKey).count() + 1); } /** @@ -385,26 +400,26 @@ public class LevelsManager { * @return true if player has the perm or the player is offline */ boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) { - String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world); - return Bukkit.getPlayer(targetPlayer) == null - || Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN); + String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world); + return Bukkit.getPlayer(targetPlayer) == null + || Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN); } /** * Loads all the top tens from the database */ public void loadTopTens() { - topTenLists.clear(); - Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> { - addon.log("Generating rankings"); - handler.loadObjects().forEach(il -> { - if (il.getLevel() > 0) { - addon.getIslands().getIslandById(il.getUniqueId()) - .ifPresent(i -> this.addToTopTen(i, il.getLevel())); - } - }); - topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName())); - }); + topTenLists.clear(); + Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> { + addon.log("Generating rankings"); + handler.loadObjects().forEach(il -> { + if (il.getLevel() > 0) { + addon.getIslands().getIslandById(il.getUniqueId()) + .ifPresent(i -> this.addToTopTen(i, il.getLevel())); + } + }); + topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName())); + }); } /** @@ -414,10 +429,11 @@ public class LevelsManager { * @param uuid - the island's uuid */ public void removeEntry(World world, String uuid) { - if (topTenLists.containsKey(world)) { - topTenLists.get(world).getTopTen().remove(uuid); - } - + if (topTenLists.containsKey(world)) { + topTenLists.get(world).getTopTen().remove(uuid); + // Invalidate the cache because of this deletion + cache.remove(world); + } } /** @@ -427,10 +443,10 @@ public class LevelsManager { * @param lv - initial island level */ public void setInitialIslandLevel(@NonNull Island island, long lv) { - if (island.getWorld() == null) - return; - levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv); - handler.saveObjectAsync(levelsCache.get(island.getUniqueId())); + if (island.getWorld() == null) + return; + levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv); + handler.saveObjectAsync(levelsCache.get(island.getUniqueId())); } /** @@ -442,21 +458,21 @@ public class LevelsManager { * @param lv - level */ public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) { - // Get the island - Island island = addon.getIslands().getIsland(world, targetPlayer); - if (island != null) { - String id = island.getUniqueId(); - IslandLevels il = levelsCache.computeIfAbsent(id, IslandLevels::new); - // Remove the initial level - if (addon.getSettings().isZeroNewIslandLevels()) { - il.setLevel(lv - il.getInitialLevel()); - } else { - il.setLevel(lv); - } - handler.saveObjectAsync(levelsCache.get(id)); - // Update TopTen - addToTopTen(island, levelsCache.get(id).getLevel()); - } + // Get the island + Island island = addon.getIslands().getIsland(world, targetPlayer); + if (island != null) { + String id = island.getUniqueId(); + IslandLevels il = levelsCache.computeIfAbsent(id, IslandLevels::new); + // Remove the initial level + if (addon.getSettings().isZeroNewIslandLevels()) { + il.setLevel(lv - il.getInitialLevel()); + } else { + il.setLevel(lv); + } + handler.saveObjectAsync(levelsCache.get(id)); + // Update TopTen + addToTopTen(island, levelsCache.get(id).getLevel()); + } } /** @@ -468,18 +484,18 @@ public class LevelsManager { * @param r - results of the calculation */ private void setIslandResults(Island island, Results r) { - if (island == null) - return; - IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new); - ld.setLevel(r.getLevel()); - ld.setUwCount(Maps.asMap(r.getUwCount().elementSet(), elem -> r.getUwCount().count(elem))); - ld.setMdCount(Maps.asMap(r.getMdCount().elementSet(), elem -> r.getMdCount().count(elem))); - ld.setPointsToNextLevel(r.getPointsToNextLevel()); - ld.setTotalPoints(r.getTotalPoints()); - levelsCache.put(island.getUniqueId(), ld); - handler.saveObjectAsync(ld); - // Update TopTen - addToTopTen(island, ld.getLevel()); + if (island == null) + return; + IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new); + ld.setLevel(r.getLevel()); + ld.setUwCount(Maps.asMap(r.getUwCount().elementSet(), elem -> r.getUwCount().count(elem))); + ld.setMdCount(Maps.asMap(r.getMdCount().elementSet(), elem -> r.getMdCount().count(elem))); + ld.setPointsToNextLevel(r.getPointsToNextLevel()); + ld.setTotalPoints(r.getTotalPoints()); + levelsCache.put(island.getUniqueId(), ld); + handler.saveObjectAsync(ld); + // Update TopTen + addToTopTen(island, ld.getLevel()); } /** diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 7fc1cca..8904cdf 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -40,9 +40,6 @@ import com.bgsoftware.wildstacker.api.objects.StackedBarrel; import com.google.common.collect.Multiset; import com.google.common.collect.Multiset.Entry; 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 us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI; @@ -59,13 +56,13 @@ public class IslandLevelCalculator { private static final String LINE_BREAK = "=================================="; public static final long MAX_AMOUNT = 10000000; private static final List CHESTS = Arrays.asList(Material.CHEST, Material.CHEST_MINECART, - 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.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.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.COMPOSTER, Material.BARREL, - Material.DISPENSER, Material.DROPPER, Material.SMOKER, Material.BLAST_FURNACE); + 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.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.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.COMPOSTER, Material.BARREL, + Material.DISPENSER, Material.DROPPER, Material.SMOKER, Material.BLAST_FURNACE); private static final int CHUNKS_TO_SCAN = 100; private final Level addon; private final Queue> chunksToCheck; @@ -129,18 +126,18 @@ public class IslandLevelCalculator { * @return level of island */ private long calculateLevel(long blockAndDeathPoints) { - String calcString = addon.getSettings().getLevelCalc(); - String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", - String.valueOf(this.addon.getSettings().getLevelCost())); - long evalWithValues; - try { - evalWithValues = (long) EquationEvaluator.eval(withValues); - return evalWithValues - (addon.getSettings().isZeroNewIslandLevels() ? results.initialLevel.get() : 0); + String calcString = addon.getSettings().getLevelCalc(); + String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", + String.valueOf(this.addon.getSettings().getLevelCost())); + long evalWithValues; + try { + evalWithValues = (long) EquationEvaluator.eval(withValues); + return evalWithValues - (addon.getSettings().isZeroNewIslandLevels() ? results.initialLevel.get() : 0); - } catch (ParseException e) { - addon.getPlugin().logStacktrace(e); - return 0L; - } + } catch (ParseException e) { + addon.getPlugin().logStacktrace(e); + return 0L; + } } /** @@ -168,15 +165,15 @@ public class IslandLevelCalculator { * @return - set of pairs of x,z coordinates to check */ private Queue> getChunksToScan(Island island) { - Queue> chunkQueue = new ConcurrentLinkedQueue<>(); - for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 - + 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)); - } - } - return chunkQueue; + Queue> chunkQueue = new ConcurrentLinkedQueue<>(); + for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 + + 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)); + } + } + return chunkQueue; } /** @@ -201,60 +198,60 @@ public class IslandLevelCalculator { * @return a list of lines */ private List getReport() { - List reportLines = new ArrayList<>(); - // provide counts - 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("Total block value count = " + String.format("%,d", results.rawBlockCount.get())); - reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc()); - reportLines.add("Level cost = " + addon.getSettings().getLevelCost()); - reportLines.add("Deaths handicap = " + results.deathHandicap.get()); - if (addon.getSettings().isZeroNewIslandLevels()) { - reportLines.add("Initial island level = " + (0L - addon.getManager().getInitialLevel(island))); - } - reportLines.add("Previous level = " + addon.getManager().getIslandLevel(island.getWorld(), island.getOwner())); - reportLines.add("New level = " + results.getLevel()); - reportLines.add(LINE_BREAK); - int total = 0; - if (!results.uwCount.isEmpty()) { - 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.addAll(sortedReport(total, results.uwCount)); - } - reportLines.add("Regular block count"); - reportLines.add("Total number of blocks = " + String.format("%,d", results.mdCount.size())); - reportLines.addAll(sortedReport(total, results.mdCount)); + List reportLines = new ArrayList<>(); + // provide counts + 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("Total block value count = " + String.format("%,d", results.rawBlockCount.get())); + reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc()); + reportLines.add("Level cost = " + addon.getSettings().getLevelCost()); + reportLines.add("Deaths handicap = " + results.deathHandicap.get()); + if (addon.getSettings().isZeroNewIslandLevels()) { + reportLines.add("Initial island level = " + (0L - addon.getManager().getInitialLevel(island))); + } + reportLines.add("Previous level = " + addon.getManager().getIslandLevel(island.getWorld(), island.getOwner())); + reportLines.add("New level = " + results.getLevel()); + reportLines.add(LINE_BREAK); + int total = 0; + if (!results.uwCount.isEmpty()) { + 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.addAll(sortedReport(total, results.uwCount)); + } + reportLines.add("Regular block count"); + reportLines.add("Total number of blocks = " + String.format("%,d", results.mdCount.size())); + reportLines.addAll(sortedReport(total, results.mdCount)); - reportLines.add( - "Blocks not counted because they exceeded limits: " + String.format("%,d", results.ofCount.size())); - Iterable> entriesSortedByCount = results.ofCount.entrySet(); - Iterator> it = entriesSortedByCount.iterator(); - while (it.hasNext()) { - Entry type = it.next(); - Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement()); - String explain = ")"; - if (limit == null) { - Material generic = type.getElement(); - limit = addon.getBlockConfig().getBlockLimits().get(generic); - explain = " - All types)"; - } - reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) - + " blocks (max " + limit + explain); - } - reportLines.add(LINE_BREAK); - reportLines.add("Blocks on island that are not in config.yml"); - reportLines.add("Total number = " + String.format("%,d", results.ncCount.size())); - entriesSortedByCount = results.ncCount.entrySet(); - it = entriesSortedByCount.iterator(); - while (it.hasNext()) { - Entry type = it.next(); - reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) + " blocks"); - } - reportLines.add(LINE_BREAK); + reportLines.add( + "Blocks not counted because they exceeded limits: " + String.format("%,d", results.ofCount.size())); + Iterable> entriesSortedByCount = results.ofCount.entrySet(); + Iterator> it = entriesSortedByCount.iterator(); + while (it.hasNext()) { + Entry type = it.next(); + Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement()); + String explain = ")"; + if (limit == null) { + Material generic = type.getElement(); + limit = addon.getBlockConfig().getBlockLimits().get(generic); + explain = " - All types)"; + } + reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) + + " blocks (max " + limit + explain); + } + reportLines.add(LINE_BREAK); + reportLines.add("Blocks on island that are not in config.yml"); + reportLines.add("Total number = " + String.format("%,d", results.ncCount.size())); + entriesSortedByCount = results.ncCount.entrySet(); + it = entriesSortedByCount.iterator(); + while (it.hasNext()) { + Entry type = it.next(); + reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) + " blocks"); + } + reportLines.add(LINE_BREAK); - return reportLines; + return reportLines; } /** @@ -356,39 +353,39 @@ public class IslandLevelCalculator { * @param chunk - the chunk to scan */ private void scanChests(Chunk chunk) { - // Count blocks in chests - for (BlockState bs : chunk.getTileEntities()) { - if (bs instanceof Container container) { - if (addon.isAdvChestEnabled()) { - AdvancedChest aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation()); - if (aChest != null && aChest.getChestType().getName().equals("NORMAL")) { - aChest.getPages().stream().map(ChestPage::getItems).forEach(c -> { - for (Object i : c) { - countItemStack((ItemStack) i); - } - }); - continue; - } - } - // Regular chest - container.getSnapshotInventory().forEach(this::countItemStack); - } - } + // Count blocks in chests + for (BlockState bs : chunk.getTileEntities()) { + if (bs instanceof Container container) { + if (addon.isAdvChestEnabled()) { + AdvancedChest aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation()); + if (aChest != null && aChest.getChestType().getName().equals("NORMAL")) { + aChest.getPages().stream().map(ChestPage::getItems).forEach(c -> { + for (Object i : c) { + countItemStack((ItemStack) i); + } + }); + continue; + } + } + // Regular chest + container.getSnapshotInventory().forEach(this::countItemStack); + } + } } 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++) { - if (addon.getSettings().isIncludeShulkersInChest() - && i.getItemMeta() instanceof BlockStateMeta blockStateMeta - && blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox) { - shulkerBox.getSnapshotInventory().forEach(this::countItemStack); - } + for (int c = 0; c < i.getAmount(); c++) { + if (addon.getSettings().isIncludeShulkersInChest() + && i.getItemMeta() instanceof BlockStateMeta blockStateMeta + && blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox) { + shulkerBox.getSnapshotInventory().forEach(this::countItemStack); + } - checkBlock(i.getType(), false); - } + checkBlock(i.getType(), false); + } } /** @@ -401,26 +398,26 @@ public class IslandLevelCalculator { * that will be true if the scan was successful, false if not */ private CompletableFuture scanChunk(List chunks) { - // If the chunk hasn't been generated, return - if (chunks == null || chunks.isEmpty()) { - return CompletableFuture.completedFuture(false); - } - // Count blocks in chunk - CompletableFuture result = new CompletableFuture<>(); - /* - * 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 - * not sure how much lag this will cause, but as all the chunks are loaded, - * maybe not that much. - */ - List preLoad = chunks.stream().map(c -> new ChunkPair(c.getWorld(), c, c.getChunkSnapshot())) - .toList(); - Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> { - preLoad.forEach(this::scanAsync); - // Once they are all done, return to the main thread. - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> result.complete(true)); - }); - return result; + // If the chunk hasn't been generated, return + if (chunks == null || chunks.isEmpty()) { + return CompletableFuture.completedFuture(false); + } + // Count blocks in chunk + CompletableFuture result = new CompletableFuture<>(); + /* + * 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 + * not sure how much lag this will cause, but as all the chunks are loaded, + * maybe not that much. + */ + List preLoad = chunks.stream().map(c -> new ChunkPair(c.getWorld(), c, c.getChunkSnapshot())) + .toList(); + Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> { + preLoad.forEach(this::scanAsync); + // Once they are all done, return to the main thread. + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> result.complete(true)); + }); + return result; } record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) { @@ -432,67 +429,53 @@ public class IslandLevelCalculator { * @param cp chunk to scan */ private void scanAsync(ChunkPair cp) { - for (int x = 0; x < 16; x++) { - // Check if the block coordinate is inside the protection zone and if not, don't - // count it - if (cp.chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || cp.chunkSnapshot.getX() * 16 - + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) { - continue; - } - for (int z = 0; z < 16; z++) { - // Check if the block coordinate is inside the protection zone and if not, don't - // count it - if (cp.chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || cp.chunkSnapshot.getZ() * 16 - + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) { - continue; - } - // Only count to the highest block in the world for some optimization - for (int y = cp.world.getMinHeight(); y < cp.world.getMaxHeight(); y++) { - BlockData blockData = cp.chunkSnapshot.getBlockData(x, y, z); - boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight; - // Slabs can be doubled, so check them twice - if (Tag.SLABS.isTagged(blockData.getMaterial())) { - Slab slab = (Slab) blockData; - if (slab.getType().equals(Slab.Type.DOUBLE)) { - checkBlock(blockData.getMaterial(), belowSeaLevel); - } - } - // Hook for Wild Stackers (Blocks and Spawners Only) - this has to use the real - // chunk - 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)); - } + for (int x = 0; x < 16; x++) { + // Check if the block coordinate is inside the protection zone and if not, don't + // count it + if (cp.chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || cp.chunkSnapshot.getX() * 16 + + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) { + continue; + } + for (int z = 0; z < 16; z++) { + // Check if the block coordinate is inside the protection zone and if not, don't + // count it + if (cp.chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || cp.chunkSnapshot.getZ() * 16 + + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) { + continue; + } + // Only count to the highest block in the world for some optimization + for (int y = cp.world.getMinHeight(); y < cp.world.getMaxHeight(); y++) { + BlockData blockData = cp.chunkSnapshot.getBlockData(x, y, z); + Material m = blockData.getMaterial(); + boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight; + // Slabs can be doubled, so check them twice + if (Tag.SLABS.isTagged(m)) { + Slab slab = (Slab) blockData; + 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 + if (addon.isStackersEnabled() && (m.equals(Material.CAULDRON) || m.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); + 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()) { - if (!blockData.getMaterial().equals(Material.AIR)) { - BlockStack stack = UltimateStacker.getInstance().getBlockStackManager().getBlock(block, - CompatibleMaterial.getMaterial(block)); - if (stack != null) { - int value = limitCount(blockData.getMaterial()); - if (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); - } - } - } + // Scan chests + if (addon.getSettings().isIncludeChests() && CHESTS.contains(m)) { + chestBlocks.add(cp.chunk); + } + // Add the value of the block's material + checkBlock(m, belowSeaLevel); + } + } + } } /** @@ -502,94 +485,94 @@ public class IslandLevelCalculator { * to be scanned, and false if not */ public CompletableFuture scanNextChunk() { - if (chunksToCheck.isEmpty()) { - addon.logError("Unexpected: no chunks to scan!"); - // This should not be needed, but just in case - return CompletableFuture.completedFuture(false); - } - // Retrieve and remove from the queue - Queue> pairList = new ConcurrentLinkedQueue<>(); - int i = 0; - while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) { - pairList.add(chunksToCheck.poll()); - } - Queue> endPairList = new ConcurrentLinkedQueue<>(pairList); - Queue> netherPairList = new ConcurrentLinkedQueue<>(pairList); - // Set up the result - CompletableFuture result = new CompletableFuture<>(); - // Get chunks and scan - // Get chunks and scan - getWorldChunk(Environment.THE_END, endPairList).thenAccept( - endChunks -> scanChunk(endChunks).thenAccept(b -> getWorldChunk(Environment.NETHER, netherPairList) - .thenAccept(netherChunks -> scanChunk(netherChunks) - .thenAccept(b2 -> getWorldChunk(Environment.NORMAL, pairList) - .thenAccept(normalChunks -> scanChunk(normalChunks).thenAccept(b3 -> - // Complete the result now that all chunks have been scanned - result.complete(!chunksToCheck.isEmpty()))))))); + if (chunksToCheck.isEmpty()) { + addon.logError("Unexpected: no chunks to scan!"); + // This should not be needed, but just in case + return CompletableFuture.completedFuture(false); + } + // Retrieve and remove from the queue + Queue> pairList = new ConcurrentLinkedQueue<>(); + int i = 0; + while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) { + pairList.add(chunksToCheck.poll()); + } + Queue> endPairList = new ConcurrentLinkedQueue<>(pairList); + Queue> netherPairList = new ConcurrentLinkedQueue<>(pairList); + // Set up the result + CompletableFuture result = new CompletableFuture<>(); + // Get chunks and scan + // Get chunks and scan + getWorldChunk(Environment.THE_END, endPairList).thenAccept( + endChunks -> scanChunk(endChunks).thenAccept(b -> getWorldChunk(Environment.NETHER, netherPairList) + .thenAccept(netherChunks -> scanChunk(netherChunks) + .thenAccept(b2 -> getWorldChunk(Environment.NORMAL, pairList) + .thenAccept(normalChunks -> scanChunk(normalChunks).thenAccept(b3 -> + // Complete the result now that all chunks have been scanned + result.complete(!chunksToCheck.isEmpty()))))))); - return result; + return result; } private Collection sortedReport(int total, Multiset materialCount) { - Collection result = new ArrayList<>(); - Iterable> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount) - .entrySet(); - for (Entry en : entriesSortedByCount) { - Material type = en.getElement(); + Collection result = new ArrayList<>(); + Iterable> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount) + .entrySet(); + for (Entry en : entriesSortedByCount) { + Material type = en.getElement(); - int value = getValue(type); + int value = getValue(type); - result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = " - + (value * en.getCount())); - total += (value * en.getCount()); + result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = " + + (value * en.getCount())); + total += (value * en.getCount()); - } - result.add("Subtotal = " + total); - result.add(LINE_BREAK); - return result; + } + result.add("Subtotal = " + total); + result.add(LINE_BREAK); + return result; } /** * Finalizes the calculations and makes the report */ public void tidyUp() { - // Finalize calculations - results.rawBlockCount - .addAndGet((long) (results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier())); + // Finalize calculations + results.rawBlockCount + .addAndGet((long) (results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier())); - // Set the death penalty - if (this.addon.getSettings().isSumTeamDeaths()) { - for (UUID uuid : this.island.getMemberSet()) { - this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid)); - } - } else { - // At this point, it may be that the island has become unowned. - this.results.deathHandicap.set(this.island.getOwner() == null ? 0 - : this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner())); - } + // Set the death penalty + if (this.addon.getSettings().isSumTeamDeaths()) { + for (UUID uuid : this.island.getMemberSet()) { + this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid)); + } + } else { + // At this point, it may be that the island has become unowned. + this.results.deathHandicap.set(this.island.getOwner() == null ? 0 + : this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner())); + } - long blockAndDeathPoints = this.results.rawBlockCount.get(); - this.results.totalPoints.set(blockAndDeathPoints); + long blockAndDeathPoints = this.results.rawBlockCount.get(); + this.results.totalPoints.set(blockAndDeathPoints); - if (this.addon.getSettings().getDeathPenalty() > 0) { - // Proper death penalty calculation. - blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty(); - } - this.results.level.set(calculateLevel(blockAndDeathPoints)); + if (this.addon.getSettings().getDeathPenalty() > 0) { + // Proper death penalty calculation. + blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty(); + } + this.results.level.set(calculateLevel(blockAndDeathPoints)); - // Calculate how many points are required to get to the next level - long nextLevel = this.results.level.get(); - long blocks = blockAndDeathPoints; - while (nextLevel < this.results.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) { - nextLevel = calculateLevel(++blocks); - } - this.results.pointsToNextLevel.set(blocks - blockAndDeathPoints); + // Calculate how many points are required to get to the next level + long nextLevel = this.results.level.get(); + long blocks = blockAndDeathPoints; + while (nextLevel < this.results.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) { + nextLevel = calculateLevel(++blocks); + } + this.results.pointsToNextLevel.set(blocks - blockAndDeathPoints); - // Report - results.report = getReport(); - // Set the duration - addon.getPipeliner().setTime(System.currentTimeMillis() - duration); - // All done. + // Report + results.report = getReport(); + // Set the duration + addon.getPipeliner().setTime(System.currentTimeMillis() - duration); + // All done. } /** @@ -600,58 +583,58 @@ public class IslandLevelCalculator { } public void scanIsland(Pipeliner pipeliner) { - // Scan the next chunk - scanNextChunk().thenAccept(result -> { - if (!Bukkit.isPrimaryThread()) { - addon.getPlugin().logError("scanChunk not on Primary Thread!"); - } - // Timeout check - if (System.currentTimeMillis() - - pipeliner.getInProcessQueue().get(this) > addon.getSettings().getCalculationTimeout() * 60000) { - // Done - pipeliner.getInProcessQueue().remove(this); - getR().complete(new Results(Result.TIMEOUT)); - addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout() - + "m for island: " + getIsland()); - if (!isNotZeroIsland()) { - addon.logError("Island level was being zeroed."); - } - return; - } - if (Boolean.TRUE.equals(result) && !pipeliner.getTask().isCancelled()) { - // scanNextChunk returns true if there are more chunks to scan - scanIsland(pipeliner); - } else { - // Done - pipeliner.getInProcessQueue().remove(this); - // Chunk finished - // This was the last chunk - handleStackedBlocks(); - handleChests(); - long checkTime = System.currentTimeMillis(); - finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> { - // Check every half second if all the chests and stacks have been cleared - if ((chestBlocks.isEmpty() && stackedBlocks.isEmpty()) - || System.currentTimeMillis() - checkTime > MAX_AMOUNT) { - this.tidyUp(); - this.getR().complete(getResults()); - finishTask.cancel(); - } - }, 0, 10L); + // Scan the next chunk + scanNextChunk().thenAccept(result -> { + if (!Bukkit.isPrimaryThread()) { + addon.getPlugin().logError("scanChunk not on Primary Thread!"); + } + // Timeout check + if (System.currentTimeMillis() + - pipeliner.getInProcessQueue().get(this) > addon.getSettings().getCalculationTimeout() * 60000) { + // Done + pipeliner.getInProcessQueue().remove(this); + getR().complete(new Results(Result.TIMEOUT)); + addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout() + + "m for island: " + getIsland()); + if (!isNotZeroIsland()) { + addon.logError("Island level was being zeroed."); + } + return; + } + if (Boolean.TRUE.equals(result) && !pipeliner.getTask().isCancelled()) { + // scanNextChunk returns true if there are more chunks to scan + scanIsland(pipeliner); + } else { + // Done + pipeliner.getInProcessQueue().remove(this); + // Chunk finished + // This was the last chunk + handleStackedBlocks(); + handleChests(); + long checkTime = System.currentTimeMillis(); + finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> { + // Check every half second if all the chests and stacks have been cleared + if ((chestBlocks.isEmpty() && stackedBlocks.isEmpty()) + || System.currentTimeMillis() - checkTime > MAX_AMOUNT) { + this.tidyUp(); + this.getR().complete(getResults()); + finishTask.cancel(); + } + }, 0, 10L); - } - }); + } + }); } private void handleChests() { - Iterator it = chestBlocks.iterator(); - while (it.hasNext()) { - Chunk v = it.next(); - Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> { - scanChests(c); - it.remove(); - }); - } + Iterator it = chestBlocks.iterator(); + while (it.hasNext()) { + Chunk v = it.next(); + Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> { + scanChests(c); + it.remove(); + }); + } } private void handleStackedBlocks() { diff --git a/src/main/java/world/bentobox/level/calculators/UltimateStackerCalc.java b/src/main/java/world/bentobox/level/calculators/UltimateStackerCalc.java new file mode 100644 index 0000000..bc89f1c --- /dev/null +++ b/src/main/java/world/bentobox/level/calculators/UltimateStackerCalc.java @@ -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); + } + } + } +} + diff --git a/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java b/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java index 3d1420e..bf471de 100644 --- a/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java +++ b/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java @@ -111,7 +111,11 @@ public class IslandLevelCommand extends CompositeCommand { } // Send player how many points are required to reach next island level if (results.getPointsToNextLevel() >= 0) { - user.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(results.getPointsToNextLevel())); + user.sendMessage("island.level.required-points-to-next-level", + "[points]", String.valueOf(results.getPointsToNextLevel()), + "[progress]", String.valueOf(this.addon.getSettings().getLevelCost()-results.getPointsToNextLevel()), + "[levelcost]", String.valueOf(this.addon.getSettings().getLevelCost()) + ); } // Tell other team members if (results.getLevel() != oldLevel) { diff --git a/src/main/java/world/bentobox/level/util/CachedData.java b/src/main/java/world/bentobox/level/util/CachedData.java new file mode 100644 index 0000000..54f61b6 --- /dev/null +++ b/src/main/java/world/bentobox/level/util/CachedData.java @@ -0,0 +1,30 @@ +package world.bentobox.level.util; + +import java.time.Instant; +import java.util.Map; + +/** + * Cache for top tens + */ +public class CachedData { + private Map cachedMap; + private Instant lastUpdated; + + public CachedData(Map cachedMap, Instant lastUpdated) { + this.cachedMap = cachedMap; + this.lastUpdated = lastUpdated; + } + + public Map getCachedMap() { + return cachedMap; + } + + public Instant getLastUpdated() { + return lastUpdated; + } + + public void updateCache(Map newMap, Instant newUpdateTime) { + this.cachedMap = newMap; + this.lastUpdated = newUpdateTime; + } +} diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index f71b52d..b389a69 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -42,8 +42,8 @@ island: estimated-wait: "&a Estimated wait: [number] seconds" in-queue: "&a You are number [number] in the queue" island-level-is: "&a Island level is &b[level]" - required-points-to-next-level: "&a [points] points required until the next level" - deaths: "&c([number] deaths)" + required-points-to-next-level: "&a Level progress: &6 [progress]&b /&e [levelcost] &a points" + deaths: "&c ([number] deaths)" cooldown: "&c You must wait &b[time] &c seconds until you can do that again" in-progress: "&6 Island level calculation is in progress..." time-out: "&c The level calculation took too long. Please try again later." diff --git a/src/main/resources/panels/detail_panel.yml b/src/main/resources/panels/detail_panel.yml index da08f6c..74fb90e 100644 --- a/src/main/resources/panels/detail_panel.yml +++ b/src/main/resources/panels/detail_panel.yml @@ -121,7 +121,7 @@ detail_panel: # TIPPED_ARROW:INSTANT_DAMAGE:2::LINGER:2 # TIPPED_ARROW:JUMP:2:NOTEXTENDED:NOSPLASH:1 # TIPPED_ARROW:WEAKNESS::::1 - any weakness enchantment - icon: TIPPED_ARROW:INSTANT_HEAL::::1 + icon: tipped_arrow{CustomPotionColor:11546150} title: level.gui.buttons.previous.name description: level.gui.buttons.previous.description data: @@ -139,7 +139,7 @@ detail_panel: 7: material_button 8: material_button 9: - icon: TIPPED_ARROW:JUMP::::1 + icon: tipped_arrow{CustomPotionColor:8439583} title: level.gui.buttons.next.name description: level.gui.buttons.next.description data: diff --git a/src/main/resources/panels/value_panel.yml b/src/main/resources/panels/value_panel.yml index c173313..ac06443 100644 --- a/src/main/resources/panels/value_panel.yml +++ b/src/main/resources/panels/value_panel.yml @@ -64,7 +64,7 @@ value_panel: 8: material_button 3: 1: - icon: TIPPED_ARROW:INSTANT_HEAL::::1 + icon: tipped_arrow{CustomPotionColor:11546150} title: level.gui.buttons.previous.name description: level.gui.buttons.previous.description data: @@ -82,7 +82,7 @@ value_panel: 7: material_button 8: material_button 9: - icon: TIPPED_ARROW:JUMP::::1 + icon: tipped_arrow{CustomPotionColor:8439583} title: level.gui.buttons.next.name description: level.gui.buttons.next.description data: