From 0ce89dea7f99154277e8e449d00d52af3e7949f1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 3 Jul 2021 15:50:37 -0700 Subject: [PATCH 001/106] Version 2.7.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b51afd5..5e0f441 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ -LOCAL - 2.7.0 + 2.7.1 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From 383ede3d592e1b89afbe930e18f9344f8ffa6bd0 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 10 Jul 2021 22:04:17 -0700 Subject: [PATCH 002/106] Version 2.7.2 --- pom.xml | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/pom.xml b/pom.xml index 5e0f441..b142e86 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ -LOCAL - 2.7.1 + 2.7.2 BentoBoxWorld_Level bentobox-world https://sonarcloud.io @@ -111,30 +111,6 @@ - - sonar - - https://sonarcloud.io - bentobox-world - - - - - org.sonarsource.scanner.maven - sonar-maven-plugin - 3.6.0.1398 - - - verify - - sonar - - - - - - - From 4661bcd109b1bb2622a2007f45d0545ca5fdfadf Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 24 Jul 2021 14:26:45 -0700 Subject: [PATCH 003/106] Use Java 9's takeWhile --- .../bentobox/level/CustomSpliterator.java | 38 ------------------- .../world/bentobox/level/LevelsManager.java | 15 +------- 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 src/main/java/world/bentobox/level/CustomSpliterator.java diff --git a/src/main/java/world/bentobox/level/CustomSpliterator.java b/src/main/java/world/bentobox/level/CustomSpliterator.java deleted file mode 100644 index e7e31c8..0000000 --- a/src/main/java/world/bentobox/level/CustomSpliterator.java +++ /dev/null @@ -1,38 +0,0 @@ -package world.bentobox.level; - -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** - * Java 8 version of Java 9's forWhile - * https://www.baeldung.com/java-break-stream-foreach - * @author tastybento - * - * @param - */ -public class CustomSpliterator extends Spliterators.AbstractSpliterator { - - private Spliterator splitr; - private Predicate predicate; - private boolean isMatched = true; - - public CustomSpliterator(Spliterator splitr, Predicate predicate) { - super(splitr.estimateSize(), 0); - this.splitr = splitr; - this.predicate = predicate; - } - - @Override - public synchronized boolean tryAdvance(Consumer consumer) { - boolean hadNext = splitr.tryAdvance(elem -> { - if (predicate.test(elem) && isMatched) { - consumer.accept(elem); - } else { - isMatched = false; - } - }); - return hadNext && isMatched; - } -} \ No newline at end of file diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index 7f0b366..e5e1edb 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -14,10 +14,8 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.stream.StreamSupport; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -444,18 +442,7 @@ public int getRank(@NonNull World world, UUID uuid) { .filter(e -> addon.getIslands().isOwner(world, e.getKey())) .filter(l -> l.getValue() > 0) .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); - return takeWhile(stream, x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).collect(Collectors.toList()).size() + 1; - } - - /** - * Java 8's version of Java 9's takeWhile - * @param stream - * @param predicate - * @return stream - */ - public static Stream takeWhile(Stream stream, Predicate predicate) { - CustomSpliterator customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate); - return StreamSupport.stream(customSpliterator, false); + return stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).collect(Collectors.toList()).size() + 1; } /** From 76a2688556a6f26a6834f0bf569da38b9b02c97c Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 24 Jul 2021 14:54:06 -0700 Subject: [PATCH 004/106] Added placeholder %Level_[gamemode]_rank_value Fixes https://github.com/BentoBoxWorld/Level/issues/228 --- src/main/java/world/bentobox/level/Level.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 68fe28f..cd790bc 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -36,6 +36,7 @@ import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.JoinLeaveListener; import world.bentobox.level.objects.LevelsData; +import world.bentobox.level.objects.TopTenData; import world.bentobox.level.requests.LevelRequestHandler; import world.bentobox.level.requests.TopTenRequestHandler; @@ -206,6 +207,9 @@ private void registerPlaceholders(GameModeAddon gm) { gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank)); } + // Personal rank + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_rank_value", u -> getRankValue(gm.getOverWorld(), u)); } String getRankName(World world, int rank) { @@ -228,8 +232,23 @@ String getRankLevel(World world, int rank) { .orElse(null)); } + /** + * Return the rank of the player in a world + * @param world world + * @param user player + * @return rank where 1 is the top rank. + */ + String getRankValue(World world, User user) { + if (user == null) { + return ""; + } + // Get the island level for this user + long level = getManager().getIslandLevel(world, user.getUniqueId()); + return String.valueOf(getManager().getTopTenLists().getOrDefault(world, new TopTenData(world)).getTopTen().values().stream().filter(l -> l > level).count() + 1); + } + String getVisitedIslandLevel(GameModeAddon gm, User user) { - if (!gm.inWorld(user.getLocation())) return ""; + if (user == null || !gm.inWorld(user.getLocation())) return ""; return getIslands().getIslandAt(user.getLocation()) .map(island -> getManager().getIslandLevelString(gm.getOverWorld(), island.getOwner())) .orElse("0"); From d55f66f868858bd1f87da0092788d159c405e54e Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 8 Aug 2021 11:09:36 -0700 Subject: [PATCH 005/106] No save on disable (#231) * Release 2.6.4 * Remove saving to database on disable. https://github.com/BentoBoxWorld/Level/issues/229 First, the top ten tables are never actually used or loaded. They are created in memory by loading the island levels. So there is no reason to keep saving them. Second, the island level data is saved every time it is changed, so there is no need to save all of the cache on exit. * Fixes tests --- src/main/java/world/bentobox/level/Level.java | 5 ---- .../world/bentobox/level/LevelsManager.java | 28 ++----------------- .../bentobox/level/LevelsManagerTest.java | 12 ++------ 3 files changed, 4 insertions(+), 41 deletions(-) diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index cd790bc..6e2b287 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -275,11 +275,6 @@ private void registerCommands(GameModeAddon gm) { public void onDisable() { // Stop the pipeline this.getPipeliner().stop(); - // Save player data and the top tens - if (manager != null) { - manager.save(); - } - } private void loadBlockSettings() { diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index e5e1edb..cfa2247 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -62,8 +62,6 @@ public class LevelsManager { private final Database handler; // A cache of island levels. private final Map levelsCache; - - private final Database topTenHandler; // Top ten lists private final Map topTenLists; // Background @@ -77,8 +75,6 @@ public LevelsManager(Level addon) { // Set up the database handler to store and retrieve data // Note that these are saved by the BentoBox database handler = new Database<>(addon, IslandLevels.class); - // Top Ten handler - topTenHandler = new Database<>(addon, TopTenData.class); // Initialize the cache levelsCache = new HashMap<>(); // Initialize top ten lists @@ -179,8 +175,6 @@ public CompletableFuture calculateLevel(UUID targetPlayer, Island islan } // Save result setIslandResults(island.getWorld(), island.getOwner(), r); - // Save top ten - addon.getManager().saveTopTen(island.getWorld()); // Save the island scan details result.complete(r); }); @@ -462,15 +456,14 @@ boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) { void loadTopTens() { topTenLists.clear(); Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> { - addon.log("Generating Top Ten Tables"); + 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("Loaded top ten for " + w.getName()); - this.saveTopTen(w); + addon.log("Generated rankings for " + w.getName()); }); }); @@ -484,27 +477,10 @@ void loadTopTens() { public void removeEntry(World world, UUID uuid) { if (topTenLists.containsKey(world)) { topTenLists.get(world).getTopTen().remove(uuid); - topTenHandler.saveObjectAsync(topTenLists.get(world)); } } - /** - * Saves all player data and the top ten - */ - public void save() { - levelsCache.values().forEach(handler::saveObjectAsync); - topTenLists.values().forEach(topTenHandler::saveObjectAsync); - } - - /** - * Save the top ten for world - * @param world - world - */ - public void saveTopTen(World world) { - topTenHandler.saveObjectAsync(topTenLists.get(world)); - } - /** * Set an initial island level * @param island - the island to set. Must have a non-null world diff --git a/src/test/java/world/bentobox/level/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java index 3d9b26b..a8541e1 100644 --- a/src/test/java/world/bentobox/level/LevelsManagerTest.java +++ b/src/test/java/world/bentobox/level/LevelsManagerTest.java @@ -383,8 +383,8 @@ public void testLoadTopTens() { Bukkit.getScheduler(); verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture()); task.getValue().run(); - verify(addon).log(eq("Generating Top Ten Tables")); - verify(addon).log(eq("Loaded top ten for bskyblock-world")); + verify(addon).log(eq("Generating rankings")); + verify(addon).log(eq("Generated rankings for bskyblock-world")); } @@ -401,14 +401,6 @@ public void testRemoveEntry() { assertFalse(tt.containsKey(uuid)); } - /** - * Test method for {@link world.bentobox.level.LevelsManager#save()}. - */ - @Test - public void testSave() { - lm.save(); - } - /** * Test method for {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}. */ From bd6c264f4ddfb1b7aab1f8fab47d0f801b723374 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 9 Aug 2021 20:00:55 -0700 Subject: [PATCH 006/106] Rosestacker (#232) * Add support for RoseStacker 1.3.0 --- pom.xml | 13 ++++ src/main/java/world/bentobox/level/Level.java | 15 +++- .../calculators/IslandLevelCalculator.java | 75 ++++++++++++------- 3 files changed, 74 insertions(+), 29 deletions(-) diff --git a/pom.xml b/pom.xml index b142e86..c276d63 100644 --- a/pom.xml +++ b/pom.xml @@ -131,6 +131,11 @@ jitpack.io https://jitpack.io + + + rosewood-repo + https://repo.rosewooddev.io/repository/public/ + @@ -171,6 +176,7 @@ com.github.OmerBenGera WildStackerAPI b18 + provided -LOCAL - 2.7.2 + 2.8.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From 1b29f7f6ac187060db4d2018d663492a68c0c5f7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 6 Sep 2021 11:57:32 -0700 Subject: [PATCH 009/106] Added new placeholders %Level_%gamemode%_top_island_name_%rank% - lists the island name %Level_%gamemode%_top_island_members_%rank% - a comma separated list of team members https://github.com/BentoBoxWorld/Level/issues/224 https://github.com/BentoBoxWorld/Level/issues/211 https://github.com/BentoBoxWorld/Level/issues/132 https://github.com/BentoBoxWorld/Level/issues/107 https://github.com/BentoBoxWorld/Level/issues/105 --- src/main/java/world/bentobox/level/Level.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index df3004d..1416194 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -3,8 +3,11 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.World; @@ -208,6 +211,12 @@ private void registerPlaceholders(GameModeAddon gm) { // Name getPlugin().getPlaceholdersManager().registerPlaceholder(this, gm.getDescription().getName().toLowerCase() + "_top_name_" + i, u -> getRankName(gm.getOverWorld(), rank)); + // Island Name + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_top_island_name_" + i, u -> getRankIslandName(gm.getOverWorld(), rank)); + // Members + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_top_members_" + i, u -> getRankMembers(gm.getOverWorld(), rank)); // Level getPlugin().getPlaceholdersManager().registerPlaceholder(this, gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank)); @@ -224,6 +233,37 @@ String getRankName(World world, int rank) { return getPlayers().getName(getManager().getTopTen(world, TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null)); } + String getRankIslandName(World world, int rank) { + if (rank < 1) rank = 1; + if (rank > TEN) rank = TEN; + UUID owner = getManager().getTopTen(world, TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null); + if (owner != null) { + Island island = getIslands().getIsland(world, owner); + if (island != null) { + return island.getName() == null ? "" : island.getName(); + } + } + return ""; + } + + String getRankMembers(World world, int rank) { + if (rank < 1) rank = 1; + if (rank > TEN) rank = TEN; + UUID owner = getManager().getTopTen(world, TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null); + if (owner != null) { + Island island = getIslands().getIsland(world, owner); + if (island != null) { + // Sort members by rank + return island.getMembers().entrySet().stream() + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) + .map(Map.Entry::getKey) + .map(getPlayers()::getName) + .collect(Collectors.joining(",")); + } + } + return ""; + } + String getRankLevel(World world, int rank) { if (rank < 1) rank = 1; if (rank > TEN) rank = TEN; From d212fcee9953b99147026e7082011a189f0c625e Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 1 Oct 2021 17:40:07 -0700 Subject: [PATCH 010/106] Update to BentoBox API 1.18 --- pom.xml | 2 +- .../level/calculators/IslandLevelCalculator.java | 2 +- .../level/events/IslandLevelCalculatedEvent.java | 14 ++++++++++++++ .../bentobox/level/events/IslandPreLevelEvent.java | 13 +++++++++++++ src/main/resources/addon.yml | 2 +- 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 6e2936c..1a1d333 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 2.0.9 1.17-R0.1-SNAPSHOT - 1.17.0 + 1.18.0-SNAPSHOT ${build.version}-SNAPSHOT diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 5dd2523..7052077 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -203,7 +203,7 @@ public IslandLevelCalculator(Level addon, Island island, CompletableFuture Date: Fri, 1 Oct 2021 17:41:39 -0700 Subject: [PATCH 011/106] Open up modules for testing access. --- pom.xml | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1a1d333..276cf7d 100644 --- a/pom.xml +++ b/pom.xml @@ -249,7 +249,33 @@ 3.0.0-M5 - --illegal-access=permit + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.math=ALL-UNNAMED + --add-opens java.base/java.io=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens + java.base/java.util.stream=ALL-UNNAMED + --add-opens java.base/java.text=ALL-UNNAMED + --add-opens + java.base/java.util.regex=ALL-UNNAMED + --add-opens + java.base/java.nio.channels.spi=ALL-UNNAMED + --add-opens java.base/sun.nio.ch=ALL-UNNAMED + --add-opens java.base/java.net=ALL-UNNAMED + --add-opens + java.base/java.util.concurrent=ALL-UNNAMED + --add-opens java.base/sun.nio.fs=ALL-UNNAMED + --add-opens java.base/sun.nio.cs=ALL-UNNAMED + --add-opens java.base/java.nio.file=ALL-UNNAMED + --add-opens + java.base/java.nio.charset=ALL-UNNAMED + --add-opens + java.base/java.lang.reflect=ALL-UNNAMED + --add-opens + java.logging/java.util.logging=ALL-UNNAMED + --add-opens java.base/java.lang.ref=ALL-UNNAMED + --add-opens java.base/java.util.jar=ALL-UNNAMED + --add-opens java.base/java.util.zip=ALL-UNNAMED From 4c59d4d4ae7d54c563c565b8d3d1a8cf91d714e3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 4 Oct 2021 23:01:57 -0700 Subject: [PATCH 012/106] Back support for BentoBox 1.16.5. --- pom.xml | 4 ++-- src/main/resources/addon.yml | 2 +- src/main/resources/plugin.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 276cf7d..b3f602a 100644 --- a/pom.xml +++ b/pom.xml @@ -58,8 +58,8 @@ 2.0.9 - 1.17-R0.1-SNAPSHOT - 1.18.0-SNAPSHOT + 1.16.5-R0.1-SNAPSHOT + 1.16.5-SNAPSHOT ${build.version}-SNAPSHOT diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index f669a12..d7e5b6d 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -2,7 +2,7 @@ name: Level main: world.bentobox.level.Level version: ${version}${build.number} icon: DIAMOND -api-version: 1.18 +api-version: 1.16.5 authors: tastybento diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c5bd022..f4d8c68 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: Pladdon main: world.bentobox.level.LevelPladdon version: ${version} -api-version: "1.17" +api-version: "1.16" description: Level Addon author: tastybento depend: From 11618085ff255037acff425692e3a3bf66fc57f9 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 21 Nov 2021 08:14:35 -0800 Subject: [PATCH 013/106] Version 2.8.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b3f602a..b57a4d0 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ -LOCAL - 2.8.0 + 2.8.1 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From b1d117d344b4597d22af286baf7ff054d177d964 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 21 Nov 2021 20:08:05 -0800 Subject: [PATCH 014/106] Speeds up level calculation by doing more chunk scans async. If chests are scanned, then it will take longer because these have to be done sync. https://github.com/BentoBoxWorld/Level/issues/243 --- .../calculators/IslandLevelCalculator.java | 188 ++++++++++++++---- .../bentobox/level/calculators/Pipeliner.java | 48 ++--- 2 files changed, 161 insertions(+), 75 deletions(-) diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 7052077..2a1ac3a 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -1,14 +1,17 @@ package world.bentobox.level.calculators; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; @@ -16,6 +19,7 @@ import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.ChunkSnapshot; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Tag; import org.bukkit.World; @@ -26,8 +30,7 @@ import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Slab; import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; -import org.eclipse.jdt.annotation.Nullable; +import org.bukkit.scheduler.BukkitTask; import com.bgsoftware.wildstacker.api.WildStackerAPI; import com.bgsoftware.wildstacker.api.objects.StackedBarrel; @@ -44,10 +47,19 @@ import world.bentobox.bentobox.util.Pair; import world.bentobox.bentobox.util.Util; import world.bentobox.level.Level; +import world.bentobox.level.calculators.Results.Result; public class IslandLevelCalculator { private static final String LINE_BREAK = "=================================="; public static final long MAX_AMOUNT = 10000; + 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); + private static final int CHUNKS_TO_SCAN = 100; /** * Method to evaluate a mathematical equation @@ -156,6 +168,10 @@ void nextChar() { private final boolean zeroIsland; private final Map worlds = new EnumMap<>(Environment.class); private final int seaHeight; + private final List stackedBlocks = new ArrayList<>(); + private final Set chestBlocks = new HashSet<>(); + private BukkitTask finishTask; + /** * Constructor to get the level for an island @@ -339,17 +355,35 @@ private int getValue(Material md) { * @param z - chunk z coordinate * @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether */ - private CompletableFuture getWorldChunk(Environment env, int x, int z) { + private CompletableFuture> getWorldChunk(Environment env, Queue> pairList) { if (worlds.containsKey(env)) { - CompletableFuture r2 = new CompletableFuture<>(); + CompletableFuture> r2 = new CompletableFuture<>(); + List chunkList = new ArrayList<>(); + World world = worlds.get(env); // Get the chunk, and then coincidentally check the RoseStacker - Util.getChunkAtAsync(worlds.get(env), x, z, true).thenAccept(chunk -> roseStackerCheck(r2, chunk)); + loadChunks(r2, world, pairList, chunkList); return r2; } - return CompletableFuture.completedFuture(null); + return CompletableFuture.completedFuture(Collections.emptyList()); } - private void roseStackerCheck(CompletableFuture r2, Chunk chunk) { + private void loadChunks(CompletableFuture> r2, World world, Queue> pairList, + List chunkList) { + if (pairList.isEmpty()) { + r2.complete(chunkList); + return; + } + Pair p = pairList.poll(); + Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { + if (chunk != null) { + chunkList.add(chunk); + roseStackerCheck(chunk); + } + loadChunks(r2, world, pairList, chunkList); // Iteration + }); + } + + private void roseStackerCheck(Chunk chunk) { if (addon.isRoseStackersEnabled()) { RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { // Blocks below sea level can be scored differently @@ -360,7 +394,6 @@ private void roseStackerCheck(CompletableFuture r2, Chunk chunk) { } }); } - r2.complete(chunk); } /** @@ -385,11 +418,10 @@ private int limitCount(Material md) { /** * Count the blocks on the island - * @param result - the CompletableFuture that should be completed when this scan is done - * @param chunkSnapshot - the chunk to scan + * @param chunk chunk to scan */ - private void scanAsync(CompletableFuture result, ChunkSnapshot chunkSnapshot, Chunk chunk) { - List stackedBlocks = new ArrayList<>(); + private void scanAsync(Chunk chunk) { + ChunkSnapshot chunkSnapshot = chunk.getChunkSnapshot(); 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 (chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || chunkSnapshot.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) { @@ -413,29 +445,17 @@ private void scanAsync(CompletableFuture result, ChunkSnapshot chunkSna } // Hook for Wild Stackers (Blocks Only) - this has to use the real chunk if (addon.isStackersEnabled() && blockData.getMaterial() == Material.CAULDRON) { - stackedBlocks.add(new Vector(x,y,z)); + stackedBlocks.add(new Location(chunk.getWorld(), x + chunkSnapshot.getX() * 16,y,z + chunkSnapshot.getZ() * 16)); + } + // Scan chests + if (addon.getSettings().isIncludeChests() && CHESTS.contains(blockData.getMaterial())) { + chestBlocks.add(chunk); } // Add the value of the block's material checkBlock(blockData.getMaterial(), belowSeaLevel); } } } - // Complete the future - this must go back onto the primary thread to exit async otherwise subsequent actions will be async - Bukkit.getScheduler().runTask(addon.getPlugin(),() -> { - // Deal with any stacked blocks - stackedBlocks.forEach(v -> { - Block cauldronBlock = chunk.getBlock(v.getBlockX(), v.getBlockY(), v.getBlockZ()); - boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; - if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) { - StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock); - int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock); - for (int _x = 0; _x < barrelAmt; _x++) { - checkBlock(barrel.getType(), belowSeaLevel); - } - } - }); - result.complete(true); - }); } /** @@ -473,20 +493,21 @@ private void countItemStack(ItemStack i) { /** * Scan the chunk chests and count the blocks - * @param chunk - the chunk to scan + * @param chunks - the chunk 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 */ - private CompletableFuture scanChunk(@Nullable Chunk chunk) { + private CompletableFuture scanChunk(List chunks) { // If the chunk hasn't been generated, return - if (chunk == null) return CompletableFuture.completedFuture(false); - // Scan chests - if (addon.getSettings().isIncludeChests()) { - scanChests(chunk); + if (chunks == null || chunks.isEmpty()) { + return CompletableFuture.completedFuture(false); } // Count blocks in chunk CompletableFuture result = new CompletableFuture<>(); - ChunkSnapshot snapshot = chunk.getChunkSnapshot(); - Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> scanAsync(result, snapshot, chunk)); + + Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> { + chunks.forEach(chunk -> scanAsync(chunk)); + Bukkit.getScheduler().runTask(addon.getPlugin(),() -> result.complete(true)); + }); return result; } @@ -501,16 +522,23 @@ public CompletableFuture scanNextChunk() { return CompletableFuture.completedFuture(false); } // Retrieve and remove from the queue - Pair p = chunksToCheck.poll(); + 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 - getWorldChunk(Environment.THE_END, p.x, p.z).thenAccept(endChunk -> - scanChunk(endChunk).thenAccept(b -> - getWorldChunk(Environment.NETHER, p.x, p.z).thenAccept(netherChunk -> - scanChunk(netherChunk).thenAccept(b2 -> - getWorldChunk(Environment.NORMAL, p.x, p.z).thenAccept(normalChunk -> - scanChunk(normalChunk).thenAccept(b3 -> + // 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())))) ) @@ -591,4 +619,76 @@ public void tidyUp() { boolean isNotZeroIsland() { return !zeroIsland; } + + public void scanIsland(Pipeliner pipeliner) { + // Scan the next chunk + scanNextChunk().thenAccept(r -> { + 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(r) && !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(); + }); + } + } + + private void handleStackedBlocks() { + // Deal with any stacked blocks + Iterator it = stackedBlocks.iterator(); + while (it.hasNext()) { + Location v = it.next(); + Util.getChunkAtAsync(v).thenAccept(c -> { + Block cauldronBlock = v.getBlock(); + boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; + if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) { + StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock); + int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock); + for (int _x = 0; _x < barrelAmt; _x++) { + checkBlock(barrel.getType(), belowSeaLevel); + } + } + it.remove(); + }); + } + } } diff --git a/src/main/java/world/bentobox/level/calculators/Pipeliner.java b/src/main/java/world/bentobox/level/calculators/Pipeliner.java index 83af5e8..6daacfb 100644 --- a/src/main/java/world/bentobox/level/calculators/Pipeliner.java +++ b/src/main/java/world/bentobox/level/calculators/Pipeliner.java @@ -50,7 +50,7 @@ public Pipeliner(Level addon) { if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) { inProcessQueue.put(iD, System.currentTimeMillis()); // Start the scanning of a island with the first chunk - scanChunk(iD); + scanIsland(iD); } } }, 1L, 10L); @@ -71,42 +71,14 @@ public int getIslandsInQueue() { * Scans one chunk of an island and adds the results to a results object * @param iD */ - private void scanChunk(IslandLevelCalculator iD) { + private void scanIsland(IslandLevelCalculator iD) { if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned() || task.isCancelled()) { // Island is deleted, so finish early with nothing inProcessQueue.remove(iD); iD.getR().complete(null); return; } - // Scan the next chunk - iD.scanNextChunk().thenAccept(r -> { - if (!Bukkit.isPrimaryThread()) { - addon.getPlugin().logError("scanChunk not on Primary Thread!"); - } - // Timeout check - if (System.currentTimeMillis() - inProcessQueue.get(iD) > addon.getSettings().getCalculationTimeout() * 60000) { - // Done - inProcessQueue.remove(iD); - iD.getR().complete(new Results(Result.TIMEOUT)); - addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout() + "m for island: " + iD.getIsland()); - if (!iD.isNotZeroIsland()) { - addon.logError("Island level was being zeroed."); - } - return; - } - if (Boolean.TRUE.equals(r) || task.isCancelled()) { - // scanNextChunk returns true if there are more chunks to scan - scanChunk(iD); - } else { - // Done - inProcessQueue.remove(iD); - // Chunk finished - // This was the last chunk - iD.tidyUp(); - iD.getR().complete(iD.getResults()); - } - }); - + iD.scanIsland(this); } @@ -169,6 +141,20 @@ public void stop() { this.toProcessQueue.clear(); } + /** + * @return the inProcessQueue + */ + protected Map getInProcessQueue() { + return inProcessQueue; + } + + /** + * @return the task + */ + protected BukkitTask getTask() { + return task; + } + From 4de5b80ab4a19b8d6d1dbeb9292e1de6575bf41c Mon Sep 17 00:00:00 2001 From: Huynh Tien Date: Sat, 27 Nov 2021 04:10:53 +0700 Subject: [PATCH 015/106] add Vietnamese (#240) --- src/main/resources/locales/vi.yml | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/main/resources/locales/vi.yml diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml new file mode 100644 index 0000000..8ff6d15 --- /dev/null +++ b/src/main/resources/locales/vi.yml @@ -0,0 +1,56 @@ +# +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +admin: + level: + parameters: + description: tính toán cấp độ đảo của người chơi + sethandicap: + parameters: + description: chỉnh cấp của các đảo bắt đầu + changed: '&a Cấp đảo bắt đầu đã chỉnh từ [number] thành [new_number].' + invalid-level: '&c Cấp không xác định. Hãy dùng số nguyên.' + levelstatus: + description: xem bao nhiêu đảo đang trong hàng chờ được quét + islands-in-queue: '&a Đảo đang chờ: [number]' + top: + description: xem bảng xếp hạng TOP 10 + unknown-world: '&c Thế giới không xác định!' + display: '&f[rank]. &a[name] &7- &b[level]' + remove: + description: xoá người khỏi TOP 10 + parameters: +island: + level: + parameters: '[người chơi]' + description: tính toán cấp đảo của bạn hoặc xem cấp đảo của [người chơi] + calculating: '&a Đang tính toán cấp đảo...' + estimated-wait: '&a Thời gian còn lại: [number] giây' + in-queue: '&a Bạn đang ở vị trí [number] trong hàng chờ' + island-level-is: '&a Cấp đảo là &b[level]' + required-points-to-next-level: '&a Cần [points] điểm để qua cấp tiếp theo' + deaths: '&c([number] lần chết)' + cooldown: '&c Bạn phải chờ &b[time] &c giây trước khi có thể làm điều đó' + in-progress: '&6 Quá trình tính toán cấp đảo đang thực hiện...' + time-out: '&c Tính toán cấp đảo quá lâu. Vui lòng thử lại sau.' + top: + description: xem TOP 10 + gui-title: '&a TOP 10' + gui-heading: '&6[name]: &B[rank]' + island-level: '&b Cấp [level]' + warp-to: '&A Đang dịch chuyển đến đảo của [name]' + level-details: + above-sea-level-blocks: Khối Trên Mực Nước Biển + spawners: Lồng Sinh Quái + underwater-blocks: Khối Dưới Nước + all-blocks: Toàn Bộ Khối + no-island: '&c Không có đảo!' + names-island: 'đảo của [name]' + syntax: '[name] x [number]' + hint: '&c Chạy lệnh cấp để xem báo cáo khối' + value: + description: xem giá trị của bất kì khối + success: '&7 Giá trị của khối này là: &e[value]' + success-underwater: '&7 Giá trị của khối này dưới mực nước biển: &e[value]' + empty-hand: '&c Không có khối nào trên tay bạn' + no-value: '&c Vật phẩm này vô giá trị.' From cc977d8562ae8f6eb9ee4f2857793ef28c3893ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n?= <44579213+Rubenicos@users.noreply.github.com> Date: Fri, 26 Nov 2021 15:11:30 -0600 Subject: [PATCH 016/106] Raw island level placeholder (#241) --- src/main/java/world/bentobox/level/Level.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 1416194..79c7a5e 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -197,6 +197,9 @@ private void registerPlaceholders(GameModeAddon gm) { getPlugin().getPlaceholdersManager().registerPlaceholder(this, gm.getDescription().getName().toLowerCase() + "_island_level", user -> getManager().getIslandLevelString(gm.getOverWorld(), user.getUniqueId())); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_island_level_raw", + user -> String.valueOf(getManager().getIslandLevel(gm.getOverWorld(), user.getUniqueId()))); getPlugin().getPlaceholdersManager().registerPlaceholder(this, gm.getDescription().getName().toLowerCase() + "_points_to_next_level", user -> getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId())); From 4a21e4b30c9db689f5bbd6638df953778e5bb0aa Mon Sep 17 00:00:00 2001 From: Pierre Dedrie Date: Sun, 19 Dec 2021 02:22:28 +0100 Subject: [PATCH 017/106] Changed IslandLevelCalculator minHeight to world minHeight for negative blocks height support since 1.18. (#246) --- .../world/bentobox/level/calculators/IslandLevelCalculator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 2a1ac3a..845ba5c 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -433,7 +433,7 @@ private void scanAsync(Chunk chunk) { continue; } // Only count to the highest block in the world for some optimization - for (int y = 0; y < chunk.getWorld().getMaxHeight(); y++) { + for (int y = chunk.getWorld().getMinHeight(); y < chunk.getWorld().getMaxHeight(); y++) { BlockData blockData = chunkSnapshot.getBlockData(x, y, z); boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight; // Slabs can be doubled, so check them twice From 5ce71798a6bc86d01b6ec8957c3ecdc91128de93 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Dec 2021 08:39:09 -0800 Subject: [PATCH 018/106] Version 2.9.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b57a4d0..5766363 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ -LOCAL - 2.8.1 + 2.9.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From cbaf14e5f053b5a8fd7bce76af24f5a8bf18e393 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Sat, 1 Jan 2022 18:35:59 -0800 Subject: [PATCH 019/106] Chinese Translation (#249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translate zh-CN.yml via GitLocalize * Translate zh-CN.yml via GitLocalize Co-authored-by: mt-gitlocalize Co-authored-by: 织梦 <493733933@qq.com> --- src/main/resources/locales/zh-CN.yml | 46 ++++++++++++++++++---------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index 00f291d..f7e4348 100755 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -1,39 +1,53 @@ -########################################################################################### -# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # -# the one at http://yaml-online-parser.appspot.com # -########################################################################################### - +--- admin: level: parameters: "" - description: "计算某玩家的岛屿等级" + description: 计算某玩家的岛屿等级 + sethandicap: + parameters: "<玩家> <让分>" + description: 设置孤岛障碍,通常是首发岛的水平 + changed: "&a初始离岛差由[number]更改为[new_number]。" + invalid-level: "&c差点。使用整数。" + levelstatus: + description: 显示要扫描的队列中有多少岛 + islands-in-queue: "&a列队中的列队:[人数]" top: - description: "显示前十名" + description: 显示前十名 unknown-world: "&c未知世界!" display: "&f[rank]. &a[name] &7- &b[level]" remove: - description: "将玩家移出前十" + description: 将玩家移出前十 parameters: "" - island: - level: + level: parameters: "[player]" - description: "计算你或玩家 [player] 的岛屿等级" - calculating: "&a计算等级中..." + description: 计算你或玩家 [player] 的岛屿等级 + calculating: "&a计算等级中..." + estimated-wait: "&a 预计等待时间: [number] 秒" + in-queue: "&a您是队列中的数字[number]" island-level-is: "&a岛屿等级为 &b[level]" required-points-to-next-level: "&a还需 [points] 才能升到下一级" deaths: "&c([number] 次死亡)" cooldown: "&c再等 &b[time] &c秒才能再次使用" - + in-progress: "&6岛级计算正在进行中..." + time-out: "&c等级计算花了太长时间。请稍后再试。" top: - description: "显示前十名" + description: 显示前十名 gui-title: "&a前十" gui-heading: "&6[name]: &B[rank]" island-level: "&B等级 [level]" warp-to: "&A正传送到 [name] 的岛屿" - + level-details: + above-sea-level-blocks: 海拔以上的街区 + spawners: 产卵者 + underwater-blocks: 水下积木 + all-blocks: 所有块 + no-island: "&c没有岛!" + names-island: "[名字]的小岛" + syntax: "[名称] x [数字]" + hint: "&c运行级别以查看阻止报告" value: - description: "查看某方块的价值" + description: 查看某方块的价值 success: "&7本方块的价值: &e[value]" success-underwater: "&7本方块的水下价值: &e[value]" empty-hand: "&c你手里没有方块" From 34da24d719878f1c40ad7b31dd793a6c0b998ff2 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Sat, 1 Jan 2022 18:36:45 -0800 Subject: [PATCH 020/106] Translate id.yml via GitLocalize (#250) Co-authored-by: Nathan Adhitya --- src/main/resources/locales/id.yml | 57 ++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index 8b13789..62fec59 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -1 +1,56 @@ - +--- +admin: + level: + parameters: "" + description: hitung level pulau untuk player + sethandicap: + parameters: " " + description: mengatur handicap pulau, biasanya tingkat pulau pemula + changed: "& Handicap pulau awal diubah dari [number] menjadi [new_number]." + invalid-level: "& c Handicap tidak valid. Gunakan angka bulat." + levelstatus: + description: menunjukkan berapa pulau yang menunggu pindaian + islands-in-queue: "&a Pulau di dalam menunggu: [number]" + top: + description: menunjukkan daftar sepuluh besar + unknown-world: "&c World tidak ditemukan!" + display: "&f[rank]. &a[name] &7- &b[level]" + remove: + description: menghilangkan player dari sepuluh besar + parameters: "" +island: + level: + parameters: "[player]" + description: hitung level pulau Anda atau tunjukkan level [player] + calculating: "&a Menghitung level..." + estimated-wait: "&a Waktu tunggu perkiraan: [number] detik" + in-queue: "&aAnda berada pada posisi [number] pada urutan menunggu" + island-level-is: "&a Level pulau adalah &b[level]" + required-points-to-next-level: "&a [points] poin dibutuhkan hingga level selanjutnya" + deaths: "&c([number] kematian)" + cooldown: "&c Anda harus menunggu &b[time] &c detik sebelum Anda dapat melakukannya + lagi" + in-progress: "&6 Perhitungan level pulau sedang dijalankan..." + time-out: "&c Perhitungan level pulau terlalu lama. Coba lagi nanti." + top: + description: menunjukkan sepuluh besar + gui-title: "&a Sepuluh Besar" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Level [level]" + warp-to: "&A Warping ke pulau milik [name]" + level-details: + above-sea-level-blocks: Blok di atas permukaan laut + spawners: Spawner + underwater-blocks: Blok di bawah permukaan laut + all-blocks: Semua blok + no-island: "&c Tidak terdapat pulau!" + names-island: Pulau milik [name] + syntax: "[name] x [number]" + hint: "& c Jalankan perintah level untuk melihat laporan blok" + value: + description: menunjukkan nilai dari apapun blok + success: "&7 Nilai blok ini adalah: &e[value]" + success-underwater: "&7 Nilai blok ini di bawah permukaan laut adalah: &e[value]" + empty-hand: "&c Tidak ada balok di tangan Anda" + no-value: "&c Benda itu tidak bernilai." +'': false From 840a8c1d798615aed8b6b98ee5acaac7ce2d0881 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Sat, 1 Jan 2022 18:38:15 -0800 Subject: [PATCH 021/106] Translate fr.yml via GitLocalize (#251) Co-authored-by: organizatsiya --- src/main/resources/locales/fr.yml | 37 +++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index 3f906c0..a7fc26f 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -1,31 +1,54 @@ --- admin: level: - description: calcule le niveau d'île d'un joueur parameters: "" + description: calcule le niveau d'île d'un joueur + sethandicap: + parameters: " " + description: définir le handicap de l'île, généralement le niveau de l'île de + départ + changed: "&a le handicap initial de l'île est passé de [number] à [new_number]." + invalid-level: "&c Handicap non valide. Utilisez un nombre entier." + levelstatus: + description: affiche le nombre d'îles dans la file d'attente pour l'analyse + islands-in-queue: "&a Nombre d'Îles dans la file d'attente: [number]" top: description: affiche le top 10 des îles - display: "&f[rank]. &a[name] &7- &b[level]" unknown-world: "&cMonde inconnu." + display: "&f[rank]. &a[name] &7- &b[level]" remove: description: retire le joueur du top 10 parameters: "" island: level: - calculating: "&aCalcul du niveau en cours..." - deaths: "&c([number] morts)" + parameters: "[joueur]" description: calcule le niveau de votre île ou affiche le niveau d'un [joueur] + calculating: "&aCalcul du niveau en cours..." + estimated-wait: "&a Attente estimée: [number] seconds" + in-queue: "&a Vous êtes le numéro [number ] dans la file d'attente" island-level-is: "&aLe niveau d'île est &b[level]" - parameters: "[joueur]" required-points-to-next-level: "&a[points] points avant le prochain niveau" + deaths: "&c([number] morts)" cooldown: "&cVous devez attendre &b[time] &csecondes avant de pouvoir refaire cette action" + in-progress: "&6 Le calcul du niveau de l'île est en cours ..." + time-out: "&c Le calcul du niveau a pris trop de temps. Veuillez réessayer plus + tard." top: description: affiche le top 10 - gui-heading: "&6[name]: &B[rank]" gui-title: "&aTop 10" + gui-heading: "&6[name]: &B[rank]" island-level: "&BNiveau [level]" warp-to: "&ATéléportation vers l'île de [name]" + level-details: + above-sea-level-blocks: Blocs au-dessus du niveau de la mer + spawners: Spawners + underwater-blocks: Blocs en-dessous du niveau de la mer + all-blocks: Total des blocs + no-island: "&c Pas d'île!" + names-island: île de [name] + syntax: "[name] x [number]" + hint: "&c Exécuter level pour voir le rapport des blocs" value: description: affiche la valeur d'un bloc success: "&7Valeur de ce bloc : &e[value]" @@ -34,4 +57,4 @@ island: no-value: "&cCet objet n'a pas de valeur." meta: authors: - - plagoutte + '0': plagoutte From 488c6ac9d34cb66225ca1819685481727cc6b603 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Sat, 1 Jan 2022 18:39:15 -0800 Subject: [PATCH 022/106] Korean translation (#252) * Translate ko.yml via GitLocalize * Translate ko.yml via GitLocalize Co-authored-by: chickiyeah Co-authored-by: mt-gitlocalize --- src/main/resources/locales/ko.yml | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/resources/locales/ko.yml diff --git a/src/main/resources/locales/ko.yml b/src/main/resources/locales/ko.yml new file mode 100644 index 0000000..3b56843 --- /dev/null +++ b/src/main/resources/locales/ko.yml @@ -0,0 +1,51 @@ +--- +admin: + level: + parameters: "" + description: 플레이어의 섬레벨을 계산합니다 + sethandicap: + parameters: "<플레이어> <핸디캡>" + description: 섬 핸디캡을 설정하십시오. 일반적으로 시작 섬의 레벨 + changed: "& a 초기 아일랜드 핸디캡이 [번호]에서 [new_number] (으)로 변경되었습니다." + invalid-level: "& c 잘못된 핸디캡. 정수를 사용하십시오." + levelstatus: + description: 스캔 대기열에 몇 개의 섬이 있는지 표시 + islands-in-queue: "& a 대기열에있는 섬 : [번호]" + top: + unknown-world: "& c 알수없는 월드 입니다" + display: "&f[rank]. &a[name] &7-&b[level]" + remove: + description: 탑 10에서 플레이어를 제거합니다 + parameters: "<플레이어>" +island: + level: + parameters: "[플레이어]" + description: 섬 레벨을 계산하거나 [플레이어]의 섬레벨을 보여줍니다 + calculating: "&a 계산중....\n" + estimated-wait: "&a예상 대기 시간 : [번호] 초" + in-queue: "& a 당신은 대기열에있는 숫자 [번호]입니다" + island-level-is: "& a 섬 레벨은 & b [level]" + required-points-to-next-level: "&a [point] 다음 레벨까지 요구되는 경험치" + deaths: "&c ([number] 사망)" + cooldown: "&c그것을 다시하려면 &b[time]초&c를 기다려야합니다." + top: + description: 탑 10을 보여줍니다 + gui-title: "&a 탑 10" + gui-heading: "&6 [name] : &B[rank]" + island-level: "&b 레벨 [level]" + warp-to: "&a[name]님의 섬으로 이동중입니다.." + level-details: + above-sea-level-blocks: 해발 블록 + spawners: 스포너 + underwater-blocks: 수중 블록 + all-blocks: 모든 블록 + no-island: "&c 섬이 없습니다." + names-island: "[name]의 섬" + syntax: "[name] x [number]" + hint: "&c 블록 리포트를 보려면 레벨을 해야합니다." + value: + description: 모든 블록의 값을 보여줍니다 + success: "&7이 블록의 값은 &e [value]입니다." + success-underwater: "&7 해수면 아래의 블록 값 : &e [value]" + empty-hand: "& c 손에 블록이 없습니다" + no-value: "&c 해당 항목에는 가치가 없습니다." From 322ea825ea7198bab4b7e325c298aeb2e4dd854d Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Sat, 1 Jan 2022 18:40:51 -0800 Subject: [PATCH 023/106] German Translation (#253) * Translate de.yml via GitLocalize * Update de.yml Co-authored-by: Rikamo045 Co-authored-by: tastybento --- src/main/resources/locales/de.yml | 32 ++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 950b8c0..75c6889 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -3,32 +3,42 @@ admin: level: parameters: "" description: Berechne das Insel Level für den Spieler + levelstatus: + islands-in-queue: "& a Inseln in der Warteschlange: [number]" top: - remove: - description: entferne Spieler von Top-10 - parameters: "" description: Zeige die Top-10 Liste unknown-world: "&cUnbekannte Welt!" display: "&f[rank]. &a[name] &7- &b[level]" + remove: + description: entferne Spieler von Top-10 + parameters: "" island: level: parameters: "[Spieler]" description: Berechne dein Insel Level oder zeige das Level von [Spieler] - required-points-to-next-level: "&a[points] Punkte werden für das nächste Level - benötigt" calculating: "&aBerechne Level..." + estimated-wait: "& a Geschätzte Wartezeit: [number] Sekunden" + in-queue: "& a Sie sind Nummer [number] in der Warteschlange" island-level-is: "&aInsel Level: &b[level]" + required-points-to-next-level: "&a[points] Punkte werden für das nächste Level + benötigt" deaths: "&c([number] Tode)" cooldown: "&cDu musst &b[time] &csekunden warten bevor du das erneut machen kannst." - value: - description: Zeige den Wert jedes Blockes - success-underwater: "&7Wert des Blockes Unterwasser: &e[value]" - success: "&7Wert: &e[value]" - empty-hand: "&cDu hast keinen Block in der Hand" - no-value: "&cDas Item hat kein wert!" top: description: Zeige die Top-10 gui-title: "&aTop Zehn" gui-heading: "&6[name]: &B[rank]" island-level: "&BLevel [level]" warp-to: "&ATeleportiere zu [name]'s Insel" + level-details: + above-sea-level-blocks: Blöcke über dem Meeresspiegel + spawners: Spawner + underwater-blocks: Unterwasserblöcke + all-blocks: Alle Blöcke + no-island: "&c Keine Insel!" + value: + description: Zeige den Wert jedes Blockes + success: "&7Wert: &e[value]" + success-underwater: "&7Wert des Blockes Unterwasser: &e[value]" + empty-hand: "&cDu hast keinen Block in der Hand" + no-value: "&cDas Item hat kein wert!" From 6f174e2b3a4a6244315fd6edc62862b65f025c82 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Sat, 1 Jan 2022 18:41:49 -0800 Subject: [PATCH 024/106] Translate hu.yml via GitLocalize (#254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: András Marczinkó --- src/main/resources/locales/hu.yml | 47 ++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index f8131c8..b0bf727 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -1,39 +1,54 @@ -########################################################################################### -# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # -# the one at http://yaml-online-parser.appspot.com # -########################################################################################### - +--- admin: level: parameters: "" - description: "Egy játékos sziget szintjének kiszámítása" + description: Egy játékos sziget szintjének kiszámítása + sethandicap: + parameters: " " + description: állítsa be a sziget hátrányát, általában a kezdő sziget szintjét + changed: "&a A kezdeti sziget hátrány változott erről [number] erre [new_number]." + invalid-level: "&c Érvénytelen hátrány. Használj egész számot." + levelstatus: + description: megmutatja, hogy hány sziget van a szkennelési sorban + islands-in-queue: "&a Szigetek a sorban: [number]" top: - description: "Top Tíz lista megtekintése" + description: Top Tíz lista megtekintése unknown-world: "&cIsmeretlen világ!" display: "&f[rank]. &a[name] &7- &b[level]" - + remove: + description: játékos törlése a Top Tízből + parameters: "" island: - level: + level: parameters: "[player]" - description: "A saját vagy más játékos sziget szintjének kiszámítása" - calculating: "&aSziget szint kiszámítása..." + description: A saját vagy más játékos sziget szintjének kiszámítása + calculating: "&aSziget szint kiszámítása..." + estimated-wait: "&a Becsült várakozás: [number] másodperc" + in-queue: "&a Te vagy a(z) [number] a sorban" island-level-is: "&aA sziget szint: &b[level]" required-points-to-next-level: "&a[points] pont szükséges a következő szinthez." deaths: "&c([number] halál)" cooldown: "&cVárnod kell &b[time] &cmásodpercet, hogy újra használhasd." - top: - description: "Top Tíz lista megtekintése" + description: Top Tíz lista megtekintése gui-title: "&aTop Tíz" gui-heading: "&6[name]: &B[rank]" island-level: "&BLevel [level]" warp-to: "&ATeleportálás [name] szigetére." remove: - description: "játékos törlése a Top Tízből" + description: játékos törlése a Top Tízből parameters: "" - + level-details: + above-sea-level-blocks: Tengerszint Feletti Blokkok + spawners: Spawner-ek + underwater-blocks: Víz Alatti Blokkok + all-blocks: Minden Blokk + no-island: "&c Nincs sziget!" + names-island: "[name] szigete" + syntax: "[name] x [number]" + hint: "&c Futtassa a szintet a blokk jelentés megjelenítéséhez" value: - description: "Bármely blokk értékét mutatja" + description: Bármely blokk értékét mutatja success: "&7Ennek a blokknak az értéke: &e[value]" success-underwater: "&7Ennek a blokknak a tengerszint alatti értéke: &e[value]" empty-hand: "&cNincsenek blokkok a kezedben" From a3d06ee41a9ac39801dbd27c20fa3c9fcca2aadb Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 28 Jan 2022 22:15:19 -0800 Subject: [PATCH 025/106] Version 2.9.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5766363..a3bd1f0 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ -LOCAL - 2.9.0 + 2.9.1 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From 336e8d47bff2d87eebde6ac22b55eef3e82d358c Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 28 Jan 2022 22:15:34 -0800 Subject: [PATCH 026/106] Attempt to handle WildStacker spawners --- .../calculators/IslandLevelCalculator.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 845ba5c..e99a9e5 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -27,6 +27,7 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.Container; +import org.bukkit.block.CreatureSpawner; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Slab; import org.bukkit.inventory.ItemStack; @@ -443,8 +444,8 @@ private void scanAsync(Chunk chunk) { checkBlock(blockData.getMaterial(), belowSeaLevel); } } - // Hook for Wild Stackers (Blocks Only) - this has to use the real chunk - if (addon.isStackersEnabled() && blockData.getMaterial() == Material.CAULDRON) { + // 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(chunk.getWorld(), x + chunkSnapshot.getX() * 16,y,z + chunkSnapshot.getZ() * 16)); } // Scan chests @@ -678,14 +679,19 @@ private void handleStackedBlocks() { while (it.hasNext()) { Location v = it.next(); Util.getChunkAtAsync(v).thenAccept(c -> { - Block cauldronBlock = v.getBlock(); + Block stackedBlock = v.getBlock(); boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; - if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) { - StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock); - int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock); + if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { + StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); + int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); for (int _x = 0; _x < barrelAmt; _x++) { checkBlock(barrel.getType(), belowSeaLevel); } + } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { + int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); + for (int _x = 0; _x < spawnerAmt; _x++) { + checkBlock(stackedBlock.getType(), belowSeaLevel); + } } it.remove(); }); From 5d9aa00c13ce646f807d6065a9ba0327f7c04afa Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 6 Feb 2022 08:40:56 -0800 Subject: [PATCH 027/106] Fix error lon loading id locale --- src/main/resources/locales/id.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index 62fec59..4347616 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -53,4 +53,3 @@ island: success-underwater: "&7 Nilai blok ini di bawah permukaan laut adalah: &e[value]" empty-hand: "&c Tidak ada balok di tangan Anda" no-value: "&c Benda itu tidak bernilai." -'': false From 0a79b7fa58039a385f9fedcb40a30a93dd81d951 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 6 Feb 2022 08:47:20 -0800 Subject: [PATCH 028/106] Avoid async chunk snapshotting. Fixes https://github.com/BentoBoxWorld/Level/issues/256 --- .../calculators/IslandLevelCalculator.java | 100 ++++++++++-------- 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index e99a9e5..76b2bd3 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -417,48 +417,6 @@ private int limitCount(Material md) { } - /** - * Count the blocks on the island - * @param chunk chunk to scan - */ - private void scanAsync(Chunk chunk) { - ChunkSnapshot chunkSnapshot = chunk.getChunkSnapshot(); - 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 (chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || 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 (chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || 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 = chunk.getWorld().getMinHeight(); y < chunk.getWorld().getMaxHeight(); y++) { - BlockData blockData = 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(chunk.getWorld(), x + chunkSnapshot.getX() * 16,y,z + chunkSnapshot.getZ() * 16)); - } - // Scan chests - if (addon.getSettings().isIncludeChests() && CHESTS.contains(blockData.getMaterial())) { - chestBlocks.add(chunk); - } - // Add the value of the block's material - checkBlock(blockData.getMaterial(), belowSeaLevel); - } - } - } - } - /** * Scan all containers in a chunk and count their blocks * @param chunk - the chunk to scan @@ -493,8 +451,9 @@ private void countItemStack(ItemStack i) { } /** - * Scan the chunk chests and count the blocks - * @param chunks - the chunk to scan + * Scan the chunk chests and count the blocks. Note that the chunks are a list 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 * @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 scanChunk(List chunks) { @@ -504,14 +463,63 @@ private CompletableFuture scanChunk(List chunks) { } // 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(), () -> { - chunks.forEach(chunk -> scanAsync(chunk)); + 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) {} + + /** + * Count the blocks on the island + * @param chunk 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, x + cp.chunkSnapshot.getX() * 16,y,z + cp.chunkSnapshot.getZ() * 16)); + } + // 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 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 From e16fad882ee897eed9f48d959a39a130c4221342 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sat, 12 Mar 2022 12:52:44 +0200 Subject: [PATCH 029/106] Update to BentoBox API 1.20. Replace plugin.yml with spigot-annotations. Implement customizable TopLevelPanel. --- pom.xml | 7 +- src/main/java/world/bentobox/level/Level.java | 4 + .../world/bentobox/level/LevelPladdon.java | 11 +- .../level/commands/IslandTopCommand.java | 4 +- .../bentobox/level/panels/TopLevelPanel.java | 532 ++++++++++++++++++ src/main/resources/locales/en-US.yml | 32 +- src/main/resources/panels/top_panel.yml | 124 ++++ src/main/resources/plugin.yml | 9 - 8 files changed, 709 insertions(+), 14 deletions(-) create mode 100644 src/main/java/world/bentobox/level/panels/TopLevelPanel.java create mode 100644 src/main/resources/panels/top_panel.yml delete mode 100644 src/main/resources/plugin.yml diff --git a/pom.xml b/pom.xml index a3bd1f0..17d1dd9 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 2.0.9 1.16.5-R0.1-SNAPSHOT - 1.16.5-SNAPSHOT + 1.20.0 ${build.version}-SNAPSHOT @@ -146,6 +146,11 @@ ${spigot.version} provided + + org.spigotmc + plugin-annotations + 1.2.3-SNAPSHOT + org.mockito diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 79c7a5e..c91a291 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -79,6 +79,10 @@ public void onLoad() { private boolean loadSettings() { // Load settings again to get worlds settings = configObject.loadConfigObject(); + + // Save existing panels. + this.saveResource("panels/top_panel.yml", false); + return settings == null; } diff --git a/src/main/java/world/bentobox/level/LevelPladdon.java b/src/main/java/world/bentobox/level/LevelPladdon.java index 7bebe0c..2e8032e 100644 --- a/src/main/java/world/bentobox/level/LevelPladdon.java +++ b/src/main/java/world/bentobox/level/LevelPladdon.java @@ -1,16 +1,23 @@ package world.bentobox.level; + +import org.bukkit.plugin.java.annotation.dependency.Dependency; +import org.bukkit.plugin.java.annotation.plugin.ApiVersion; +import org.bukkit.plugin.java.annotation.plugin.Plugin; + import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Pladdon; + /** * @author tastybento * */ +@Plugin(name="Pladdon", version="1.0") +@ApiVersion(ApiVersion.Target.v1_16) +@Dependency(value = "BentoBox") public class LevelPladdon extends Pladdon { - @Override public Addon getAddon() { return new Level(); } - } diff --git a/src/main/java/world/bentobox/level/commands/IslandTopCommand.java b/src/main/java/world/bentobox/level/commands/IslandTopCommand.java index 7521b9c..5e35d3e 100644 --- a/src/main/java/world/bentobox/level/commands/IslandTopCommand.java +++ b/src/main/java/world/bentobox/level/commands/IslandTopCommand.java @@ -5,6 +5,8 @@ import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.level.Level; +import world.bentobox.level.panels.TopLevelPanel; + public class IslandTopCommand extends CompositeCommand { @@ -24,7 +26,7 @@ public void setup() { @Override public boolean execute(User user, String label, List list) { - addon.getManager().getGUI(getWorld(), user); + TopLevelPanel.openPanel(this.addon, user, this.getWorld(), this.getPermissionPrefix()); return true; } } diff --git a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java new file mode 100644 index 0000000..c9f5741 --- /dev/null +++ b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java @@ -0,0 +1,532 @@ +/// +// Created by BONNe +// Copyright - 2021 +/// + +package world.bentobox.level.panels; + + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.permissions.PermissionAttachmentInfo; +import java.io.File; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.TemplatedPanel; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.RanksManager; +import world.bentobox.level.Level; + + +/** + * This panel opens top likes panel + */ +public class TopLevelPanel +{ +// --------------------------------------------------------------------- +// Section: Internal Constructor +// --------------------------------------------------------------------- + + + /** + * This is internal constructor. It is used internally in current class to avoid creating objects everywhere. + * + * @param addon Level object. + * @param user User who opens Panel. + * @param world World where gui is opened + * @param permissionPrefix Permission Prefix + */ + private TopLevelPanel(Level addon, User user, World world, String permissionPrefix) + { + this.addon = addon; + this.user = user; + this.world = world; + + this.iconPermission = permissionPrefix + "level.icon"; + + this.topIslands = this.addon.getManager().getTopTen(this.world, 10).entrySet().stream(). + map(entry -> { + Island island = this.addon.getIslandsManager().getIsland(this.world, entry.getKey()); + return new IslandTopRecord(island, entry.getValue()); + }). + collect(Collectors.toList()); + } + + + /** + * Build method manages current panel opening. It uses BentoBox PanelAPI that is easy to use and users can get nice + * panels. + */ + public void build() + { + TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); + + panelBuilder.user(this.user); + panelBuilder.world(this.world); + + panelBuilder.template("top_panel", new File(this.addon.getDataFolder(), "panels")); + + panelBuilder.registerTypeBuilder("VIEW", this::createViewerButton); + panelBuilder.registerTypeBuilder("TOP", this::createPlayerButton); + + // Register unknown type builder. + panelBuilder.build(); + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * Creates fallback based on template. + * @param template Template record for fallback button. + * @param index Place of the fallback. + * @return Fallback panel item. + */ + private PanelItem createFallback(ItemTemplateRecord template, long index) + { + if (template == null) + { + return null; + } + + final String reference = "level.gui.buttons.island."; + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + builder.icon(template.icon().clone()); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.world, template.title(), + "[name]", String.valueOf(index))); + } + else + { + builder.name(this.user.getTranslation(this.world, reference, + "[name]", String.valueOf(index))); + } + + if (template.description() != null) + { + builder.description(this.user.getTranslation(this.world, template.description(), + "[number]", String.valueOf(index))); + } + + builder.amount(index != 0 ? (int) index : 1); + + return builder.build(); + } + + + /** + * This method creates player icon with warp functionality. + * + * @return PanelItem for PanelBuilder. + */ + private PanelItem createPlayerButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot itemSlot) + { + int index = (int) template.dataMap().getOrDefault("index", 0); + + if (index < 1) + { + return this.createFallback(template.fallback(), index); + } + + IslandTopRecord islandTopRecord = this.topIslands.size() < index ? null : this.topIslands.get(index - 1); + + if (islandTopRecord == null) + { + return this.createFallback(template.fallback(), index); + } + + return this.createIslandIcon(template, islandTopRecord, index); + } + + + /** + * This method creates button from template for given island top record. + * @param template Icon Template. + * @param islandTopRecord Island Top Record. + * @param index Place Index. + * @return PanelItem for PanelBuilder. + */ + private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord islandTopRecord, int index) + { + // Get player island. + Island island = islandTopRecord.island(); + + if (island == null) + { + return this.createFallback(template.fallback(), index); + } + + PanelItemBuilder builder = new PanelItemBuilder(); + + this.populateIslandIcon(builder, template, island); + this.populateIslandTitle(builder, template, island); + this.populateIslandDescription(builder, template, island, islandTopRecord, index); + + builder.amount(index); + + // Get only possible actions, by removing all inactive ones. + List activeActions = new ArrayList<>(template.actions()); + + activeActions.removeIf(action -> + "VIEW".equalsIgnoreCase(action.actionType()) && island.getOwner() == null && + island.getMemberSet(RanksManager.MEMBER_RANK). + contains(this.user.getUniqueId())); + + // Add Click handler + builder.clickHandler((panel, user, clickType, i) -> + { + for (ItemTemplateRecord.ActionRecords action : activeActions) + { + if (clickType == action.clickType() && "VIEW".equalsIgnoreCase(action.actionType())) + { + this.user.closeInventory(); + // Open Detailed GUI. + } + } + + return true; + }); + + // Collect tooltips. + List tooltips = activeActions.stream(). + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); + } + + + /** + * Populate given panel item builder name with values from template and island objects. + * + * @param builder the builder + * @param template the template + * @param island the island + */ + private void populateIslandTitle(PanelItemBuilder builder, + ItemTemplateRecord template, + Island island) + { + final String reference = "level.gui.buttons.island."; + + // Get Island Name + String nameText; + + if (island.getName() == null || island.getName().isEmpty()) + { + nameText = this.user.getTranslation(reference + "owners-island", + "[player]", + island.getOwner() == null ? + this.user.getTranslation(reference + "unknown") : + this.addon.getPlayers().getName(island.getOwner())); + } + else + { + nameText = island.getName(); + } + + // Template specific title is always more important than custom one. + if (template.title() != null && !template.title().isBlank()) + { + builder.name(this.user.getTranslation(this.world, template.title(), + "[name]", nameText)); + } + else + { + builder.name(this.user.getTranslation(reference + "name", "[name]", nameText)); + } + } + + + /** + * Populate given panel item builder icon with values from template and island objects. + * + * @param builder the builder + * @param template the template + * @param island the island + */ + private void populateIslandIcon(PanelItemBuilder builder, + ItemTemplateRecord template, + Island island) + { + User owner = island.getOwner() == null ? null : User.getInstance(island.getOwner()); + + // Get permission or island icon + String permissionIcon = TopLevelPanel.getPermissionValue(owner, this.iconPermission); + + Material material; + + if (permissionIcon != null && !permissionIcon.equals("*")) + { + material = Material.matchMaterial(permissionIcon); + } + else + { + material = null; + } + + if (material != null) + { + if (!material.equals(Material.PLAYER_HEAD)) + { + builder.icon(material); + } + else + { + builder.icon(owner.getName()); + } + } + else if (template.icon() != null) + { + builder.icon(template.icon().clone()); + } + else if (owner != null) + { + builder.icon(owner.getName()); + } + else + { + builder.icon(Material.PLAYER_HEAD); + } + } + + + /** + * Populate given panel item builder description with values from template and island objects. + * + * @param builder the builder + * @param template the template + * @param island the island + * @param islandTopRecord the top record object + * @param index place index. + */ + private void populateIslandDescription(PanelItemBuilder builder, + ItemTemplateRecord template, + Island island, + IslandTopRecord islandTopRecord, + int index) + { + final String reference = "level.gui.buttons.island."; + + // Get Owner Name + String ownerText = this.user.getTranslation(reference + "owner", + "[player]", + island.getOwner() == null ? + this.user.getTranslation(reference + "unknown") : + this.addon.getPlayers().getName(island.getOwner())); + + // Get Members Text + String memberText; + + if (island.getMemberSet().size() > 1) + { + StringBuilder memberBuilder = new StringBuilder( + this.user.getTranslationOrNothing(reference + "members-title")); + + for (UUID uuid : island.getMemberSet()) + { + User user = User.getInstance(uuid); + + if (memberBuilder.length() > 0) + { + memberBuilder.append("\n"); + } + + memberBuilder.append( + this.user.getTranslationOrNothing(reference + "member", + "[player]", user.getName())); + } + + memberText = memberBuilder.toString(); + } + else + { + memberText = ""; + } + + String placeText = this.user.getTranslation(reference + "place", + "[number]", String.valueOf(index)); + + String levelText = this.user.getTranslation(reference + "level", + "[number]", String.valueOf(islandTopRecord.level())); + + // Template specific description is always more important than custom one. + if (template.description() != null && !template.description().isBlank()) + { + builder.description(this.user.getTranslation(this.world, template.description(), + "[owner]", ownerText, + "[members]", memberText, + "[level]", levelText, + "[place]", placeText). + replaceAll("(?m)^[ \\t]*\\r?\\n", ""). + replaceAll("(? permissions = user.getEffectivePermissions().stream(). + map(PermissionAttachmentInfo::getPermission). + filter(permission -> permission.startsWith(permPrefix)). + collect(Collectors.toList()); + + for (String permission : permissions) + { + if (permission.contains(permPrefix + "*")) + { + // * means all. So continue to search more specific. + continue; + } + + String[] parts = permission.split(permPrefix); + + if (parts.length > 1) + { + return parts[1]; + } + } + } + + return null; + } + + +// --------------------------------------------------------------------- +// Section: Record +// --------------------------------------------------------------------- + + + /** + * This record is used internally. It converts user -> level to island -> level. + */ + private record IslandTopRecord(Island island, Long level) {} + + +// --------------------------------------------------------------------- +// Section: Variables +// --------------------------------------------------------------------- + + /** + * This variable allows to access addon object. + */ + private final Level addon; + + /** + * This variable holds user who opens panel. Without it panel cannot be opened. + */ + private final User user; + + /** + * This variable holds a world to which gui referee. + */ + private final World world; + + /** + * Location to icon permission. + */ + private final String iconPermission; + + /** + * List of top 10 island records. + */ + private final List topIslands; +} diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 6b75387..144aee9 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -60,4 +60,34 @@ island: success: "&7 The value of this block is: &e[value]" success-underwater: "&7 The value of this block below sea-level: &e[value]" empty-hand: "&c There are no blocks in your hand" - no-value: "&c That item has no value." \ No newline at end of file + no-value: "&c That item has no value." + +level: + gui: + titles: + top: "&0&l Top Islands" + buttons: + island: + empty: '&f&l [name]. place' + name: '&f&l [name]' + description: |- + [owner] + [members] + [place] + [level] + # Text that is replacing [name] if island do not have a name + owners-island: "[player]'s Island" + # Text for [owner] in description. + owner: "&7&l Owner: &r&b [player]" + # Title before listing members for [members] in description + members-title: "&7&l Members:" + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: "unknown" + # Section for parsing [place] + place: "&7&o [number]. &r&7 place" + # Section for parsing [level] + level: "&7 Level: &o [number]" + tips: + click-to-view: "&e Click &7 to view." diff --git a/src/main/resources/panels/top_panel.yml b/src/main/resources/panels/top_panel.yml new file mode 100644 index 0000000..6f24683 --- /dev/null +++ b/src/main/resources/panels/top_panel.yml @@ -0,0 +1,124 @@ +top_panel: + title: level.gui.titles.top + type: INVENTORY + background: + icon: BLACK_STAINED_GLASS_PANE + title: "&b&r" # Empty text + border: + icon: BLACK_STAINED_GLASS_PANE + title: "&b&r" # Empty text + force-shown: [2,3,4,5] + content: + 2: + 5: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 1 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 3: + 4: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 2 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 6: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 3 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 4: + 2: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 4 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 3: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 5 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 4: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 6 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 5: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 7 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 6: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 8 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 7: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 9 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 8: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: TOP + index: 10 + fallback: + icon: LIME_STAINED_GLASS_PANE + title: level.gui.buttons.island.empty + 6: + 5: + icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: VIEW + actions: + left: + tooltip: level.gui.tips.click-to-view \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml deleted file mode 100644 index c5bd022..0000000 --- a/src/main/resources/plugin.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Pladdon -main: world.bentobox.level.LevelPladdon -version: ${version} -api-version: "1.17" -description: Level Addon -author: tastybento -depend: - - BentoBox - From 43fcde5781f6dfab7164de35a3117a48a6ce8685 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 13 Mar 2022 14:26:56 +0200 Subject: [PATCH 030/106] Fixes some small issues with TopLevelPanel Add Utils class that contains some useful things. --- .../bentobox/level/panels/TopLevelPanel.java | 59 +++----------- .../java/world/bentobox/level/util/Utils.java | 76 +++++++++++++++++++ 2 files changed, 85 insertions(+), 50 deletions(-) create mode 100644 src/main/java/world/bentobox/level/util/Utils.java diff --git a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java index c9f5741..23c313a 100644 --- a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java +++ b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java @@ -8,10 +8,9 @@ import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.event.inventory.ClickType; import java.io.File; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import world.bentobox.bentobox.api.panels.PanelItem; @@ -23,6 +22,7 @@ import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.level.Level; +import world.bentobox.level.util.Utils; /** @@ -194,10 +194,13 @@ private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord { for (ItemTemplateRecord.ActionRecords action : activeActions) { - if (clickType == action.clickType() && "VIEW".equalsIgnoreCase(action.actionType())) + if ((clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) && + "VIEW".equalsIgnoreCase(action.actionType())) { this.user.closeInventory(); // Open Detailed GUI. + + DetailsPanel.openPanel(this.addon, this.world, this.user); } } @@ -279,7 +282,8 @@ private void populateIslandIcon(PanelItemBuilder builder, User owner = island.getOwner() == null ? null : User.getInstance(island.getOwner()); // Get permission or island icon - String permissionIcon = TopLevelPanel.getPermissionValue(owner, this.iconPermission); + String permissionIcon = owner == null ? null : + Utils.getPermissionValue(owner, this.iconPermission, null); Material material; @@ -375,7 +379,7 @@ private void populateIslandDescription(PanelItemBuilder builder, "[number]", String.valueOf(index)); String levelText = this.user.getTranslation(reference + "level", - "[number]", String.valueOf(islandTopRecord.level())); + "[number]", this.addon.getManager().formatLevel(islandTopRecord.level())); // Template specific description is always more important than custom one. if (template.description() != null && !template.description().isBlank()) @@ -445,51 +449,6 @@ public static void openPanel(Level addon, User user, World world, String permiss } - /** - * This method gets string value of given permission prefix. If user does not have given permission or it have all - * (*), then return default value. - * - * @param user User who's permission should be checked. - * @param permissionPrefix Prefix that need to be found. - * @return String value that follows permissionPrefix. - */ - private static String getPermissionValue(User user, String permissionPrefix) - { - if (user != null && user.isPlayer()) - { - if (permissionPrefix.endsWith(".")) - { - permissionPrefix = permissionPrefix.substring(0, permissionPrefix.length() - 1); - } - - String permPrefix = permissionPrefix + "."; - - List permissions = user.getEffectivePermissions().stream(). - map(PermissionAttachmentInfo::getPermission). - filter(permission -> permission.startsWith(permPrefix)). - collect(Collectors.toList()); - - for (String permission : permissions) - { - if (permission.contains(permPrefix + "*")) - { - // * means all. So continue to search more specific. - continue; - } - - String[] parts = permission.split(permPrefix); - - if (parts.length > 1) - { - return parts[1]; - } - } - } - - return null; - } - - // --------------------------------------------------------------------- // Section: Record // --------------------------------------------------------------------- diff --git a/src/main/java/world/bentobox/level/util/Utils.java b/src/main/java/world/bentobox/level/util/Utils.java new file mode 100644 index 0000000..2c6cd71 --- /dev/null +++ b/src/main/java/world/bentobox/level/util/Utils.java @@ -0,0 +1,76 @@ +// +// Created by BONNe +// Copyright - 2021 +// + + +package world.bentobox.level.util; + + +import org.bukkit.permissions.PermissionAttachmentInfo; +import java.util.List; +import java.util.stream.Collectors; + +import world.bentobox.bentobox.api.user.User; + + +public class Utils +{ + /** + * This method sends a message to the user with appended "prefix" text before message. + * @param user User who receives message. + * @param translationText Translation text of the message. + * @param parameters Parameters for the translation text. + */ + public static void sendMessage(User user, String translationText, String... parameters) + { + user.sendMessage(user.getTranslation( "level.conversations.prefix") + + user.getTranslation( translationText, parameters)); + } + + + /** + * This method gets string value of given permission prefix. If user does not have given permission or it have all + * (*), then return default value. + * + * @param user User who's permission should be checked. + * @param permissionPrefix Prefix that need to be found. + * @param defaultValue Default value that will be returned if permission not found. + * @return String value that follows permissionPrefix. + */ + public static String getPermissionValue(User user, String permissionPrefix, String defaultValue) + { + if (user.isPlayer()) + { + if (permissionPrefix.endsWith(".")) + { + permissionPrefix = permissionPrefix.substring(0, permissionPrefix.length() - 1); + } + + String permPrefix = permissionPrefix + "."; + + List permissions = user.getEffectivePermissions().stream(). + map(PermissionAttachmentInfo::getPermission). + filter(permission -> permission.startsWith(permPrefix)). + collect(Collectors.toList()); + + for (String permission : permissions) + { + if (permission.contains(permPrefix + "*")) + { + // * means all. So continue to search more specific. + continue; + } + + String[] parts = permission.split(permPrefix); + + if (parts.length > 1) + { + return parts[1]; + } + } + } + + return defaultValue; + } +} From 15ff5150789e0adb2544ee723ab3cb187188f829 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 13 Mar 2022 14:28:08 +0200 Subject: [PATCH 031/106] Implement customizable DetailsPanel. Remove old DetailsGUITab due to new implementation. --- pom.xml | 39 + src/main/java/world/bentobox/level/Level.java | 7 +- .../world/bentobox/level/LevelsManager.java | 114 +-- .../calculators/IslandLevelCalculator.java | 5 +- .../bentobox/level/panels/DetailsGUITab.java | 211 ------ .../bentobox/level/panels/DetailsPanel.java | 692 ++++++++++++++++++ src/main/resources/locales/en-US.yml | 50 ++ src/main/resources/panels/detail_panel.yml | 109 +++ src/main/resources/panels/top_panel.yml | 3 +- 9 files changed, 899 insertions(+), 331 deletions(-) delete mode 100644 src/main/java/world/bentobox/level/panels/DetailsGUITab.java create mode 100644 src/main/java/world/bentobox/level/panels/DetailsPanel.java create mode 100644 src/main/resources/panels/detail_panel.yml diff --git a/pom.xml b/pom.xml index 17d1dd9..f818b54 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,8 @@ 1.16.5-R0.1-SNAPSHOT 1.20.0 + + 1.0.0 ${build.version}-SNAPSHOT @@ -176,6 +178,11 @@ ${bentobox.version} provided + + lv.id.bonne + panelutils + ${panelutils.version} + com.github.OmerBenGera @@ -331,6 +338,38 @@ maven-deploy-plugin 2.8.2 + + org.apache.maven.plugins + maven-shade-plugin + 3.3.1-SNAPSHOT + + true + + + lv.id.bonne:panelutils:* + + + + + + MANIFEST.MF + + + + META-INF/MANIFEST.MF + src/main/resources/META-INF/MANIFEST.MF + + + + + + package + + shade + + + + org.jacoco jacoco-maven-plugin diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index c91a291..96e0714 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -74,15 +74,16 @@ public void onLoad() { } else { configObject.saveConfigObject(settings); } + + // Save existing panels. + this.saveResource("panels/top_panel.yml", false); + this.saveResource("panels/detail_panel.yml", false); } private boolean loadSettings() { // Load settings again to get worlds settings = configObject.loadConfigObject(); - // Save existing panels. - this.saveResource("panels/top_panel.yml", false); - return settings == null; } diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index cfa2247..2c182a8 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -2,11 +2,9 @@ import java.math.BigInteger; import java.text.DecimalFormat; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -18,19 +16,12 @@ import java.util.stream.Stream; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; import org.bukkit.World; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import com.google.common.collect.Maps; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.builders.PanelBuilder; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; -import world.bentobox.bentobox.api.panels.builders.TabbedPanelBuilder; -import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.Database; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.level.calculators.Results; @@ -39,8 +30,7 @@ import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.objects.LevelsData; import world.bentobox.level.objects.TopTenData; -import world.bentobox.level.panels.DetailsGUITab; -import world.bentobox.level.panels.DetailsGUITab.DetailsType; + public class LevelsManager { private static final String INTOPTEN = "intopten"; @@ -57,16 +47,12 @@ public class LevelsManager { } private Level addon; - // Database handler for level data private final Database handler; // A cache of island levels. private final Map levelsCache; // Top ten lists private final Map topTenLists; - // Background - private final PanelItem background; - public LevelsManager(Level addon) { @@ -79,8 +65,6 @@ public LevelsManager(Level addon) { levelsCache = new HashMap<>(); // Initialize top ten lists topTenLists = new ConcurrentHashMap<>(); - // Background - background = new PanelItemBuilder().icon(Material.BLACK_STAINED_GLASS_PANE).name(" ").build(); } public void migrate() { @@ -226,102 +210,6 @@ public String formatLevel(@Nullable Long lvl) { return level; } - /** - * Displays the Top Ten list - * @param world - world - * @param user - the requesting player - */ - public void getGUI(World world, final User user) { - // Check world - Map topTen = getTopTen(world, Level.TEN); - - PanelBuilder panel = new PanelBuilder() - .name(user.getTranslation("island.top.gui-title")) - .size(54) - .user(user); - // Background - for (int j = 0; j < 54; panel.item(j++, background)); - - // Top Ten - int i = 0; - boolean inTopTen = false; - for (Entry m : topTen.entrySet()) { - PanelItem h = getHead((i+1), m.getValue(), m.getKey(), user, world); - panel.item(SLOTS[i], h); - // If this is also the asking player - if (m.getKey().equals(user.getUniqueId())) { - inTopTen = true; - addSelf(world, user, panel); - } - i++; - } - // Show remaining slots - for (; i < SLOTS.length; i++) { - panel.item(SLOTS[i], new PanelItemBuilder().icon(Material.GREEN_STAINED_GLASS_PANE).name(String.valueOf(i + 1)).build()); - } - - // Add yourself if you were not already in the top ten - if (!inTopTen) { - addSelf(world, user, panel); - } - panel.build(); - } - - private void addSelf(World world, User user, PanelBuilder panel) { - if (addon.getIslands().hasIsland(world, user) || addon.getIslands().inTeam(world, user.getUniqueId())) { - PanelItem head = getHead(this.getRank(world, user.getUniqueId()), this.getIslandLevel(world, user.getUniqueId()), user.getUniqueId(), user, world); - setClickHandler(head, user, world); - panel.item(49, head); - } - } - - private void setClickHandler(PanelItem head, User user, World world) { - head.setClickHandler((p, u, ch, s) -> { - new TabbedPanelBuilder() - .user(user) - .world(world) - .tab(1, new DetailsGUITab(addon, world, user, DetailsType.ALL_BLOCKS)) - .tab(2, new DetailsGUITab(addon, world, user, DetailsType.ABOVE_SEA_LEVEL_BLOCKS)) - .tab(3, new DetailsGUITab(addon, world, user, DetailsType.UNDERWATER_BLOCKS)) - .tab(4, new DetailsGUITab(addon, world, user, DetailsType.SPAWNERS)) - .startingSlot(1) - .size(54) - .build().openPanel(); - return true; - }); - - } - - /** - * Get the head panel item - * @param rank - the top ten rank of this player/team. Can be used in the name of the island for vanity. - * @param level - the level of the island - * @param playerUUID - the UUID of the top ten player - * @param asker - the asker of the top ten - * @return PanelItem - */ - private PanelItem getHead(int rank, long level, UUID playerUUID, User asker, World world) { - final String name = addon.getPlayers().getName(playerUUID); - List description = new ArrayList<>(); - if (rank > 0) { - description.add(asker.getTranslation("island.top.gui-heading", "[name]", name, "[rank]", String.valueOf(rank))); - } - description.add(asker.getTranslation("island.top.island-level","[level]", formatLevel(level))); - if (addon.getIslands().inTeam(world, playerUUID)) { - List memberList = new ArrayList<>(); - for (UUID members : addon.getIslands().getMembers(world, playerUUID)) { - memberList.add(ChatColor.AQUA + addon.getPlayers().getName(members)); - } - description.addAll(memberList); - } - - PanelItemBuilder builder = new PanelItemBuilder() - .icon(name) - .name(name) - .description(description); - return builder.build(); - } - /** * Get the initial level of the island. Used to zero island levels * @param island - island diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 76b2bd3..065f5e0 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -352,8 +352,7 @@ private int getValue(Material md) { /** * Get a chunk async * @param env - the environment - * @param x - chunk x coordinate - * @param z - chunk z 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 */ private CompletableFuture> getWorldChunk(Environment env, Queue> pairList) { @@ -481,7 +480,7 @@ record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) {} /** * Count the blocks on the island - * @param chunk chunk to scan + * @param cp chunk to scan */ private void scanAsync(ChunkPair cp) { for (int x = 0; x< 16; x++) { diff --git a/src/main/java/world/bentobox/level/panels/DetailsGUITab.java b/src/main/java/world/bentobox/level/panels/DetailsGUITab.java deleted file mode 100644 index 823a4df..0000000 --- a/src/main/java/world/bentobox/level/panels/DetailsGUITab.java +++ /dev/null @@ -1,211 +0,0 @@ -/** - * - */ -package world.bentobox.level.panels; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; - -import org.bukkit.Material; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.event.inventory.ClickType; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.common.base.Enums; - -import world.bentobox.bentobox.api.localization.TextVariables; -import world.bentobox.bentobox.api.panels.Panel; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.PanelItem.ClickHandler; -import world.bentobox.bentobox.api.panels.Tab; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.util.Util; -import world.bentobox.level.Level; -import world.bentobox.level.objects.IslandLevels; - -/** - * @author tastybento - * - */ -public class DetailsGUITab implements Tab, ClickHandler { - - public enum DetailsType { - ABOVE_SEA_LEVEL_BLOCKS, - ALL_BLOCKS, - SPAWNERS, - UNDERWATER_BLOCKS - } - - /** - * Converts block materials to item materials - */ - private static final Map M2I; - static { - Map m2i = new EnumMap<>(Material.class); - m2i.put(Material.WATER, Material.WATER_BUCKET); - m2i.put(Material.LAVA, Material.LAVA_BUCKET); - m2i.put(Material.AIR, Material.BLACK_STAINED_GLASS_PANE); - m2i.put(Material.VOID_AIR, Material.BLACK_STAINED_GLASS_PANE); - m2i.put(Material.CAVE_AIR, Material.BLACK_STAINED_GLASS_PANE); - m2i.put(Material.WALL_TORCH, Material.TORCH); - m2i.put(Material.REDSTONE_WALL_TORCH, Material.REDSTONE_TORCH); - m2i.put(Material.TALL_SEAGRASS, Material.SEAGRASS); - m2i.put(Material.PISTON_HEAD, Material.PISTON); - m2i.put(Material.MOVING_PISTON, Material.PISTON); - m2i.put(Material.REDSTONE_WIRE, Material.REDSTONE); - m2i.put(Material.NETHER_PORTAL, Material.MAGENTA_STAINED_GLASS_PANE); - m2i.put(Material.END_PORTAL, Material.BLACK_STAINED_GLASS_PANE); - m2i.put(Material.ATTACHED_MELON_STEM, Material.MELON_SEEDS); - m2i.put(Material.ATTACHED_PUMPKIN_STEM, Material.PUMPKIN_SEEDS); - m2i.put(Material.MELON_STEM, Material.MELON_SEEDS); - m2i.put(Material.PUMPKIN_STEM, Material.PUMPKIN_SEEDS); - m2i.put(Material.COCOA, Material.COCOA_BEANS); - m2i.put(Material.TRIPWIRE, Material.STRING); - m2i.put(Material.CARROTS, Material.CARROT); - m2i.put(Material.POTATOES, Material.POTATO); - m2i.put(Material.BEETROOTS, Material.BEETROOT); - m2i.put(Material.END_GATEWAY, Material.BEDROCK); - m2i.put(Material.FROSTED_ICE, Material.ICE); - m2i.put(Material.KELP_PLANT, Material.KELP); - m2i.put(Material.BUBBLE_COLUMN, Material.WATER_BUCKET); - m2i.put(Material.SWEET_BERRY_BUSH, Material.SWEET_BERRIES); - m2i.put(Material.BAMBOO_SAPLING, Material.BAMBOO); - m2i.put(Material.FIRE, Material.FLINT_AND_STEEL); - // 1.16.1 - if (Enums.getIfPresent(Material.class, "WEEPING_VINES_PLANT").isPresent()) { - m2i.put(Material.WEEPING_VINES_PLANT, Material.WEEPING_VINES); - m2i.put(Material.TWISTING_VINES_PLANT, Material.TWISTING_VINES); - m2i.put(Material.SOUL_WALL_TORCH, Material.SOUL_TORCH); - } - - - M2I = Collections.unmodifiableMap(m2i); - } - private final Level addon; - private final @Nullable Island island; - private List items; - private DetailsType type; - private final User user; - private final World world; - - public DetailsGUITab(Level addon, World world, User user, DetailsType type) { - this.addon = addon; - this.world = world; - this.user = user; - this.island = addon.getIslands().getIsland(world, user); - this.type = type; - // Generate report - generateReport(type); - } - - private void createItem(Material m, Integer count) { - if (count == null || count <= 0) return; - // Convert walls - m = Enums.getIfPresent(Material.class, m.name().replace("WALL_", "")).or(m); - // Tags - if (Enums.getIfPresent(Material.class, "SOUL_CAMPFIRE").isPresent()) { - if (Tag.FIRE.isTagged(m)) { - items.add(new PanelItemBuilder() - .icon(Material.CAMPFIRE) - .name(Util.prettifyText(m.name()) + " x " + count) - .build()); - return; - } - } - if (Tag.FLOWER_POTS.isTagged(m)) { - m = Enums.getIfPresent(Material.class, m.name().replace("POTTED_", "")).or(m); - } - items.add(new PanelItemBuilder() - .icon(M2I.getOrDefault(m, m)) - .name(user.getTranslation("island.level-details.syntax", TextVariables.NAME, - Util.prettifyText(m.name()), TextVariables.NUMBER, String.valueOf(count))) - .build()); - - } - - private void generateReport(DetailsType type) { - items = new ArrayList<>(); - IslandLevels ld = addon.getManager().getLevelsData(island); - // Get the items from the report - Map sumTotal = new EnumMap<>(Material.class); - sumTotal.putAll(ld.getMdCount()); - sumTotal.putAll(ld.getUwCount()); - switch(type) { - case ABOVE_SEA_LEVEL_BLOCKS: - ld.getMdCount().forEach(this::createItem); - break; - case SPAWNERS: - sumTotal.entrySet().stream().filter(m -> m.getKey().equals(Material.SPAWNER)).forEach(e -> createItem(e.getKey(), e.getValue())); - break; - case UNDERWATER_BLOCKS: - ld.getUwCount().forEach(this::createItem); - break; - default: - sumTotal.forEach(this::createItem); - break; - } - if (type.equals(DetailsType.ALL_BLOCKS) && items.isEmpty()) { - // Nothing here - looks like they need to run level - items.add(new PanelItemBuilder() - .name(user.getTranslation("island.level-details.hint")).icon(Material.WRITTEN_BOOK) - .build()); - } - } - - @Override - public PanelItem getIcon() { - switch(type) { - case ABOVE_SEA_LEVEL_BLOCKS: - return new PanelItemBuilder().icon(Material.GRASS_BLOCK).name(user.getTranslation("island.level-details.above-sea-level-blocks")).build(); - case SPAWNERS: - return new PanelItemBuilder().icon(Material.SPAWNER).name(user.getTranslation("island.level-details.spawners")).build(); - case UNDERWATER_BLOCKS: - return new PanelItemBuilder().icon(Material.WATER_BUCKET).name(user.getTranslation("island.level-details.underwater-blocks")).build(); - default: - return new PanelItemBuilder().icon(Material.GRASS_BLOCK).name(user.getTranslation("island.level-details.all-blocks")).build(); - } - } - - @Override - public String getName() { - String name = user.getTranslation("island.level-details.no-island"); - if (island.getOwner() != null) { - name = island.getName() != null ? island.getName() : - user.getTranslation("island.level-details.names-island", TextVariables.NAME, addon.getPlayers().getName(island.getOwner())); - } - return name; - } - - @Override - public List<@Nullable PanelItem> getPanelItems() { - return items; - } - - @Override - public String getPermission() { - String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world); - switch(type) { - case ABOVE_SEA_LEVEL_BLOCKS: - return permPrefix + "island.level.details.above-sea-level"; - case SPAWNERS: - return permPrefix + "island.level.details.spawners"; - case UNDERWATER_BLOCKS: - return permPrefix + "island.level.details.underwater"; - default: - return permPrefix + "island.level.details.blocks"; - - } - } - - @Override - public boolean onClick(Panel panel, User user, ClickType clickType, int slot) { - return true; - } - -} diff --git a/src/main/java/world/bentobox/level/panels/DetailsPanel.java b/src/main/java/world/bentobox/level/panels/DetailsPanel.java new file mode 100644 index 0000000..f253505 --- /dev/null +++ b/src/main/java/world/bentobox/level/panels/DetailsPanel.java @@ -0,0 +1,692 @@ +package world.bentobox.level.panels; + + +import com.google.common.base.Enums; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.TemplatedPanel; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.hooks.LangUtilsHook; +import world.bentobox.bentobox.util.Pair; +import world.bentobox.level.Level; +import world.bentobox.level.objects.IslandLevels; +import world.bentobox.level.util.Utils; + + +/** + * This class opens GUI that shows generator view for user. + */ +public class DetailsPanel +{ + // --------------------------------------------------------------------- + // Section: Internal Constructor + // --------------------------------------------------------------------- + + + /** + * This is internal constructor. It is used internally in current class to avoid creating objects everywhere. + * + * @param addon Level object + * @param world World where user is operating + * @param user User who opens panel + */ + private DetailsPanel(Level addon, + World world, + User user) + { + this.addon = addon; + this.world = world; + this.user = user; + + this.island = this.addon.getIslands().getIsland(world, user); + + if (this.island != null) + { + this.levelsData = this.addon.getManager().getLevelsData(this.island); + } + else + { + this.levelsData = null; + } + + // By default no-filters are active. + this.activeTab = Tab.ALL_BLOCKS; + this.materialCountList = new ArrayList<>(Material.values().length); + + this.updateFilters(); + } + + + /** + * This method builds this GUI. + */ + private void build() + { + if (this.island == null || this.levelsData == null) + { + // Nothing to see. + Utils.sendMessage(this.user, this.user.getTranslation("general.errors.no-island")); + return; + } + + if (this.levelsData.getMdCount().isEmpty() && this.levelsData.getUwCount().isEmpty()) + { + // Nothing to see. + Utils.sendMessage(this.user, this.user.getTranslation("level.conversations.no-data")); + return; + } + + // Start building panel. + TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); + panelBuilder.user(this.user); + panelBuilder.world(this.user.getWorld()); + + panelBuilder.template("detail_panel", new File(this.addon.getDataFolder(), "panels")); + + panelBuilder.parameters("[name]", this.user.getName()); + + panelBuilder.registerTypeBuilder("NEXT", this::createNextButton); + panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton); + panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton); + + // Register tabs + panelBuilder.registerTypeBuilder("TAB", this::createTabButton); + + // Register unknown type builder. + panelBuilder.build(); + } + + + /** + * This method updates filter of elements based on tabs. + */ + private void updateFilters() + { + this.materialCountList.clear(); + + switch (this.activeTab) + { + case ALL_BLOCKS -> { + Map materialCountMap = new EnumMap<>(Material.class); + + materialCountMap.putAll(this.levelsData.getMdCount()); + + // Add underwater blocks. + this.levelsData.getUwCount().forEach((material, count) -> { + materialCountMap.put(material, + materialCountMap.computeIfAbsent(material, key -> 0) + count); + }); + + materialCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey())). + forEachOrdered(entry -> + this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); + } + case ABOVE_SEA_LEVEL -> { + this.levelsData.getMdCount().entrySet().stream().sorted((Map.Entry.comparingByKey())). + forEachOrdered(entry -> + this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); + } + case UNDERWATER -> { + this.levelsData.getUwCount().entrySet().stream().sorted((Map.Entry.comparingByKey())). + forEachOrdered(entry -> + this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); + } + case SPAWNER -> { + int aboveWater = this.levelsData.getMdCount().getOrDefault(Material.SPAWNER, 0); + int underWater = this.levelsData.getUwCount().getOrDefault(Material.SPAWNER, 0); + + // TODO: spawners need some touch... + this.materialCountList.add(new Pair<>(Material.SPAWNER, underWater + aboveWater)); + } + } + + this.pageIndex = 0; + } + + +// --------------------------------------------------------------------- +// Section: Tab Button Type +// --------------------------------------------------------------------- + + + /** + * Create tab button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createTabButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + // Set icon + builder.icon(template.icon().clone()); + } + + if (template.title() != null) + { + // Set title + builder.name(this.user.getTranslation(this.world, template.title())); + } + + if (template.description() != null) + { + // Set description + builder.description(this.user.getTranslation(this.world, template.description())); + } + + Tab tab = Enums.getIfPresent(Tab.class, String.valueOf(template.dataMap().get("tab"))).or(Tab.ALL_BLOCKS); + + // Get only possible actions, by removing all inactive ones. + List activeActions = new ArrayList<>(template.actions()); + + activeActions.removeIf(action -> + "VIEW".equalsIgnoreCase(action.actionType()) && this.activeTab == tab); + + // Add Click handler + builder.clickHandler((panel, user, clickType, i) -> + { + for (ItemTemplateRecord.ActionRecords action : activeActions) + { + if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + { + if ("VIEW".equalsIgnoreCase(action.actionType())) + { + this.activeTab = tab; + + // Update filters. + this.updateFilters(); + this.build(); + } + } + } + + return true; + }); + + // Collect tooltips. + List tooltips = activeActions.stream(). + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + builder.glow(this.activeTab == tab); + + return builder.build(); + } + + +// --------------------------------------------------------------------- +// Section: Create common buttons +// --------------------------------------------------------------------- + + + /** + * Create next button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + long size = this.materialCountList.size(); + + if (size <= slot.amountMap().getOrDefault("BLOCK", 1) || + 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) + { + // There are no next elements + return null; + } + + int nextPageIndex = this.pageIndex + 2; + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + ItemStack clone = template.icon().clone(); + + if ((Boolean) template.dataMap().getOrDefault("indexing", false)) + { + clone.setAmount(nextPageIndex); + } + + builder.icon(clone); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.world, template.title())); + } + + if (template.description() != null) + { + builder.description(this.user.getTranslation(this.world, template.description(), + "[number]", String.valueOf(nextPageIndex))); + } + + // Add ClickHandler + builder.clickHandler((panel, user, clickType, i) -> + { + for (ItemTemplateRecord.ActionRecords action : template.actions()) + { + if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + { + if ("NEXT".equalsIgnoreCase(action.actionType())) + { + this.pageIndex++; + this.build(); + } + } + } + + // Always return true. + return true; + }); + + // Collect tooltips. + List tooltips = template.actions().stream(). + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); + } + + + /** + * Create previous button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + if (this.pageIndex == 0) + { + // There are no next elements + return null; + } + + int previousPageIndex = this.pageIndex; + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + ItemStack clone = template.icon().clone(); + + if ((Boolean) template.dataMap().getOrDefault("indexing", false)) + { + clone.setAmount(previousPageIndex); + } + + builder.icon(clone); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.world, template.title())); + } + + if (template.description() != null) + { + builder.description(this.user.getTranslation(this.world, template.description(), + "[number]", String.valueOf(previousPageIndex))); + } + + // Add ClickHandler + builder.clickHandler((panel, user, clickType, i) -> + { + for (ItemTemplateRecord.ActionRecords action : template.actions()) + { + if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + { + if ("PREVIOUS".equalsIgnoreCase(action.actionType())) + { + this.pageIndex--; + this.build(); + } + } + } + + // Always return true. + return true; + }); + + // Collect tooltips. + List tooltips = template.actions().stream(). + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); + } + + +// --------------------------------------------------------------------- +// Section: Create Material Button +// --------------------------------------------------------------------- + + + /** + * Create material button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + if (this.materialCountList.isEmpty()) + { + // Does not contain any generators. + return null; + } + + int index = this.pageIndex * slot.amountMap().getOrDefault("BLOCK", 1) + slot.slot(); + + if (index >= this.materialCountList.size()) + { + // Out of index. + return null; + } + + return this.createMaterialButton(template, this.materialCountList.get(index)); + } + + + /** + * This method creates button for material. + * + * @param template the template of the button + * @param materialCount materialCount which button must be created. + * @return PanelItem for generator tier. + */ + private PanelItem createMaterialButton(ItemTemplateRecord template, + Pair materialCount) + { + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + builder.icon(template.icon().clone()); + } + else + { + builder.icon(PanelUtils.getMaterialItem(materialCount.getKey())); + } + + if (materialCount.getValue() < 64) + { + builder.amount(materialCount.getValue()); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.world, template.title(), + "[number]", String.valueOf(materialCount.getValue()), + "[material]", DetailsPanel.prettifyObject(materialCount.getKey(), this.user))); + } + + String description = DetailsPanel.prettifyDescription(materialCount.getKey(), this.user); + + final String reference = "level.gui.buttons.material."; + String blockId = this.user.getTranslationOrNothing(reference + "id", + "[id]", materialCount.getKey().name()); + + int blockValue = this.addon.getBlockConfig().getBlockValues().getOrDefault(materialCount.getKey(), 0); + String value = blockValue > 0 ? this.user.getTranslationOrNothing(reference + "value", + "[number]", String.valueOf(blockValue)) : ""; + + int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(materialCount.getKey(), 0); + String limit = blockLimit > 0 ? this.user.getTranslationOrNothing(reference + "limit", + "[number]", String.valueOf(blockLimit)) : ""; + + String count = this.user.getTranslationOrNothing(reference + "count", + "[number]", String.valueOf(materialCount.getValue())); + + if (template.description() != null) + { + builder.description(this.user.getTranslation(this.world, template.description(), + "[description]", description, + "[id]", blockId, + "[value]", value, + "[limit]", limit, + "[count]", count). + replaceAll("(?m)^[ \\t]*\\r?\\n", ""). + replaceAll("(?> materialCountList; + + /** + * This variable holds current pageIndex for multi-page generator choosing. + */ + private int pageIndex; + + /** + * This variable stores which tab currently is active. + */ + private Tab activeTab; +} diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 144aee9..255e481 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -66,6 +66,7 @@ level: gui: titles: top: "&0&l Top Islands" + detail-panel: "&0&l [name]'s island" buttons: island: empty: '&f&l [name]. place' @@ -89,5 +90,54 @@ level: place: "&7&o [number]. &r&7 place" # Section for parsing [level] level: "&7 Level: &o [number]" + material: + name: "&f&l [number] x [material]" + description: |- + [description] + [count] + [value] + [limit] + [id] + id: "&7 Block id: &e [id]" + value: "&7 Block value: &e [number]" + limit: "&7 Block limit: &e [number]" + count: "&7 Number of blocks: &e [number]" + all_blocks: + name: "&f&l All Blocks" + description: |- + &7 Display all blocks + &7 on island. + above_sea_level: + name: "&f&l Blocks Above Sea Level" + description: |- + &7 Display only blocks + &7 that are above sea + &7 level. + underwater: + name: "&f&l Blocks Under Sea level" + description: |- + &7 Display only blocks + &7 that are bellow sea + &7 level. + spawner: + name: "&f&l Spawners" + description: |- + &7 Display only spawners. + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: "&f&l Previous Page" + description: |- + &7 Switch to [number] page + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: "&f&l Next Page" + description: |- + &7 Switch to [number] page tips: click-to-view: "&e Click &7 to view." + click-to-previous: "&e Click &7 to view previous page." + click-to-next: "&e Click &7 to view next page." + conversations: + # Prefix for messages that are send from server. + prefix: "&l&6 [BentoBox]: &r" + no-data: "&c Run level to see the block report." \ No newline at end of file diff --git a/src/main/resources/panels/detail_panel.yml b/src/main/resources/panels/detail_panel.yml new file mode 100644 index 0000000..61daf9c --- /dev/null +++ b/src/main/resources/panels/detail_panel.yml @@ -0,0 +1,109 @@ +detail_panel: + title: level.gui.titles.detail-panel + type: INVENTORY + background: + icon: BLACK_STAINED_GLASS_PANE + title: "&b&r" # Empty text + border: + icon: BLACK_STAINED_GLASS_PANE + title: "&b&r" # Empty text + force-shown: [] + content: + 1: + 2: + icon: STONE + title: level.gui.buttons.all_blocks.name + description: level.gui.buttons.all_blocks.description + data: + type: TAB + tab: ALL_BLOCKS + actions: + view: + click-type: unknwon + tooltip: level.gui.tips.click-to-view + 3: + icon: GRASS_BLOCK + title: level.gui.buttons.above_sea_level.name + description: level.gui.buttons.above_sea_level.description + data: + type: TAB + tab: ABOVE_SEA_LEVEL + actions: + view: + click-type: unknwon + tooltip: level.gui.tips.click-to-view + 4: + icon: WATER_BUCKET + title: level.gui.buttons.underwater.name + description: level.gui.buttons.underwater.description + data: + type: TAB + tab: UNDERWATER + actions: + view: + click-type: unknwon + tooltip: level.gui.tips.click-to-view + 5: + icon: SPAWNER + title: level.gui.buttons.spawner.name + description: level.gui.buttons.spawner.description + data: + type: TAB + tab: SPAWNER + actions: + view: + click-type: unknwon + tooltip: level.gui.tips.click-to-view + 2: + 2: material_button + 3: material_button + 4: material_button + 5: material_button + 6: material_button + 7: material_button + 8: material_button + 3: + 1: + icon: TIPPED_ARROW:INSTANT_HEAL::::1 + title: level.gui.buttons.previous.name + description: level.gui.buttons.previous.description + data: + type: PREVIOUS + indexing: true + actions: + previous: + click-type: unknown + tooltip: level.gui.tips.click-to-previous + 2: material_button + 3: material_button + 4: material_button + 5: material_button + 6: material_button + 7: material_button + 8: material_button + 9: + icon: TIPPED_ARROW:JUMP::::1 + title: level.gui.buttons.next.name + description: level.gui.buttons.next.description + data: + type: NEXT + indexing: true + actions: + next: + click-type: unknown + tooltip: level.gui.tips.click-to-next + 4: + 2: material_button + 3: material_button + 4: material_button + 5: material_button + 6: material_button + 7: material_button + 8: material_button + reusable: + material_button: + #icon: STONE + title: level.gui.buttons.material.name + description: level.gui.buttons.material.description + data: + type: BLOCK \ No newline at end of file diff --git a/src/main/resources/panels/top_panel.yml b/src/main/resources/panels/top_panel.yml index 6f24683..83d49ce 100644 --- a/src/main/resources/panels/top_panel.yml +++ b/src/main/resources/panels/top_panel.yml @@ -120,5 +120,6 @@ top_panel: data: type: VIEW actions: - left: + view: + click-type: unknown tooltip: level.gui.tips.click-to-view \ No newline at end of file From eb8c105be5c7fff721101e2d7c60c0f1528c7bc6 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 13 Mar 2022 15:24:12 +0200 Subject: [PATCH 032/106] Fix failing test. --- .../world/bentobox/level/LevelsManagerTest.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/test/java/world/bentobox/level/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java index a8541e1..94ead55 100644 --- a/src/test/java/world/bentobox/level/LevelsManagerTest.java +++ b/src/test/java/world/bentobox/level/LevelsManagerTest.java @@ -420,21 +420,6 @@ public void testSetIslandLevel() { } - /** - * Test method for {@link world.bentobox.level.LevelsManager#getGUI(org.bukkit.World, world.bentobox.bentobox.api.user.User)}. - */ - @Test - public void testGetGUI() { - lm.getGUI(world, user); - verify(user).getTranslation(eq("island.top.gui-title")); - verify(player).openInventory(inv); - /* - int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25}; - for (int i : SLOTS) { - verify(inv).setItem(eq(i), any()); - } - */ - } /** * Test method for {@link world.bentobox.level.LevelsManager#getRank(World, UUID)} From d9288c7e61707bd372f6f7abd9e8f7fc26e1ef1a Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 19 Mar 2022 18:27:58 +0000 Subject: [PATCH 033/106] Remove blank file --- src/main/resources/locales/ro.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/main/resources/locales/ro.yml diff --git a/src/main/resources/locales/ro.yml b/src/main/resources/locales/ro.yml deleted file mode 100644 index 8b13789..0000000 --- a/src/main/resources/locales/ro.yml +++ /dev/null @@ -1 +0,0 @@ - From fcf6e7659940cfef62ea13eceaabe2213ce264f0 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 19 Mar 2022 18:33:36 +0000 Subject: [PATCH 034/106] Added repo for maven plugin snapshots --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index f818b54..fdc2584 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,13 @@ + + + apache.snapshots + https://repository.apache.org/snapshots/ + + + spigot-repo From 4948689fe8e4e1f682dc66efc3b1ea896b0ae3bd Mon Sep 17 00:00:00 2001 From: BONNe Date: Thu, 16 Jun 2022 17:07:09 +0300 Subject: [PATCH 035/106] Implement feature that allows to sort items in detail panel. (#259) Apparently, because it is 2 years old request, it got in a state -> implement or drop. Fixes #192 --- .../bentobox/level/panels/DetailsPanel.java | 194 ++++++++++++++++++ .../java/world/bentobox/level/util/Utils.java | 58 ++++++ src/main/resources/locales/en-US.yml | 16 ++ src/main/resources/panels/detail_panel.yml | 29 ++- src/main/resources/panels/top_panel.yml | 22 +- 5 files changed, 304 insertions(+), 15 deletions(-) diff --git a/src/main/java/world/bentobox/level/panels/DetailsPanel.java b/src/main/java/world/bentobox/level/panels/DetailsPanel.java index f253505..deac18b 100644 --- a/src/main/java/world/bentobox/level/panels/DetailsPanel.java +++ b/src/main/java/world/bentobox/level/panels/DetailsPanel.java @@ -63,6 +63,7 @@ private DetailsPanel(Level addon, // By default no-filters are active. this.activeTab = Tab.ALL_BLOCKS; + this.activeFilter = Filter.NAME; this.materialCountList = new ArrayList<>(Material.values().length); this.updateFilters(); @@ -101,6 +102,8 @@ private void build() panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton); panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton); + panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton); + // Register tabs panelBuilder.registerTypeBuilder("TAB", this::createTabButton); @@ -152,6 +155,61 @@ private void updateFilters() } } + Comparator> sorter; + + switch (this.activeFilter) + { + case COUNT -> + { + sorter = (o1, o2) -> + { + if (o1.getValue().equals(o2.getValue())) + { + String o1Name = DetailsPanel.prettifyObject(o1.getKey(), this.user); + String o2Name = DetailsPanel.prettifyObject(o2.getKey(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + } + else + { + return Integer.compare(o2.getValue(), o1.getValue()); + } + }; + } + case VALUE -> + { + sorter = (o1, o2) -> + { + int o1Value = this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0); + int o2Value = this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0); + + if (o1Value == o2Value) + { + String o1Name = DetailsPanel.prettifyObject(o1.getKey(), this.user); + String o2Name = DetailsPanel.prettifyObject(o2.getKey(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + } + else + { + return Integer.compare(o2Value, o1Value); + } + }; + } + default -> + { + sorter = (o1, o2) -> + { + String o1Name = DetailsPanel.prettifyObject(o1.getKey(), this.user); + String o2Name = DetailsPanel.prettifyObject(o2.getKey(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + }; + } + } + + this.materialCountList.sort(sorter); + this.pageIndex = 0; } @@ -240,6 +298,117 @@ private PanelItem createTabButton(ItemTemplateRecord template, TemplatedPanel.It } + /** + * Create next button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createFilterButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + // Set icon + builder.icon(template.icon().clone()); + } + + Filter filter; + + if (slot.amountMap().getOrDefault("FILTER", 0) > 1) + { + filter = Enums.getIfPresent(Filter.class, String.valueOf(template.dataMap().get("filter"))).or(Filter.NAME); + } + else + { + filter = this.activeFilter; + } + + final String reference = "level.gui.buttons.filters."; + + if (template.title() != null) + { + // Set title + builder.name(this.user.getTranslation(this.world, template.title().replace("[filter]", filter.name().toLowerCase()))); + } + else + { + builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".name")); + } + + if (template.description() != null) + { + // Set description + builder.description(this.user.getTranslation(this.world, template.description().replace("[filter]", filter.name().toLowerCase()))); + } + else + { + builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".description")); + } + + // Get only possible actions, by removing all inactive ones. + List activeActions = new ArrayList<>(template.actions()); + + // Add Click handler + builder.clickHandler((panel, user, clickType, i) -> + { + for (ItemTemplateRecord.ActionRecords action : activeActions) + { + if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + { + if ("UP".equalsIgnoreCase(action.actionType())) + { + this.activeFilter = Utils.getNextValue(Filter.values(), filter); + + // Update filters. + this.updateFilters(); + this.build(); + } + else if ("DOWN".equalsIgnoreCase(action.actionType())) + { + this.activeFilter = Utils.getPreviousValue(Filter.values(), filter); + + // Update filters. + this.updateFilters(); + this.build(); + } + else if ("SELECT".equalsIgnoreCase(action.actionType())) + { + this.activeFilter = filter; + + // Update filters. + this.updateFilters(); + this.build(); + } + } + } + + return true; + }); + + // Collect tooltips. + List tooltips = activeActions.stream(). + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + builder.glow(this.activeFilter == filter); + + return builder.build(); + } + + // --------------------------------------------------------------------- // Section: Create common buttons // --------------------------------------------------------------------- @@ -646,6 +815,26 @@ private enum Tab } + /** + * Sorting order of blocks. + */ + private enum Filter + { + /** + * By name + */ + NAME, + /** + * By value + */ + VALUE, + /** + * By number + */ + COUNT + } + + // --------------------------------------------------------------------- // Section: Variables // --------------------------------------------------------------------- @@ -689,4 +878,9 @@ private enum Tab * This variable stores which tab currently is active. */ private Tab activeTab; + + /** + * This variable stores active filter for items. + */ + private Filter activeFilter; } diff --git a/src/main/java/world/bentobox/level/util/Utils.java b/src/main/java/world/bentobox/level/util/Utils.java index 2c6cd71..45bc002 100644 --- a/src/main/java/world/bentobox/level/util/Utils.java +++ b/src/main/java/world/bentobox/level/util/Utils.java @@ -73,4 +73,62 @@ public static String getPermissionValue(User user, String permissionPrefix, Stri return defaultValue; } + + + /** + * This method allows to get next value from array list after given value. + * + * @param values Array that should be searched for given value. + * @param currentValue Value which next element should be found. + * @param Instance of given object. + * @return Next value after currentValue in values array. + */ + public static T getNextValue(T[] values, T currentValue) + { + for (int i = 0; i < values.length; i++) + { + if (values[i].equals(currentValue)) + { + if (i + 1 == values.length) + { + return values[0]; + } + else + { + return values[i + 1]; + } + } + } + + return currentValue; + } + + + /** + * This method allows to get previous value from array list after given value. + * + * @param values Array that should be searched for given value. + * @param currentValue Value which previous element should be found. + * @param Instance of given object. + * @return Previous value before currentValue in values array. + */ + public static T getPreviousValue(T[] values, T currentValue) + { + for (int i = 0; i < values.length; i++) + { + if (values[i].equals(currentValue)) + { + if (i > 0) + { + return values[i - 1]; + } + else + { + return values[values.length - 1]; + } + } + } + + return currentValue; + } } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 255e481..f7eeb7a 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -123,6 +123,19 @@ level: name: "&f&l Spawners" description: |- &7 Display only spawners. + filters: + name: + name: "&f&l Sort by Name" + description: |- + &7 Sort all blocks by name. + value: + name: "&f&l Sort by Value" + description: |- + &7 Sort all blocks by their value. + count: + name: "&f&l Sort by Count" + description: |- + &7 Sort all blocks by their amount. # Button that is used in multi-page GUIs which allows to return to previous page. previous: name: "&f&l Previous Page" @@ -137,6 +150,9 @@ level: click-to-view: "&e Click &7 to view." click-to-previous: "&e Click &7 to view previous page." click-to-next: "&e Click &7 to view next page." + click-to-select: "&e Click &7 to select." + left-click-to-cycle-up: "&e Left Click &7 to cycle up." + right-click-to-cycle-down: "&e Right Click &7 to cycle down." conversations: # Prefix for messages that are send from server. prefix: "&l&6 [BentoBox]: &r" diff --git a/src/main/resources/panels/detail_panel.yml b/src/main/resources/panels/detail_panel.yml index 61daf9c..927a788 100644 --- a/src/main/resources/panels/detail_panel.yml +++ b/src/main/resources/panels/detail_panel.yml @@ -19,7 +19,7 @@ detail_panel: tab: ALL_BLOCKS actions: view: - click-type: unknwon + click-type: unknown tooltip: level.gui.tips.click-to-view 3: icon: GRASS_BLOCK @@ -30,7 +30,7 @@ detail_panel: tab: ABOVE_SEA_LEVEL actions: view: - click-type: unknwon + click-type: unknown tooltip: level.gui.tips.click-to-view 4: icon: WATER_BUCKET @@ -41,7 +41,7 @@ detail_panel: tab: UNDERWATER actions: view: - click-type: unknwon + click-type: unknown tooltip: level.gui.tips.click-to-view 5: icon: SPAWNER @@ -52,8 +52,29 @@ detail_panel: tab: SPAWNER actions: view: - click-type: unknwon + click-type: unknown tooltip: level.gui.tips.click-to-view + 9: + # You can create multiple buttons. By default it is one. + icon: IRON_TRAPDOOR + # [filter] is placeholder for different filter types. It will be replaced with name, value, count. + title: level.gui.buttons.filters.[filter].name + description: level.gui.buttons.filters.[filter].description + data: + type: FILTER + # the value of filter button. Suggestion is to leave fist value to name if you use single button. + filter: NAME + actions: + up: + click-type: left + tooltip: level.gui.tips.left-click-to-cycle-up + down: + click-type: right + tooltip: level.gui.tips.right-click-to-cycle-down + # There is also select action. With it you can create multiple filter buttons. + # select: + # click-type: unknown + # tooltip: level.gui.tips.click-to-select 2: 2: material_button 3: material_button diff --git a/src/main/resources/panels/top_panel.yml b/src/main/resources/panels/top_panel.yml index 83d49ce..2260c3a 100644 --- a/src/main/resources/panels/top_panel.yml +++ b/src/main/resources/panels/top_panel.yml @@ -11,7 +11,7 @@ top_panel: content: 2: 5: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -22,7 +22,7 @@ top_panel: title: level.gui.buttons.island.empty 3: 4: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -32,7 +32,7 @@ top_panel: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty 6: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -43,7 +43,7 @@ top_panel: title: level.gui.buttons.island.empty 4: 2: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -53,7 +53,7 @@ top_panel: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty 3: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -63,7 +63,7 @@ top_panel: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty 4: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -73,7 +73,7 @@ top_panel: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty 5: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -83,7 +83,7 @@ top_panel: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty 6: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -93,7 +93,7 @@ top_panel: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty 7: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -103,7 +103,7 @@ top_panel: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty 8: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: @@ -114,7 +114,7 @@ top_panel: title: level.gui.buttons.island.empty 6: 5: - icon: PLAYER_HEAD + #icon: PLAYER_HEAD title: level.gui.buttons.island.name description: level.gui.buttons.island.description data: From 1914fc11e037e5f590361e6c08e4c2a485ba3b8f Mon Sep 17 00:00:00 2001 From: BONNe Date: Thu, 16 Jun 2022 17:38:09 +0300 Subject: [PATCH 036/106] Implement calculated value for blocks. (#260) It is ~ value, as calculation formula cannot be applied per block. At least I think so. Part of #192 --- .../bentobox/level/panels/DetailsPanel.java | 19 ++++++++++++++++--- src/main/resources/locales/en-US.yml | 2 ++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/level/panels/DetailsPanel.java b/src/main/java/world/bentobox/level/panels/DetailsPanel.java index deac18b..bbffd57 100644 --- a/src/main/java/world/bentobox/level/panels/DetailsPanel.java +++ b/src/main/java/world/bentobox/level/panels/DetailsPanel.java @@ -180,8 +180,16 @@ private void updateFilters() { sorter = (o1, o2) -> { - int o1Value = this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0); - int o2Value = this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0); + int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o1.getKey(), 0); + int o1Count = blockLimit > 0 ? Math.min(o1.getValue(), blockLimit) : o1.getValue(); + + blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o2.getKey(), 0); + int o2Count = blockLimit > 0 ? Math.min(o2.getValue(), blockLimit) : o2.getValue(); + + long o1Value = (long) o1Count * + this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0); + long o2Value = (long) o2Count * + this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0); if (o1Value == o2Value) { @@ -192,7 +200,7 @@ private void updateFilters() } else { - return Integer.compare(o2Value, o1Value); + return Long.compare(o2Value, o1Value); } }; } @@ -659,12 +667,17 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, String count = this.user.getTranslationOrNothing(reference + "count", "[number]", String.valueOf(materialCount.getValue())); + long calculatedValue = (long) Math.min(blockLimit > 0 ? blockLimit : Integer.MAX_VALUE, materialCount.getValue()) * blockValue; + String valueText = calculatedValue > 0 ? this.user.getTranslationOrNothing(reference + "calculated", + "[number]", String.valueOf(calculatedValue)) : ""; + if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), "[description]", description, "[id]", blockId, "[value]", value, + "[calculated]", valueText, "[limit]", limit, "[count]", count). replaceAll("(?m)^[ \\t]*\\r?\\n", ""). diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index f7eeb7a..dd2e73b 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -96,12 +96,14 @@ level: [description] [count] [value] + [calculated] [limit] [id] id: "&7 Block id: &e [id]" value: "&7 Block value: &e [number]" limit: "&7 Block limit: &e [number]" count: "&7 Number of blocks: &e [number]" + calculated: "&7 Calculated value: &e [number]" all_blocks: name: "&f&l All Blocks" description: |- From cc90579f51d35d531e51b56558004a462328c2d3 Mon Sep 17 00:00:00 2001 From: KrazyxWolf <68208993+KrazyxWolf@users.noreply.github.com> Date: Thu, 16 Jun 2022 13:21:40 -0500 Subject: [PATCH 037/106] Update es.yml (#261) --- src/main/resources/locales/es.yml | 156 +++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 16 deletions(-) diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index 5f559de..7f8616b 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -6,32 +6,156 @@ admin: level: parameters: "" - description: "calcula el nivel de la isla para el jugador" + description: "Calcula el nivel de la isla del jugador" + sethandicap: + parameters: + description: "Define la desventaja de la isla, usualmente el nivel inicial para nuevas islas" + changed: "&aDesventaja inicial de la isla cambiado de [number] a [new_number]." + invalid-level: "&cNúmero no válido. Usa un número entero." + levelstatus: + description: "Muestra cuantas islas hay en la cola para escanear" + islands-in-queue: "&aIslas en cola: [number]" top: - description: "mostrar la lista de los diez primeros" - unknown-world: "&cMundo Desconocido!" + description: "Muestra la lista de las diez primeras islas" + unknown-world: "&c¡Mundo desconocido!" display: "&f[rank]. &a[name] &7- &b[level]" + remove: + description: "Elimina a un jugador de los diez primeros" + parameters: "" + island: level: parameters: "[player]" - description: "calcula tu nivel de isla o muestra el nivel de [player]" - calculating: "&aCalculando nivel..." + description: "Calcula tu nivel de isla o muestra el nivel de [player]" + calculating: "&aCalculando nivel..." + estimated-wait: "&aEspera estimada: [number] segundos" + in-queue: "&aEstás en el puesto [number] de la cola" island-level-is: "&aNivel de isla es de &b[level]" required-points-to-next-level: "&a[points] Puntos requeridos hasta el siguiente nivel." deaths: "&c([number] Muertes)" - cooldown: "&cDebes esperar &b[time] &csegundos para poder volver a hacer eso" + cooldown: "&cDebes esperar &b[time] &csegundos para poder volver a hacer esto." + in-progress: "&6El Calculo del nivel de la islas está en progreso..." + time-out: "&cEl calculo del nivel de la isla está tardando. Intente más tarde." top: - description: "mostrar los diez primeros" - gui-title: "&aDiez primeros" - gui-heading: "&6[name]: &B[rank]" - island-level: "&BNivel [level]" - warp-to: "&ATeletransportandote a la isla de [name]" + description: "Muestra el top de islas" + gui-title: "&aTop diez" + gui-heading: "&6[name]: &b[rank]" + island-level: "&bNivel [level]" + warp-to: "&aLlevándote a la isla de [name]" + + level-details: + above-sea-level-blocks: "Bloques sobre el nivel del mar" + spawners: "Spawners" + underwater-blocks: "Bloques debajo del nivel del mar" + all-blocks: "Todos los bloques" + no-island: "&c¡Sin isla!" + names-island: "Isla de [name]" + syntax: "[name] x [number]" + hint: "&cEscriba /level para ver el recuento de bloques" value: - description: "muestra el valor de cualquier bloque" - success: "&7El valor de este bloque es: &e[value]" - success-underwater: "&7El valor de este bloque bajo el nivel del mar: &e[value]" - empty-hand: "&cNo hay bloques en tu mano" - no-value: "&cEse item no tiene valor" \ No newline at end of file + description: "Muestra el valor de un bloque en la mano" + success: "&7El valor del este bloque es: &e[value]" + success-underwater: "&7El valor de este bloque debajo del nivel del mar es: &e[value]" + empty-hand: "&cNo hay bloques en tu mano." + no-value: "&cEste objeto no tiene valor." + +level: + gui: + titles: + top: "&0&lTop de islas" + detail-panel: "&0&lIsla de [name]" + buttons: + island: + empty: '&f&l[name]. lugar' + name: '&f&l[name]' + description: |- + [owner] + [members] + [place] + [level] + # Text that is replacing [name] if island do not have a name + owners-island: "Isla de [player]" + # Text for [owner] in description. + owner: "&7&l Dueño: &r&b[player]" + # Title before listing members for [members] in description + members-title: "&7&l Miembros:" + # List each member under the title for [members] in description + member: "&b - [player]" + # Name of unknown player. + unknown: " desconocido" + # Section for parsing [place] + place: "&7&o [number]. &r&7lugar" + # Section for parsing [level] + level: "&7 Nivel: &o[number]" + material: + name: "&f&l[number] x [material]" + description: |- + [description] + [count] + [value] + [calculated] + [limit] + [id] + id: "&7 ID del bloque: &e[id]" + value: "&7 Valor del bloque: &e[number]" + limit: "&7 Limite de bloques: &e[number]" + count: "&7 Número de bloques: &e[number]" + calculated: "&7 Valor calculado: &e[number]" + all_blocks: + name: "&f&lTodos los bloques" + description: |- + &7 Muestra todos los + &7 bloques en la isla. + above_sea_level: + name: "&f&lBloques sobre el nivel del mar" + description: |- + &7 Muestra solo bloques + &7 que estén sobre el + &7 nivel del mar. + underwater: + name: "&f&lBloques debajo del nivel del mar" + description: |- + &7 Muestra solo bloques + &7 que estén debajo del + &7 nivel del mar. + spawner: + name: "&f&lSpawners" + description: |- + &7Mostrar solo spawners. + filters: + name: + name: "&f&lOrdenar por nombre" + description: |- + &7Ordenar todos los bloques por nombre. + value: + name: "&f&lOrdenar por valor" + description: |- + &7Ordenar todos los bloques por valor. + count: + name: "&f&lOrdenar por cantidad" + description: |- + &7Ordenar todos los bloques por cantidad. + # Button that is used in multi-page GUIs which allows to return to previous page. + previous: + name: "&f&lPágina anterior" + description: |- + &7Cambiar a la página [number] + # Button that is used in multi-page GUIs which allows to go to next page. + next: + name: "&f&lSiguiente página" + description: |- + &7Cambiar a la página [number] + tips: + click-to-view: "&eClic &7para ver." + click-to-previous: "&eClic &7 para ir a la página anterior." + click-to-next: "&eClic &7 para ir a la siguiente página." + click-to-select: "&eClic &7 para seleccionar." + left-click-to-cycle-up: "&eClic izquierdo &7para ir hacia arriba." + right-click-to-cycle-down: "&eClic derecho &7para ir hacia abajo." + conversations: + # Prefix for messages that are send from server. + prefix: "&l&6[BentoBox]: &r" + no-data: "&cEscriba /level para ver el recuento de bloques." From 47053fde31008bcc8ff5bc071e50814888ecbe48 Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 17 Jun 2022 14:40:10 +0300 Subject: [PATCH 038/106] Implement customizable Values GUI. (#262) This GUI shows value to all items in game. It also shows max limit of blocks, if it is set. Fixes of #192 --- pom.xml | 2 +- src/main/java/world/bentobox/level/Level.java | 1 + .../level/commands/IslandValueCommand.java | 134 ++- .../bentobox/level/panels/DetailsPanel.java | 105 +-- .../bentobox/level/panels/ValuePanel.java | 773 ++++++++++++++++++ .../level/util/ConversationUtils.java | 119 +++ .../java/world/bentobox/level/util/Utils.java | 91 +++ src/main/resources/locales/en-US.yml | 55 +- src/main/resources/panels/value_panel.yml | 109 +++ 9 files changed, 1262 insertions(+), 127 deletions(-) create mode 100644 src/main/java/world/bentobox/level/panels/ValuePanel.java create mode 100644 src/main/java/world/bentobox/level/util/ConversationUtils.java create mode 100644 src/main/resources/panels/value_panel.yml diff --git a/pom.xml b/pom.xml index fdc2584..b0c45ca 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 1.16.5-R0.1-SNAPSHOT 1.20.0 - 1.0.0 + 1.1.0 ${build.version}-SNAPSHOT diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 96e0714..33c76a0 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -78,6 +78,7 @@ public void onLoad() { // Save existing panels. this.saveResource("panels/top_panel.yml", false); this.saveResource("panels/detail_panel.yml", false); + this.saveResource("panels/value_panel.yml", false); } private boolean loadSettings() { diff --git a/src/main/java/world/bentobox/level/commands/IslandValueCommand.java b/src/main/java/world/bentobox/level/commands/IslandValueCommand.java index ffdb0a7..7e04a53 100644 --- a/src/main/java/world/bentobox/level/commands/IslandValueCommand.java +++ b/src/main/java/world/bentobox/level/commands/IslandValueCommand.java @@ -1,6 +1,9 @@ package world.bentobox.level.commands; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Optional; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -8,43 +11,132 @@ import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.util.Util; import world.bentobox.level.Level; +import world.bentobox.level.panels.ValuePanel; +import world.bentobox.level.util.Utils; -public class IslandValueCommand extends CompositeCommand { + +public class IslandValueCommand extends CompositeCommand +{ private final Level addon; - public IslandValueCommand(Level addon, CompositeCommand parent) { + + public IslandValueCommand(Level addon, CompositeCommand parent) + { super(parent, "value"); this.addon = addon; } + @Override - public void setup() { + public void setup() + { this.setPermission("island.value"); - this.setDescription("island.value.description"); + this.setParametersHelp("level.commands.value.parameters"); + this.setDescription("level.commands.value.description"); this.setOnlyPlayer(true); } + @Override - public boolean execute(User user, String label, List args) { - Player player = user.getPlayer(); - PlayerInventory inventory = player.getInventory(); - if (!inventory.getItemInMainHand().getType().equals(Material.AIR)) { - Material material = inventory.getItemInMainHand().getType(); - Integer value = addon.getBlockConfig().getValue(getWorld(), material); - if (value != null) { - user.sendMessage("island.value.success", "[value]", String.valueOf(value)); - double underWater = addon.getSettings().getUnderWaterMultiplier(); - if (underWater > 1.0) { - user.sendMessage("island.value.success-underwater", "[value]", (underWater * value) + ""); - } - } else { - user.sendMessage("island.value.no-value"); + public boolean execute(User user, String label, List args) + { + if (args.size() > 1) + { + this.showHelp(this, user); + return true; + } + + if (args.isEmpty()) + { + ValuePanel.openPanel(this.addon, this.getWorld(), user); + } + else if (args.get(0).equalsIgnoreCase("HAND")) + { + Player player = user.getPlayer(); + PlayerInventory inventory = player.getInventory(); + + if (!inventory.getItemInMainHand().getType().equals(Material.AIR)) + { + this.printValue(user, inventory.getItemInMainHand().getType()); + } + else + { + Utils.sendMessage(user, user.getTranslation("level.conversations.empty-hand")); + } + } + else + { + Material material = Material.matchMaterial(args.get(0)); + + if (material == null) + { + Utils.sendMessage(user, + user.getTranslation(this.getWorld(), "level.conversations.unknown-item", + "[material]", args.get(0))); + } + else + { + this.printValue(user, material); } - } else { - user.sendMessage("island.value.empty-hand"); } + return true; } -} + + /** + * This method prints value of the given material in chat. + * @param user User who receives the message. + * @param material Material value. + */ + private void printValue(User user, Material material) + { + Integer value = this.addon.getBlockConfig().getValue(getWorld(), material); + + if (value != null) + { + Utils.sendMessage(user, + user.getTranslation(this.getWorld(), "level.conversations.value", + "[value]", String.valueOf(value), + "[material]", Utils.prettifyObject(material, user))); + + double underWater = this.addon.getSettings().getUnderWaterMultiplier(); + + if (underWater > 1.0) + { + Utils.sendMessage(user, + user.getTranslation(this.getWorld(),"level.conversations.success-underwater", + "[value]", (underWater * value) + ""), + "[material]", Utils.prettifyObject(material, user)); + } + } + else + { + Utils.sendMessage(user, + user.getTranslation(this.getWorld(),"level.conversations.no-value")); + } + } + + + @Override + public Optional> tabComplete(User user, String alias, List args) + { + String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; + + if (args.isEmpty()) + { + // Don't show every player on the server. Require at least the first letter + return Optional.empty(); + } + + List options = new ArrayList<>(Arrays.stream(Material.values()). + filter(Material::isBlock). + map(Material::name).toList()); + + options.add("HAND"); + + return Optional.of(Util.tabLimit(options, lastArg)); + } +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/level/panels/DetailsPanel.java b/src/main/java/world/bentobox/level/panels/DetailsPanel.java index bbffd57..5a13682 100644 --- a/src/main/java/world/bentobox/level/panels/DetailsPanel.java +++ b/src/main/java/world/bentobox/level/panels/DetailsPanel.java @@ -165,8 +165,8 @@ private void updateFilters() { if (o1.getValue().equals(o2.getValue())) { - String o1Name = DetailsPanel.prettifyObject(o1.getKey(), this.user); - String o2Name = DetailsPanel.prettifyObject(o2.getKey(), this.user); + String o1Name = Utils.prettifyObject(o1.getKey(), this.user); + String o2Name = Utils.prettifyObject(o2.getKey(), this.user); return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); } @@ -193,8 +193,8 @@ private void updateFilters() if (o1Value == o2Value) { - String o1Name = DetailsPanel.prettifyObject(o1.getKey(), this.user); - String o2Name = DetailsPanel.prettifyObject(o2.getKey(), this.user); + String o1Name = Utils.prettifyObject(o1.getKey(), this.user); + String o2Name = Utils.prettifyObject(o2.getKey(), this.user); return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); } @@ -208,8 +208,8 @@ private void updateFilters() { sorter = (o1, o2) -> { - String o1Name = DetailsPanel.prettifyObject(o1.getKey(), this.user); - String o2Name = DetailsPanel.prettifyObject(o2.getKey(), this.user); + String o1Name = Utils.prettifyObject(o1.getKey(), this.user); + String o2Name = Utils.prettifyObject(o2.getKey(), this.user); return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); }; @@ -647,10 +647,10 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, { builder.name(this.user.getTranslation(this.world, template.title(), "[number]", String.valueOf(materialCount.getValue()), - "[material]", DetailsPanel.prettifyObject(materialCount.getKey(), this.user))); + "[material]", Utils.prettifyObject(materialCount.getKey(), this.user))); } - String description = DetailsPanel.prettifyDescription(materialCount.getKey(), this.user); + String description = Utils.prettifyDescription(materialCount.getKey(), this.user); final String reference = "level.gui.buttons.material."; String blockId = this.user.getTranslationOrNothing(reference + "id", @@ -710,95 +710,6 @@ public static void openPanel(Level addon, } - /** - * Prettify Material object for user. - * @param object Object that must be pretty. - * @param user User who will see the object. - * @return Prettified string for Material. - */ - private static String prettifyObject(Material object, User user) - { - // Nothing to translate - if (object == null) - { - return ""; - } - - // Find addon structure with: - // [addon]: - // materials: - // [material]: - // name: [name] - String translation = user.getTranslationOrNothing("level.materials." + object.name().toLowerCase() + ".name"); - - if (!translation.isEmpty()) - { - // We found our translation. - return translation; - } - - // Find addon structure with: - // [addon]: - // materials: - // [material]: [name] - - translation = user.getTranslationOrNothing("level.materials." + object.name().toLowerCase()); - - if (!translation.isEmpty()) - { - // We found our translation. - return translation; - } - - // Find general structure with: - // materials: - // [material]: [name] - - translation = user.getTranslationOrNothing("materials." + object.name().toLowerCase()); - - if (!translation.isEmpty()) - { - // We found our translation. - return translation; - } - - // Use Lang Utils Hook to translate material - return LangUtilsHook.getMaterialName(object, user); - } - - - /** - * Prettify Material object description for user. - * @param object Object that must be pretty. - * @param user User who will see the object. - * @return Prettified description string for Material. - */ - public static String prettifyDescription(Material object, User user) - { - // Nothing to translate - if (object == null) - { - return ""; - } - - // Find addon structure with: - // [addon]: - // materials: - // [material]: - // description: [text] - String translation = user.getTranslationOrNothing("level.materials." + object.name().toLowerCase() + ".description"); - - if (!translation.isEmpty()) - { - // We found our translation. - return translation; - } - - // No text to return. - return ""; - } - - // --------------------------------------------------------------------- // Section: Enums // --------------------------------------------------------------------- diff --git a/src/main/java/world/bentobox/level/panels/ValuePanel.java b/src/main/java/world/bentobox/level/panels/ValuePanel.java new file mode 100644 index 0000000..fdc04ae --- /dev/null +++ b/src/main/java/world/bentobox/level/panels/ValuePanel.java @@ -0,0 +1,773 @@ +package world.bentobox.level.panels; + + +import com.google.common.base.Enums; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import java.io.File; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.TemplatedPanel; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.hooks.LangUtilsHook; +import world.bentobox.bentobox.util.Pair; +import world.bentobox.level.Level; +import world.bentobox.level.util.ConversationUtils; +import world.bentobox.level.util.Utils; + + +/** + * This class opens GUI that shows generator view for user. + */ +public class ValuePanel +{ + // --------------------------------------------------------------------- + // Section: Internal Constructor + // --------------------------------------------------------------------- + + + /** + * This is internal constructor. It is used internally in current class to avoid creating objects everywhere. + * + * @param addon Level object + * @param world World where user is operating + * @param user User who opens panel + */ + private ValuePanel(Level addon, + World world, + User user) + { + this.addon = addon; + this.world = world; + this.user = user; + + this.activeFilter = Filter.NAME_ASC; + this.materialRecordList = Arrays.stream(Material.values()). + filter(Material::isBlock). + filter(m -> !m.name().startsWith("LEGACY_")). + map(material -> + { + Integer value = this.addon.getBlockConfig().getValue(this.world, material); + Integer limit = this.addon.getBlockConfig().getBlockLimits().get(material); + + return new MaterialRecord(material, + value != null ? value : 0, + limit != null ? limit : 0); + }). + collect(Collectors.toList()); + + this.elementList = new ArrayList<>(Material.values().length); + this.searchText = ""; + + this.updateFilters(); + } + + + /** + * This method builds this GUI. + */ + private void build() + { + // Start building panel. + TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); + panelBuilder.user(this.user); + panelBuilder.world(this.user.getWorld()); + + panelBuilder.template("value_panel", new File(this.addon.getDataFolder(), "panels")); + + panelBuilder.registerTypeBuilder("NEXT", this::createNextButton); + panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton); + panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton); + + panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton); + panelBuilder.registerTypeBuilder("SEARCH", this::createSearchButton); + + // Register unknown type builder. + panelBuilder.build(); + } + + + /** + * This method updates filter of elements based on tabs. + */ + private void updateFilters() + { + Comparator sorter; + + switch (this.activeFilter) + { + case VALUE_ASC -> + { + sorter = (o1, o2) -> + { + if (o1.value().equals(o2.value())) + { + String o1Name = Utils.prettifyObject(o1.material(), this.user); + String o2Name = Utils.prettifyObject(o2.material(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + } + else + { + return Integer.compare(o1.value(), o2.value()); + } + }; + } + case VALUE_DESC -> + { + sorter = (o1, o2) -> + { + if (o1.value().equals(o2.value())) + { + String o1Name = Utils.prettifyObject(o1.material(), this.user); + String o2Name = Utils.prettifyObject(o2.material(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + } + else + { + return Integer.compare(o2.value(), o1.value()); + } + }; + } + case NAME_DESC -> + { + sorter = (o1, o2) -> + { + String o1Name = Utils.prettifyObject(o1.material(), this.user); + String o2Name = Utils.prettifyObject(o2.material(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o2Name, o1Name); + }; + } + default -> + { + sorter = (o1, o2) -> + { + String o1Name = Utils.prettifyObject(o1.material(), this.user); + String o2Name = Utils.prettifyObject(o2.material(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + }; + } + } + + this.materialRecordList.sort(sorter); + + if (!this.searchText.isBlank()) + { + this.elementList = new ArrayList<>(this.materialRecordList.size()); + final String text = this.searchText.toLowerCase(); + + this.materialRecordList.forEach(record -> + { + if (record.material.name().toLowerCase().contains(text) || + Utils.prettifyObject(record.material(), this.user).toLowerCase().contains(text)) + { + this.elementList.add(record); + } + }); + } + else + { + this.elementList = this.materialRecordList; + } + + this.pageIndex = 0; + } + + +// --------------------------------------------------------------------- +// Section: Tab Button Type +// --------------------------------------------------------------------- + + + /** + * Create tab button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + // Set icon + builder.icon(template.icon().clone()); + } + + if (template.title() != null) + { + // Set title + builder.name(this.user.getTranslation(this.world, template.title(), "[text]", this.searchText)); + } + + if (template.description() != null) + { + // Set description + builder.description(this.user.getTranslation(this.world, template.description(), "[text]", this.searchText)); + } + + // Get only possible actions, by removing all inactive ones. + List activeActions = new ArrayList<>(template.actions()); + + activeActions.removeIf(action -> + "CLEAR".equalsIgnoreCase(action.actionType()) && this.searchText.isBlank()); + + // Add Click handler + builder.clickHandler((panel, user, clickType, i) -> + { + for (ItemTemplateRecord.ActionRecords action : activeActions) + { + if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + { + if ("CLEAR".equalsIgnoreCase(action.actionType())) + { + this.searchText = ""; + + // Update filters. + this.updateFilters(); + this.build(); + } + else if ("INPUT".equalsIgnoreCase(action.actionType())) + { + // Create consumer that process description change + Consumer consumer = value -> + { + if (value != null) + { + this.searchText = value; + this.updateFilters(); + } + + this.build(); + }; + + // start conversation + ConversationUtils.createStringInput(consumer, + user, + user.getTranslation("level.conversations.write-search"), + user.getTranslation("level.conversations.search-updated")); + } + } + } + + return true; + }); + + // Collect tooltips. + List tooltips = activeActions.stream(). + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + builder.glow(!this.searchText.isBlank()); + + return builder.build(); + } + + + /** + * Create next button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createFilterButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + // Set icon + builder.icon(template.icon().clone()); + } + + String filterName = String.valueOf(template.dataMap().get("filter")); + + final String reference = "level.gui.buttons.filters."; + + if (template.title() != null) + { + // Set title + builder.name(this.user.getTranslation(this.world, template.title())); + } + else + { + builder.name(this.user.getTranslation(this.world, reference + filterName.toLowerCase() + ".name")); + } + + if (template.description() != null) + { + // Set description + builder.description(this.user.getTranslation(this.world, template.description())); + } + else + { + builder.name(this.user.getTranslation(this.world, reference + filterName.toLowerCase() + ".description")); + } + + // Get only possible actions, by removing all inactive ones. + List activeActions = new ArrayList<>(template.actions()); + + activeActions.removeIf(action -> { + if (this.activeFilter.name().startsWith(filterName)) + { + return this.activeFilter.name().endsWith("ASC") && "ASC".equalsIgnoreCase(action.actionType()) || + this.activeFilter.name().endsWith("DESC") && "DESC".equalsIgnoreCase(action.actionType()); + } + else + { + return "DESC".equalsIgnoreCase(action.actionType()); + } + }); + + // Add Click handler + builder.clickHandler((panel, user, clickType, i) -> + { + for (ItemTemplateRecord.ActionRecords action : activeActions) + { + if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + { + if ("ASC".equalsIgnoreCase(action.actionType())) + { + this.activeFilter = Enums.getIfPresent(Filter.class, filterName + "_ASC").or(Filter.NAME_ASC); + + // Update filters. + this.updateFilters(); + this.build(); + } + else if ("DESC".equalsIgnoreCase(action.actionType())) + { + this.activeFilter = Enums.getIfPresent(Filter.class, filterName + "_DESC").or(Filter.NAME_DESC); + + // Update filters. + this.updateFilters(); + this.build(); + } + } + } + + return true; + }); + + // Collect tooltips. + List tooltips = activeActions.stream(). + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + builder.glow(this.activeFilter.name().startsWith(filterName.toUpperCase())); + + return builder.build(); + } + + +// --------------------------------------------------------------------- +// Section: Create common buttons +// --------------------------------------------------------------------- + + + /** + * Create next button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + long size = this.elementList.size(); + + if (size <= slot.amountMap().getOrDefault("BLOCK", 1) || + 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) + { + // There are no next elements + return null; + } + + int nextPageIndex = this.pageIndex + 2; + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + ItemStack clone = template.icon().clone(); + + if ((Boolean) template.dataMap().getOrDefault("indexing", false)) + { + clone.setAmount(nextPageIndex); + } + + builder.icon(clone); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.world, template.title())); + } + + if (template.description() != null) + { + builder.description(this.user.getTranslation(this.world, template.description(), + "[number]", String.valueOf(nextPageIndex))); + } + + // Add ClickHandler + builder.clickHandler((panel, user, clickType, i) -> + { + for (ItemTemplateRecord.ActionRecords action : template.actions()) + { + if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + { + if ("NEXT".equalsIgnoreCase(action.actionType())) + { + this.pageIndex++; + this.build(); + } + } + } + + // Always return true. + return true; + }); + + // Collect tooltips. + List tooltips = template.actions().stream(). + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); + } + + + /** + * Create previous button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + if (this.pageIndex == 0) + { + // There are no next elements + return null; + } + + int previousPageIndex = this.pageIndex; + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + ItemStack clone = template.icon().clone(); + + if ((Boolean) template.dataMap().getOrDefault("indexing", false)) + { + clone.setAmount(previousPageIndex); + } + + builder.icon(clone); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.world, template.title())); + } + + if (template.description() != null) + { + builder.description(this.user.getTranslation(this.world, template.description(), + "[number]", String.valueOf(previousPageIndex))); + } + + // Add ClickHandler + builder.clickHandler((panel, user, clickType, i) -> + { + for (ItemTemplateRecord.ActionRecords action : template.actions()) + { + if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + { + if ("PREVIOUS".equalsIgnoreCase(action.actionType())) + { + this.pageIndex--; + this.build(); + } + } + } + + // Always return true. + return true; + }); + + // Collect tooltips. + List tooltips = template.actions().stream(). + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); + } + + +// --------------------------------------------------------------------- +// Section: Create Material Button +// --------------------------------------------------------------------- + + + /** + * Create material button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + if (this.elementList.isEmpty()) + { + // Does not contain any generators. + return null; + } + + int index = this.pageIndex * slot.amountMap().getOrDefault("BLOCK", 1) + slot.slot(); + + if (index >= this.elementList.size()) + { + // Out of index. + return null; + } + + return this.createMaterialButton(template, this.elementList.get(index)); + } + + + /** + * This method creates button for material. + * + * @param template the template of the button + * @param materialRecord materialRecord which button must be created. + * @return PanelItem for generator tier. + */ + private PanelItem createMaterialButton(ItemTemplateRecord template, + MaterialRecord materialRecord) + { + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + builder.icon(template.icon().clone()); + } + else + { + builder.icon(PanelUtils.getMaterialItem(materialRecord.material())); + } + + if (materialRecord.value() <= 64 && materialRecord.value() > 0) + { + builder.amount(materialRecord.value()); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.world, template.title(), + "[material]", Utils.prettifyObject(materialRecord.material(), this.user))); + } + + String description = Utils.prettifyDescription(materialRecord.material(), this.user); + + final String reference = "level.gui.buttons.material."; + String blockId = this.user.getTranslationOrNothing(reference + "id", + "[id]", materialRecord.material().name()); + + String value = this.user.getTranslationOrNothing(reference + "value", + "[number]", String.valueOf(materialRecord.value())); + + String underWater; + + if (this.addon.getSettings().getUnderWaterMultiplier() > 1.0) + { + underWater = this.user.getTranslationOrNothing(reference + "underwater", + "[number]", String.valueOf(materialRecord.value() * this.addon.getSettings().getUnderWaterMultiplier())); + } + else + { + underWater = ""; + } + + String limit = materialRecord.limit() > 0 ? this.user.getTranslationOrNothing(reference + "limit", + "[number]", String.valueOf(materialRecord.limit())) : ""; + + if (template.description() != null) + { + builder.description(this.user.getTranslation(this.world, template.description(), + "[description]", description, + "[id]", blockId, + "[value]", value, + "[underwater]", underWater, + "[limit]", limit). + replaceAll("(?m)^[ \\t]*\\r?\\n", ""). + replaceAll("(? { + System.out.println("Material: " + materialRecord.material()); + return true; + }); + + return builder.build(); + } + + +// --------------------------------------------------------------------- +// Section: Other Methods +// --------------------------------------------------------------------- + + + /** + * This method is used to open UserPanel outside this class. It will be much easier to open panel with single method + * call then initializing new object. + * + * @param addon Level object + * @param world World where user is operating + * @param user User who opens panel + */ + public static void openPanel(Level addon, + World world, + User user) + { + new ValuePanel(addon, world, user).build(); + } + + +// --------------------------------------------------------------------- +// Section: Enums +// --------------------------------------------------------------------- + + + /** + * Sorting order of blocks. + */ + private enum Filter + { + /** + * By name asc + */ + NAME_ASC, + /** + * By name desc + */ + NAME_DESC, + /** + * By value asc + */ + VALUE_ASC, + /** + * By value desc + */ + VALUE_DESC, + } + + + private record MaterialRecord(Material material, Integer value, Integer limit) + { + } + + +// --------------------------------------------------------------------- +// Section: Variables +// --------------------------------------------------------------------- + + /** + * This variable allows to access addon object. + */ + private final Level addon; + + /** + * This variable holds user who opens panel. Without it panel cannot be opened. + */ + private final User user; + + /** + * This variable holds a world to which gui referee. + */ + private final World world; + + /** + * This variable stores the list of elements to display. + */ + private final List materialRecordList; + + /** + * This variable stores the list of elements to display. + */ + private List elementList; + + /** + * This variable holds current pageIndex for multi-page generator choosing. + */ + private int pageIndex; + + /** + * This variable stores which tab currently is active. + */ + private String searchText; + + /** + * This variable stores active filter for items. + */ + private Filter activeFilter; +} diff --git a/src/main/java/world/bentobox/level/util/ConversationUtils.java b/src/main/java/world/bentobox/level/util/ConversationUtils.java new file mode 100644 index 0000000..ad9dbcc --- /dev/null +++ b/src/main/java/world/bentobox/level/util/ConversationUtils.java @@ -0,0 +1,119 @@ +// +// Created by BONNe +// Copyright - 2021 +// + + +package world.bentobox.level.util; + + +import org.bukkit.conversations.*; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import java.util.function.Consumer; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.user.User; + + +public class ConversationUtils +{ +// --------------------------------------------------------------------- +// Section: Conversation API implementation +// --------------------------------------------------------------------- + + + /** + * This method will close opened gui and writes question in chat. After players answers on question in chat, message + * will trigger consumer and gui will reopen. + * + * @param consumer Consumer that accepts player output text. + * @param question Message that will be displayed in chat when player triggers conversion. + * @param user User who is targeted with current confirmation. + */ + public static void createStringInput(Consumer consumer, + User user, + @NonNull String question, + @Nullable String successMessage) + { + // Text input message. + StringPrompt stringPrompt = new StringPrompt() + { + @Override + public @NonNull String getPromptText(@NonNull ConversationContext context) + { + user.closeInventory(); + return question; + } + + + @Override + public @NonNull Prompt acceptInput(@NonNull ConversationContext context, @Nullable String input) + { + consumer.accept(input); + return ConversationUtils.endMessagePrompt(successMessage); + } + }; + + new ConversationFactory(BentoBox.getInstance()). + withPrefix(context -> user.getTranslation("level.conversations.prefix")). + withFirstPrompt(stringPrompt). + // On cancel conversation will be closed. + withLocalEcho(false). + withTimeout(90). + withEscapeSequence(user.getTranslation("level.conversations.cancel-string")). + // Use null value in consumer to detect if user has abandoned conversation. + addConversationAbandonedListener(ConversationUtils.getAbandonListener(consumer, user)). + buildConversation(user.getPlayer()). + begin(); + } + + + /** + * This is just a simple end message prompt that displays requested message. + * + * @param message Message that will be displayed. + * @return MessagePrompt that displays given message and exists from conversation. + */ + private static MessagePrompt endMessagePrompt(@Nullable String message) + { + return new MessagePrompt() + { + @Override + public @NonNull String getPromptText(@NonNull ConversationContext context) + { + return message == null ? "" : message; + } + + + @Override + protected @Nullable Prompt getNextPrompt(@NonNull ConversationContext context) + { + return Prompt.END_OF_CONVERSATION; + } + }; + } + + + /** + * This method creates and returns abandon listener for every conversation. + * + * @param consumer Consumer which must return null value. + * @param user User who was using conversation. + * @return ConversationAbandonedListener instance. + */ + private static ConversationAbandonedListener getAbandonListener(Consumer consumer, User user) + { + return abandonedEvent -> + { + if (!abandonedEvent.gracefulExit()) + { + consumer.accept(null); + // send cancell message + abandonedEvent.getContext().getForWhom().sendRawMessage( + user.getTranslation("level.conversations.prefix") + + user.getTranslation("level.conversations.cancelled")); + } + }; + } +} diff --git a/src/main/java/world/bentobox/level/util/Utils.java b/src/main/java/world/bentobox/level/util/Utils.java index 45bc002..6d177a1 100644 --- a/src/main/java/world/bentobox/level/util/Utils.java +++ b/src/main/java/world/bentobox/level/util/Utils.java @@ -7,11 +7,13 @@ package world.bentobox.level.util; +import org.bukkit.Material; import org.bukkit.permissions.PermissionAttachmentInfo; import java.util.List; import java.util.stream.Collectors; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.hooks.LangUtilsHook; public class Utils @@ -131,4 +133,93 @@ public static T getPreviousValue(T[] values, T currentValue) return currentValue; } + + + /** + * Prettify Material object for user. + * @param object Object that must be pretty. + * @param user User who will see the object. + * @return Prettified string for Material. + */ + public static String prettifyObject(Material object, User user) + { + // Nothing to translate + if (object == null) + { + return ""; + } + + // Find addon structure with: + // [addon]: + // materials: + // [material]: + // name: [name] + String translation = user.getTranslationOrNothing("level.materials." + object.name().toLowerCase() + ".name"); + + if (!translation.isEmpty()) + { + // We found our translation. + return translation; + } + + // Find addon structure with: + // [addon]: + // materials: + // [material]: [name] + + translation = user.getTranslationOrNothing("level.materials." + object.name().toLowerCase()); + + if (!translation.isEmpty()) + { + // We found our translation. + return translation; + } + + // Find general structure with: + // materials: + // [material]: [name] + + translation = user.getTranslationOrNothing("materials." + object.name().toLowerCase()); + + if (!translation.isEmpty()) + { + // We found our translation. + return translation; + } + + // Use Lang Utils Hook to translate material + return LangUtilsHook.getMaterialName(object, user); + } + + + /** + * Prettify Material object description for user. + * @param object Object that must be pretty. + * @param user User who will see the object. + * @return Prettified description string for Material. + */ + public static String prettifyDescription(Material object, User user) + { + // Nothing to translate + if (object == null) + { + return ""; + } + + // Find addon structure with: + // [addon]: + // materials: + // [material]: + // description: [text] + String translation = user.getTranslationOrNothing("level.materials." + object.name().toLowerCase() + ".description"); + + if (!translation.isEmpty()) + { + // We found our translation. + return translation; + } + + // No text to return. + return ""; + } } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index dd2e73b..af9d584 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -55,18 +55,16 @@ island: syntax: "[name] x [number]" hint: "&c Run level to see the block report" - value: - description: "shows the value of any block" - success: "&7 The value of this block is: &e[value]" - success-underwater: "&7 The value of this block below sea-level: &e[value]" - empty-hand: "&c There are no blocks in your hand" - no-value: "&c That item has no value." - level: + commands: + value: + parameters: "[hand|]" + description: "shows the value of blocks. Add 'hand' at the end to display value for item in hand." gui: titles: top: "&0&l Top Islands" detail-panel: "&0&l [name]'s island" + value-panel: "&0&l Block Values" buttons: island: empty: '&f&l [name]. place' @@ -138,6 +136,18 @@ level: name: "&f&l Sort by Count" description: |- &7 Sort all blocks by their amount. + value: + name: "&f&l [material]" + description: |- + [description] + [value] + [underwater] + [limit] + [id] + id: "&7 Block id: &e [id]" + value: "&7 Block value: &e [number]" + underwater: "&7 Bellow sea level: &e [number]" + limit: "&7 Block limit: &e [number]" # Button that is used in multi-page GUIs which allows to return to previous page. previous: name: "&f&l Previous Page" @@ -148,6 +158,12 @@ level: name: "&f&l Next Page" description: |- &7 Switch to [number] page + search: + name: "&f&l Search" + description: |- + &7 Search for a specific + &7 value. + search: "&b Value: [value]" tips: click-to-view: "&e Click &7 to view." click-to-previous: "&e Click &7 to view previous page." @@ -155,7 +171,30 @@ level: click-to-select: "&e Click &7 to select." left-click-to-cycle-up: "&e Left Click &7 to cycle up." right-click-to-cycle-down: "&e Right Click &7 to cycle down." + left-click-to-change: "&e Left Click &7 to edit." + right-click-to-clear: "&e Right Click &7 to clear." + click-to-asc: "&e Click &7 to sort in increasing order." + click-to-desc: "&e Click &7 to sort in decreasing order." conversations: # Prefix for messages that are send from server. prefix: "&l&6 [BentoBox]: &r" - no-data: "&c Run level to see the block report." \ No newline at end of file + no-data: "&c Run level to see the block report." + # String that allows to cancel conversation. (can be only one) + cancel-string: "cancel" + # List of strings that allows to exit conversation. (separated with ,) + exit-string: "cancel, exit, quit" + # Message that asks for search value input. + write-search: "&e Please enter a search value. (Write 'cancel' to exit)" + # Message that appears after updating search value. + search-updated: "&a Search value updated." + # Message that is sent to user when conversation is cancelled. + cancelled: "&c Conversation cancelled!" + # Message that is sent to user when given material does not have any value. + no-value: "&c That item has no value." + # Message that is sent to user when requested material does not exist. + unknown-item: "&c The '[material]' does not exist in game." + # Messages that is sent to user when requesting value for a specific material. + value: "&7 The value of '[material]' is: &e[value]" + value-underwater: "&7 The value of '[material]' below sea-level: &e[value]" + # Message that is sent to user when he does not hold any items in hand. + empty-hand: "&c There are no blocks in your hand" diff --git a/src/main/resources/panels/value_panel.yml b/src/main/resources/panels/value_panel.yml new file mode 100644 index 0000000..c173313 --- /dev/null +++ b/src/main/resources/panels/value_panel.yml @@ -0,0 +1,109 @@ +value_panel: + title: level.gui.titles.value-panel + type: INVENTORY + background: + icon: BLACK_STAINED_GLASS_PANE + title: "&b&r" # Empty text + border: + icon: BLACK_STAINED_GLASS_PANE + title: "&b&r" # Empty text + force-shown: [] + content: + 1: + 4: + icon: PAPER + title: level.gui.buttons.filters.name.name + description: level.gui.buttons.filters.name.description + data: + type: FILTER + # the value of filter button. Suggestion is to leave fist value to name if you use single button. + filter: NAME + actions: + asc: + click-type: unknown + tooltip: level.gui.tips.click-to-asc + desc: + click-type: unknown + tooltip: level.gui.tips.click-to-desc + 5: + # You can create multiple buttons. By default it is one. + icon: MAP + title: level.gui.buttons.search.name + description: level.gui.buttons.search.description + data: + type: SEARCH + actions: + input: + click-type: left + tooltip: level.gui.tips.left-click-to-change + clear: + click-type: right + tooltip: level.gui.tips.right-click-to-clear + 6: + icon: DIAMOND + title: level.gui.buttons.filters.value.name + description: level.gui.buttons.filters.value.description + data: + type: FILTER + # the value of filter button. Suggestion is to leave fist value to name if you use single button. + filter: VALUE + actions: + asc: + click-type: unknown + tooltip: level.gui.tips.click-to-asc + desc: + click-type: unknown + tooltip: level.gui.tips.click-to-desc + 2: + 2: material_button + 3: material_button + 4: material_button + 5: material_button + 6: material_button + 7: material_button + 8: material_button + 3: + 1: + icon: TIPPED_ARROW:INSTANT_HEAL::::1 + title: level.gui.buttons.previous.name + description: level.gui.buttons.previous.description + data: + type: PREVIOUS + indexing: true + actions: + previous: + click-type: unknown + tooltip: level.gui.tips.click-to-previous + 2: material_button + 3: material_button + 4: material_button + 5: material_button + 6: material_button + 7: material_button + 8: material_button + 9: + icon: TIPPED_ARROW:JUMP::::1 + title: level.gui.buttons.next.name + description: level.gui.buttons.next.description + data: + type: NEXT + indexing: true + actions: + next: + click-type: unknown + tooltip: level.gui.tips.click-to-next + 4: + 2: material_button + 3: material_button + 4: material_button + 5: material_button + 6: material_button + 7: material_button + 8: material_button + reusable: + material_button: + #icon: STONE + title: level.gui.buttons.value.name + description: level.gui.buttons.value.description + data: + type: BLOCK \ No newline at end of file From 90ae98e59994f1e91b603a5eda4411f4752b1f4b Mon Sep 17 00:00:00 2001 From: DeadSilenceIV Date: Thu, 14 Jul 2022 17:53:27 -0500 Subject: [PATCH 039/106] Support for AdvancedChests was updated. (#266) --- pom.xml | 2 +- .../bentobox/level/calculators/IslandLevelCalculator.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index b0c45ca..d49376f 100644 --- a/pom.xml +++ b/pom.xml @@ -209,7 +209,7 @@ com.github.DeadSilenceIV AdvancedChestsAPI - 1.8 + 2.9-BETA provided diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 065f5e0..9c9fb43 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -425,11 +425,11 @@ private void scanChests(Chunk chunk) { for (BlockState bs : chunk.getTileEntities()) { if (bs instanceof Container) { if (addon.isAdvChestEnabled()) { - AdvancedChest aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation()); - if (aChest != null) { + AdvancedChest aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation()); + if (aChest != null && aChest.getChestType().getName().equals("NORMAL")) { aChest.getPages().stream().map(ChestPage::getItems).forEach(c -> { - for (ItemStack i : c) { - countItemStack(i); + for (Object i : c) { + countItemStack((ItemStack)i); } }); continue; From dae3db6c98887ad3b22e442a968909f7c926f90d Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 21 Aug 2022 13:00:56 +0300 Subject: [PATCH 040/106] Implements visit/warp actions in top gui Add 2 new actions for island buttons in TOP GUI: - Visit -> allows to visit island, but it requires Visit Addon - Warp -> allows to warp to island, but it requires Warp Addon Requested via Discord. --- pom.xml | 16 ++++ src/main/java/world/bentobox/level/Level.java | 76 ++++++++++++++++++- .../bentobox/level/panels/TopLevelPanel.java | 63 ++++++++++++--- src/main/resources/locales/en-US.yml | 3 + src/main/resources/panels/top_panel.yml | 70 +++++++++++++++++ 5 files changed, 217 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index d49376f..c36d67d 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,10 @@ 1.16.5-R0.1-SNAPSHOT 1.20.0 + + 1.12.0 + + 1.4.0 1.1.0 @@ -185,6 +189,18 @@ ${bentobox.version} provided + + world.bentobox + warps + ${warps.version} + provided + + + world.bentobox + visit + ${visit.version} + provided + lv.id.bonne panelutils diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 33c76a0..b1e65df 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -42,6 +42,9 @@ import world.bentobox.level.objects.TopTenData; import world.bentobox.level.requests.LevelRequestHandler; import world.bentobox.level.requests.TopTenRequestHandler; +import world.bentobox.visit.VisitAddon; +import world.bentobox.warps.Warp; + /** * @author tastybento @@ -63,6 +66,17 @@ public class Level extends Addon implements Listener { private boolean roseStackersEnabled; private final List registeredGameModes = new ArrayList<>(); + /** + * Local variable that stores if warpHook is present. + */ + private Warp warpHook; + + /** + * Local variable that stores if visitHook is present. + */ + private VisitAddon visitHook; + + @Override public void onLoad() { // Save the default config from config.yml @@ -125,10 +139,10 @@ public void onEnable() { advChestEnabled = advChest != null; if (advChestEnabled) { // Check version - if (compareVersions(advChest.getDescription().getVersion(), "14.2") > 0) { + if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) { log("Hooked into AdvancedChests."); } else { - logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + " - requires version 14.3 or later"); + logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + " - requires version 23.0 or later"); advChestEnabled = false; } } @@ -139,6 +153,45 @@ public void onEnable() { } } + @Override + public void allLoaded() + { + super.allLoaded(); + + if (this.isEnabled()) + { + this.hookExtensions(); + } + } + + + /** + * This method tries to hook into addons and plugins + */ + private void hookExtensions() + { + // Try to find Visit addon and if it does not exist, display a warning + this.getAddonByName("Visit").ifPresentOrElse(addon -> + { + this.visitHook = (VisitAddon) addon; + this.log("Likes Addon hooked into Visit addon."); + }, () -> + { + this.visitHook = null; + }); + + // Try to find Warps addon and if it does not exist, display a warning + this.getAddonByName("Warps").ifPresentOrElse(addon -> + { + this.warpHook = (Warp) addon; + this.log("Likes Addon hooked into Warps addon."); + }, () -> + { + this.warpHook = null; + }); + } + + /** * Compares versions * @param version1 @@ -507,4 +560,23 @@ public boolean isRoseStackersEnabled() { return roseStackersEnabled; } + /** + * Method Level#getVisitHook returns the visitHook of this object. + * + * @return {@code Visit} of this object, {@code null} otherwise. + */ + public VisitAddon getVisitHook() + { + return this.visitHook; + } + + /** + * Method Level#getWarpHook returns the warpHook of this object. + * + * @return {@code Warp} of this object, {@code null} otherwise. + */ + public Warp getWarpHook() + { + return this.warpHook; + } } diff --git a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java index 23c313a..937de87 100644 --- a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java +++ b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java @@ -13,6 +13,7 @@ import java.util.*; import java.util.stream.Collectors; +import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.TemplatedPanel; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; @@ -185,22 +186,66 @@ private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord List activeActions = new ArrayList<>(template.actions()); activeActions.removeIf(action -> - "VIEW".equalsIgnoreCase(action.actionType()) && island.getOwner() == null && - island.getMemberSet(RanksManager.MEMBER_RANK). - contains(this.user.getUniqueId())); + { + switch (action.actionType().toUpperCase()) + { + case "WARP" -> { + return island.getOwner() == null || + this.addon.getWarpHook() == null || + !this.addon.getWarpHook().getWarpSignsManager().hasWarp(this.world, island.getOwner()); + } + case "VISIT" -> { + return island.getOwner() == null || + this.addon.getVisitHook() == null || + !this.addon.getVisitHook().getAddonManager().preprocessTeleportation(this.user, island); + } + case "VIEW" -> { + return island.getOwner() == null || + !island.getMemberSet(RanksManager.MEMBER_RANK).contains(this.user.getUniqueId()); + } + default -> { + return false; + } + } + }); // Add Click handler builder.clickHandler((panel, user, clickType, i) -> { for (ItemTemplateRecord.ActionRecords action : activeActions) { - if ((clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) && - "VIEW".equalsIgnoreCase(action.actionType())) + if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) { - this.user.closeInventory(); - // Open Detailed GUI. - - DetailsPanel.openPanel(this.addon, this.world, this.user); + switch (action.actionType().toUpperCase()) + { + case "WARP" -> { + this.user.closeInventory(); + this.addon.getWarpHook().getWarpSignsManager().warpPlayer(this.world, this.user, island.getOwner()); + } + case "VISIT" -> { + // The command call implementation solves necessity to check for all visits options, + // like cool down, confirmation and preprocess in single go. Would it be better to write + // all logic here? + + this.addon.getPlugin().getIWM().getAddon(this.world). + flatMap(GameModeAddon::getPlayerCommand).ifPresent(command -> + { + String mainCommand = + this.addon.getVisitHook().getSettings().getPlayerMainCommand(); + + if (!mainCommand.isBlank()) + { + this.user.closeInventory(); + this.user.performCommand(command.getTopLabel() + " " + mainCommand + " " + island.getOwner()); + } + }); + } + case "VIEW" -> { + this.user.closeInventory(); + // Open Detailed GUI. + DetailsPanel.openPanel(this.addon, this.world, this.user); + } + } } } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index af9d584..6456818 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -175,6 +175,9 @@ level: right-click-to-clear: "&e Right Click &7 to clear." click-to-asc: "&e Click &7 to sort in increasing order." click-to-desc: "&e Click &7 to sort in decreasing order." + click-to-warp: "&e Click &7 to warp." + click-to-visit: "&e Click &7 to visit." + right-click-to-visit: "&e Right Click &7 to visit." conversations: # Prefix for messages that are send from server. prefix: "&l&6 [BentoBox]: &r" diff --git a/src/main/resources/panels/top_panel.yml b/src/main/resources/panels/top_panel.yml index 2260c3a..3b80784 100644 --- a/src/main/resources/panels/top_panel.yml +++ b/src/main/resources/panels/top_panel.yml @@ -17,6 +17,13 @@ top_panel: data: type: TOP index: 1 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty @@ -28,6 +35,13 @@ top_panel: data: type: TOP index: 2 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty @@ -38,6 +52,13 @@ top_panel: data: type: TOP index: 3 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty @@ -49,6 +70,13 @@ top_panel: data: type: TOP index: 4 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty @@ -59,6 +87,13 @@ top_panel: data: type: TOP index: 5 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty @@ -69,6 +104,13 @@ top_panel: data: type: TOP index: 6 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty @@ -79,6 +121,13 @@ top_panel: data: type: TOP index: 7 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty @@ -89,6 +138,13 @@ top_panel: data: type: TOP index: 8 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty @@ -99,6 +155,13 @@ top_panel: data: type: TOP index: 9 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty @@ -109,6 +172,13 @@ top_panel: data: type: TOP index: 10 + actions: + warp: + click-type: LEFT + tooltip: level.gui.tips.click-to-warp + visit: + click-type: RIGHT + tooltip: level.gui.tips.right-click-to-visit fallback: icon: LIME_STAINED_GLASS_PANE title: level.gui.buttons.island.empty From 2ca4e0a0709c0e6a03b0a095c81bc2ea74612d30 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 21 Aug 2022 17:31:42 +0300 Subject: [PATCH 041/106] Fixes a Level addon crash on startup. Level addon crashed at the startup if Visit or Warps addon were not installed. It happened because Level addon main class were implementing Listener interface. To avoid it and fix the crash, I moved migration listener to a separate class. Fixes #2012 --- src/main/java/world/bentobox/level/Level.java | 46 ++------------ .../world/bentobox/level/LevelsManager.java | 5 +- .../level/listeners/MigrationListener.java | 61 +++++++++++++++++++ 3 files changed, 69 insertions(+), 43 deletions(-) create mode 100644 src/main/java/world/bentobox/level/listeners/MigrationListener.java diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index b1e65df..f41830d 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -13,8 +13,6 @@ import org.bukkit.World; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -22,7 +20,6 @@ import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.configuration.Config; -import world.bentobox.bentobox.api.events.BentoBoxReadyEvent; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.util.Util; @@ -38,6 +35,7 @@ import world.bentobox.level.config.ConfigSettings; import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.JoinLeaveListener; +import world.bentobox.level.listeners.MigrationListener; import world.bentobox.level.objects.LevelsData; import world.bentobox.level.objects.TopTenData; import world.bentobox.level.requests.LevelRequestHandler; @@ -50,7 +48,7 @@ * @author tastybento * */ -public class Level extends Addon implements Listener { +public class Level extends Addon { // The 10 in top ten public static final int TEN = 10; @@ -112,7 +110,8 @@ public void onEnable() { // Register listeners this.registerListener(new IslandActivitiesListeners(this)); this.registerListener(new JoinLeaveListener(this)); - this.registerListener(this); + this.registerListener(new MigrationListener(this)); + // Register commands for GameModes registeredGameModes.clear(); getPlugin().getAddonsManager().getGameModeAddons().stream() @@ -174,7 +173,7 @@ private void hookExtensions() this.getAddonByName("Visit").ifPresentOrElse(addon -> { this.visitHook = (VisitAddon) addon; - this.log("Likes Addon hooked into Visit addon."); + this.log("Level Addon hooked into Visit addon."); }, () -> { this.visitHook = null; @@ -184,7 +183,7 @@ private void hookExtensions() this.getAddonByName("Warps").ifPresentOrElse(addon -> { this.warpHook = (Warp) addon; - this.log("Likes Addon hooked into Warps addon."); + this.log("Level Addon hooked into Warps addon."); }, () -> { this.warpHook = null; @@ -217,39 +216,6 @@ public static int compareVersions(String version1, String version2) { return comparisonResult; } - @EventHandler - public void onBentoBoxReady(BentoBoxReadyEvent e) { - // Perform upgrade check - manager.migrate(); - // Load TopTens - manager.loadTopTens(); - /* - * DEBUG code to generate fake islands and then try to level them all. - Bukkit.getScheduler().runTaskLater(getPlugin(), () -> { - getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) - .forEach(gm -> { - for (int i = 0; i < 1000; i++) { - try { - NewIsland.builder().addon(gm).player(User.getInstance(UUID.randomUUID())).name("default").reason(Reason.CREATE).noPaste().build(); - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - } - }); - // Queue all islands DEBUG - - getIslands().getIslands().stream().filter(Island::isOwned).forEach(is -> { - - this.getManager().calculateLevel(is.getOwner(), is).thenAccept(r -> - log("Result for island calc " + r.getLevel() + " at " + is.getCenter())); - - }); - }, 60L);*/ - } - - private void registerPlaceholders(GameModeAddon gm) { if (getPlugin().getPlaceholdersManager() == null) return; // Island Level diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index 2c182a8..a7fc4a7 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -35,7 +35,6 @@ public class LevelsManager { private static final String INTOPTEN = "intopten"; private static final TreeMap LEVELS; - private static final int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25}; private static final BigInteger THOUSAND = BigInteger.valueOf(1000); static { LEVELS = new TreeMap<>(); @@ -45,7 +44,7 @@ public class LevelsManager { LEVELS.put(THOUSAND.pow(3), "G"); LEVELS.put(THOUSAND.pow(4), "T"); } - private Level addon; + private final Level addon; // Database handler for level data private final Database handler; @@ -341,7 +340,7 @@ boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) { /** * Loads all the top tens from the database */ - void loadTopTens() { + public void loadTopTens() { topTenLists.clear(); Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> { addon.log("Generating rankings"); diff --git a/src/main/java/world/bentobox/level/listeners/MigrationListener.java b/src/main/java/world/bentobox/level/listeners/MigrationListener.java new file mode 100644 index 0000000..640a26c --- /dev/null +++ b/src/main/java/world/bentobox/level/listeners/MigrationListener.java @@ -0,0 +1,61 @@ +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.level.listeners; + + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import world.bentobox.bentobox.api.events.BentoBoxReadyEvent; +import world.bentobox.level.Level; + + +/** + * This listener checks when BentoBox is ready and then tries to migrate Levels addon database, if it is required. + */ +public class MigrationListener implements Listener +{ + public MigrationListener(Level addon) + { + this.addon = addon; + } + + @EventHandler + public void onBentoBoxReady(BentoBoxReadyEvent e) { + // Perform upgrade check + this.addon.getManager().migrate(); + // Load TopTens + this.addon.getManager().loadTopTens(); + /* + * DEBUG code to generate fake islands and then try to level them all. + Bukkit.getScheduler().runTaskLater(getPlugin(), () -> { + getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) + .forEach(gm -> { + for (int i = 0; i < 1000; i++) { + try { + NewIsland.builder().addon(gm).player(User.getInstance(UUID.randomUUID())).name("default").reason(Reason.CREATE).noPaste().build(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + }); + // Queue all islands DEBUG + + getIslands().getIslands().stream().filter(Island::isOwned).forEach(is -> { + + this.getManager().calculateLevel(is.getOwner(), is).thenAccept(r -> + log("Result for island calc " + r.getLevel() + " at " + is.getCenter())); + + }); + }, 60L);*/ + } + + + private final Level addon; +} From 32690630d6a6eeef9de2229a459c480766c5f7df Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Sat, 27 Aug 2022 15:14:55 +0300 Subject: [PATCH 042/106] Translate pl.yml via GitLocalize (#269) Co-authored-by: wiktorm12 --- src/main/resources/locales/pl.yml | 133 ++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 7 deletions(-) diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index fec739d..93473c9 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -5,6 +5,9 @@ admin: description: oblicza poziom wyspy sethandicap: parameters: " " + description: ustawić 0 poziom wyspy, zwykle poziom wyspy startowej + changed: "&a Początkowy poziom wysp został zmieniony z [number] na [new_number]." + invalid-level: "&c Nieprawidłowy poziom. Użyj liczby całkowitej." levelstatus: description: pokazuje ile wysp znajduje się w kolejce do skanowania islands-in-queue: "&a Wyspy w kolejce: [number]" @@ -23,7 +26,7 @@ island: estimated-wait: "&a Szacowany czas: [number] sekund" in-queue: "&a Jestes numerem [number] w kolejce" island-level-is: "&aPoziom wyspy wynosi &b[level]" - required-points-to-next-level: "&a[points] punktów do następnego poziomu" + required-points-to-next-level: "&aPozostało [points] punktów do następnego poziomu" deaths: "&c([number] śmierci)" cooldown: "&cMusisz zaczekać &b[time] &csekund przed następnym obliczeniem poziomu" in-progress: "&6 Trwa obliczanie poziomu twojej wyspy..." @@ -44,9 +47,125 @@ island: names-island: Wyspa gracza [name] syntax: "[name] x [number]" hint: "&c Uruchom poziom, aby wyświetlić raport o blokach" - value: - description: pokazuje wartość dowolnego przedmiotu - success: "&7Wartość punktowa tego bloku wynosi: &e[value]" - success-underwater: "&7Wartość tego bloku poniżej poziomu morza: &e[value]" - empty-hand: "&cNie trzymasz żadnego bloku." - no-value: "&cTen przedmiot nie ma wartości :(" +level: + commands: + value: + parameters: "[hand|]" + description: pokazuje wartość bloków. Dodaj „hand” na końcu, aby wyświetlić + wartość pozycji w ręku. + gui: + titles: + top: "&0&l Najlepsze wyspy" + detail-panel: "&0&l Wyspa gracza [name] " + value-panel: "&0&l Wartości bloków" + buttons: + island: + empty: "&f&l [name]. miejsce" + name: "&f&l [name]" + description: |- + [owner] + [members] + [place] + [level] + owners-island: wyspa gracza [player] + owner: "&7&l Lider: &r&b [player]" + members-title: "&7&l Członkowie:" + member: "&b - [player]" + unknown: nieznany + place: "&7&o [number]. &r&7 miejsce" + level: "&7 Poziom: &o [number]" + material: + name: "&f&l [number] x [material]" + description: |- + [description] + [count] + [value] + [calculated] + [limit] + [id] + id: "&7 Identyfikator bloku: &e [id]" + value: "&7 Wartość bloku: &e [number]" + limit: "&7 Limit bloków: &e [number]" + count: "&7 Numer bloku: &e [number]" + calculated: "&7 Obliczona wartość: &e [number]" + all_blocks: + name: "&f&l Wszystkie bloki" + description: |- + &7 Wyświetl wszystkie bloki + &7 na wyspie. + above_sea_level: + name: "&f&l Bloki nad poziomem morza" + description: |- + &7 Wyświetlaj tylko bloki + &7 które są nad poziomem + &7 morza + underwater: + name: "&f&l Bloki pod poziomem morza" + description: |- + &7 Wyświetlaj tylko bloki + &7 ponad poziomem morza + spawner: + name: "&f&l Spawnery" + description: "&7 Wyświetlaj tylko spawnery." + filters: + name: + name: "&f&l Sortuj według nazwy" + description: "&7 Sortuj wszystkie bloki według nazwy." + value: + name: "&f&l Sortuj według wartości" + description: "&7 Sortuj wszystkie bloki według ich wartości." + count: + name: "&f&l Sortuj według liczby" + description: "&7 Sortuj wszystkie bloki według ich ilości." + value: + name: "&f&l [material]" + description: |- + [description] + [value] + [underwater] + [limit] + [id] + id: "&7 Identyfikator bloku: &e [id]" + value: "&7 Wartość bloku: &e [number]" + underwater: "&7 Poniżej poziomu morza: &e [number]" + limit: "&7 Limit bloku: &e [number]" + previous: + name: "&f&l Poprzednia strona" + description: "&7 Przełącz na stronę [number]" + next: + name: "&f&l Następna strona" + description: "&7 Przełącz na stronę [number]" + search: + name: "&f&l Szukaj" + description: |- + &7 Wyszukaj konkretną + &7 wartość. + search: "&b Wartość: [value]" + tips: + click-to-view: "&e Kliknij &7, aby wyświetlić." + click-to-previous: "&e Kliknij &7, aby wyświetlić poprzednią stronę." + click-to-next: "&e Kliknij &7, aby wyświetlić następną stronę." + click-to-select: "&e Kliknij &7, aby wybrać." + left-click-to-cycle-up: "&e Kliknij lewym przyciskiem &7, aby przejść w górę." + right-click-to-cycle-down: "&e Kliknij prawym przyciskiem &7, aby przejść w + dół." + left-click-to-change: "&e Kliknij lewym przyciskiem &7, aby edytować." + right-click-to-clear: "&e Kliknij prawym przyciskiem &7, aby wyczyścić." + click-to-asc: "&e Kliknij &7, aby posortować w porządku rosnącym." + click-to-desc: "&e Kliknij &7, aby posortować w porządku malejącym." + click-to-warp: "&e Kliknij&7, aby przenieść" + click-to-visit: "&e Kliknij&7, aby odwiedzić" + right-click-to-visit: "&e Kliknij prawym przyciskiem &7, aby odwiedzić." + conversations: + prefix: "&l&6 [BentoBox]: &r" + no-data: "&c Wykonaj sprawdzenie poziomu, przed raportem bloków" + cancel-string: anuluj + exit-string: cancel, exit, quit, anuluj + write-search: "&e Wprowadź wartość wyszukiwania. (Napisz „anuluj”, aby wyjść)" + search-updated: "&a Zaktualizowano wartość wyszukiwania." + cancelled: "&c Rozmowa została anulowana!" + no-value: "&c Ten element nie ma wartości." + unknown-item: "&c „[material]” nie istnieje w grze." + value: "&7 Wartość '[material]' to: &e[value]" + value-underwater: "&7 Wartość „[material]” poniżej poziomu morza: &e[value]" + empty-hand: "&c W twojej ręce nie ma bloków" From 97d95225638aa2613460ff903edc7b775cf5c30b Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 08:22:33 +0200 Subject: [PATCH 043/106] Translate fr.yml via GitLocalize (#272) Co-authored-by: organizatsiya --- src/main/resources/locales/fr.yml | 131 ++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 8 deletions(-) diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index a7fc26f..f7d914b 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -1,7 +1,7 @@ --- admin: level: - parameters: "" + parameters: "" description: calcule le niveau d'île d'un joueur sethandicap: parameters: " " @@ -18,7 +18,7 @@ admin: display: "&f[rank]. &a[name] &7- &b[level]" remove: description: retire le joueur du top 10 - parameters: "" + parameters: "" island: level: parameters: "[joueur]" @@ -49,12 +49,127 @@ island: names-island: île de [name] syntax: "[name] x [number]" hint: "&c Exécuter level pour voir le rapport des blocs" - value: - description: affiche la valeur d'un bloc - success: "&7Valeur de ce bloc : &e[value]" - success-underwater: "&7Valeur de ce bloc en dessous du niveau de la mer : &e[value]" - empty-hand: "&cIl n'y a aucun bloc dans votre main" - no-value: "&cCet objet n'a pas de valeur." +level: + commands: + value: + parameters: "[hand|]" + description: affiche la valeur des blocs. Ajoutez 'hand' à la fin pour afficher + la valeur de l'objet en main. + gui: + titles: + top: "&0&l Top Islands" + detail-panel: "&0&l [name]'s island" + value-panel: "&0&l Block Values" + buttons: + island: + empty: "&f&l [name]. place" + name: "&f&l [name]" + description: |- + [owner] + [members] + [place] + [level] + owners-island: "[player]'s Island" + owner: "&7&l Propriétaire: &r&b [player]" + members-title: "&7&l Membres:" + member: "&b - [player]" + unknown: inconnue + place: "&7&o [number]. &r&7 place" + level: "&7 Level: &o [number]" + material: + name: "&f&l [number] x [material]" + description: |- + [description] + [count] + [value] + [calculated] + [limit] + [id] + id: "&7 Block id: &e [id]" + value: "&7 Block value: &e [number]" + limit: "&7 Block limit: &e [number]" + count: "&7 Nombre de blocs: &e [number]" + calculated: "&7 Valeur calculée: &e [number]" + all_blocks: + name: "&f&l Tous les blocs" + description: |- + &7 Afficher tous les blocs + &7 sur l'île. + above_sea_level: + name: "&f&l Blocs au-dessus du niveau de la mer" + description: |- + &7 Afficher uniquement les blocs + &7 qui sont au-dessus du niveau + &7 de la mer. + underwater: + name: "&f&l Blocs sous le niveau de la mer" + description: |- + &7 Afficher uniquement les blocs + &7 situés sous le niveau + &7 de la mer. + spawner: + name: "&f&l Spawners" + description: "&7 Afficher uniquement les spawners." + filters: + name: + name: "&f&l STrier par nom" + description: "&7 Trier tous les blocs par nom." + value: + name: "&f&l Trier par valeur" + description: "&7 Triez tous les blocs par leur valeur." + count: + name: "&f&l Trier par nombre" + description: "&7 Trier tous les blocs par leur montant." + value: + name: "&f&l [material]" + description: |- + [description] + [value] + [underwater] + [limit] + [id] + id: "&7 Block id: &e [id]" + value: "&7 Block value: &e [number]" + underwater: "&7 Sous le niveau de la mer : &e [number]" + limit: "&7 Block limit: &e [number]" + previous: + name: "&f&l Page précédente" + description: "&7 Passer à la page [number]" + next: + name: "&f&l Page suivante" + description: "&7 Passer à la page [number]" + search: + name: "&f&l Rechercher" + description: "&7 Recherche une valeur \n&7 spécifique." + search: "&b Valeur : [value]" + tips: + click-to-view: "&e Cliquez &7 pour afficher." + click-to-previous: "&e Cliquez &7 pour afficher la page précédente." + click-to-next: "&e Cliquez &7 pour afficher la page suivante." + click-to-select: "&e Cliquez &7 pour sélectionner." + left-click-to-cycle-up: "&e Clic gauche &7 pour monter." + right-click-to-cycle-down: "&e Clic droit &7 pour descendre." + left-click-to-change: "&e Clic gauche &7 pour éditer." + right-click-to-clear: "&e Clic droit &7 pour effacer." + click-to-asc: "&e Cliquez &7 pour trier par ordre croissant." + click-to-desc: "&e Cliquez &7 pour trier par ordre décroissant." + click-to-warp: "&e Cliquer &7 to warp." + click-to-visit: "&e Cliquer &7 pour visiter." + right-click-to-visit: "&e Clic droit&7 pour visiter." + conversations: + prefix: "&l&6 [BentoBox]: &r" + no-data: "&c Niveau d'exécution pour voir le rapport de blocage." + cancel-string: annuler + exit-string: annuler, sortir, quitter + write-search: "&e Veuillez entrer une valeur de recherche. (Ecrivez 'cancel' pour + quitter)" + search-updated: "&a Valeur de recherche mise à jour." + cancelled: "&c Conversation annulée !" + no-value: "&c Cet item n'a aucune valeur." + unknown-item: "&c Le '[material]' n'existe pas dans le jeu." + value: "&7 La valeur de '[material]' est : &e[value]" + value-underwater: "&7 La valeur de '[material]' sous le niveau de la mer : &e[value]" + empty-hand: "&c Il n'y a pas de blocs dans votre main" meta: authors: '0': plagoutte From 51338d280d67cc1056e9e16cd5da8e7651a35035 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 26 Nov 2022 17:48:55 -0800 Subject: [PATCH 044/106] Update to Java 17 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index c36d67d..5290900 100644 --- a/pom.xml +++ b/pom.xml @@ -54,11 +54,11 @@ UTF-8 UTF-8 - 16 + 17 2.0.9 - 1.16.5-R0.1-SNAPSHOT + 1.19.2-R0.1-SNAPSHOT 1.20.0 1.12.0 @@ -71,7 +71,7 @@ -LOCAL - 2.9.1 + 2.10.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From 3988659dcc5e2d55919d8a377da05042c5dd4047 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 26 Nov 2022 17:52:31 -0800 Subject: [PATCH 045/106] Update Github workflow to Java 17 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9cf60c..c771fd5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,10 +14,10 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 16 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: 16 + java-version: 17 - name: Cache SonarCloud packages uses: actions/cache@v1 with: From f3ee8a381c698836642a3d63ab9c7a60c1ce9a59 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 26 Nov 2022 18:20:14 -0800 Subject: [PATCH 046/106] Adds %Level_[gamemode]_island_level_max% placeholder This records the lifetime maximum level the island has ever had. Addresses #271 --- src/main/java/world/bentobox/level/Level.java | 3 +++ .../world/bentobox/level/LevelsManager.java | 13 +++++++++++++ .../bentobox/level/objects/IslandLevels.java | 17 +++++++++++++++++ .../world/bentobox/level/LevelsManagerTest.java | 9 +++++++++ 4 files changed, 42 insertions(+) diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index f41830d..ee2b70c 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -228,6 +228,9 @@ private void registerPlaceholders(GameModeAddon gm) { getPlugin().getPlaceholdersManager().registerPlaceholder(this, gm.getDescription().getName().toLowerCase() + "_points_to_next_level", user -> getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId())); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_island_level_max", + user -> String.valueOf(getManager().getIslandMaxLevel(gm.getOverWorld(), user.getUniqueId()))); // Visited Island Level getPlugin().getPlaceholdersManager().registerPlaceholder(this, diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index a7fc4a7..08352de 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -231,6 +231,19 @@ public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) { return island == null ? 0L : getLevelsData(island).getLevel(); } + /** + * Get the maximum level ever given to this island + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return Max level of the player's island or zero if player is unknown or UUID 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(); + } + /** * Returns a formatted string of the target player's island level * @param world - world where the island is diff --git a/src/main/java/world/bentobox/level/objects/IslandLevels.java b/src/main/java/world/bentobox/level/objects/IslandLevels.java index 532b509..7dd0123 100644 --- a/src/main/java/world/bentobox/level/objects/IslandLevels.java +++ b/src/main/java/world/bentobox/level/objects/IslandLevels.java @@ -43,6 +43,11 @@ public class IslandLevels implements DataObject { */ @Expose private long pointsToNextLevel; + /** + * The maximum level this island has ever had + */ + @Expose + private long maxLevel; /** * Underwater count @@ -94,6 +99,10 @@ public long getLevel() { */ public void setLevel(long level) { this.level = level; + // Track maximum level + if (level > this.maxLevel) { + maxLevel = level; + } } /** @@ -124,6 +133,14 @@ public void setPointsToNextLevel(long pointsToNextLevel) { this.pointsToNextLevel = pointsToNextLevel; } + /** + * Get the maximum level ever set using {@link #setLevel(long)} + * @return the maxLevel + */ + public long getMaxLevel() { + return maxLevel; + } + /** * @return the uwCount */ diff --git a/src/test/java/world/bentobox/level/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java index 94ead55..f6b5d54 100644 --- a/src/test/java/world/bentobox/level/LevelsManagerTest.java +++ b/src/test/java/world/bentobox/level/LevelsManagerTest.java @@ -270,6 +270,15 @@ public void testCalculateLevel() { //Map tt = lm.getTopTen(world, 10); //assertEquals(1, tt.size()); //assertTrue(tt.get(uuid) == 10000); + assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); + + results.setLevel(5000); + lm.calculateLevel(uuid, island); + // Complete the pipelined completable future + cf.complete(results); + assertEquals(5000L, lm.getLevelsData(island).getLevel()); + // Still should be 10000 + assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); } From 93869cb34aba3eee0be1553bc97da74818bacb40 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 26 Nov 2022 18:29:03 -0800 Subject: [PATCH 047/106] Only shows Members or higher in the top members placeholder Fixes #267 --- src/main/java/world/bentobox/level/Level.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index ee2b70c..66303de 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -22,6 +22,7 @@ import world.bentobox.bentobox.api.configuration.Config; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; import world.bentobox.level.calculators.Pipeliner; import world.bentobox.level.commands.AdminLevelCommand; @@ -286,6 +287,7 @@ String getRankMembers(World world, int rank) { if (island != null) { // Sort members by rank return island.getMembers().entrySet().stream() + .filter(e -> e.getValue() >= RanksManager.MEMBER_RANK) .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) .map(Map.Entry::getKey) .map(getPlayers()::getName) From ac6bead52e199f32486db1e91aeea63fdbcc5f48 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 16 Jan 2023 14:16:14 -0800 Subject: [PATCH 048/106] Add natural log to level-calc formula parsing Relates to #274 --- .../bentobox/level/calculators/IslandLevelCalculator.java | 3 +++ src/main/java/world/bentobox/level/config/ConfigSettings.java | 2 +- src/main/resources/config.yml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 9c9fb43..09f6218 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -135,6 +135,9 @@ void nextChar() { case "tan": x = Math.tan(Math.toRadians(x)); break; + case "log": + x = Math.log(x); + break; default: throw new RuntimeException("Unknown function: " + func); } diff --git a/src/main/java/world/bentobox/level/config/ConfigSettings.java b/src/main/java/world/bentobox/level/config/ConfigSettings.java index 67f7b91..3e8099e 100644 --- a/src/main/java/world/bentobox/level/config/ConfigSettings.java +++ b/src/main/java/world/bentobox/level/config/ConfigSettings.java @@ -90,7 +90,7 @@ public class ConfigSettings implements ConfigObject { @ConfigComment("Island level calculation formula") @ConfigComment("blocks - the sum total of all block values, less any death penalty") @ConfigComment("level_cost - in a linear equation, the value of one level") - @ConfigComment("This formula can include +,=,*,/,sqrt,^,sin,cos,tan. Result will always be rounded to a long integer") + @ConfigComment("This formula can include +,=,*,/,sqrt,^,sin,cos,tan,log (natural log). Result will always be rounded to a long integer") @ConfigComment("for example, an alternative non-linear option could be: 3 * sqrt(blocks / level_cost)") @ConfigEntry(path = "level-calc") private String levelCalc = "blocks / level_cost"; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 9b7c685..7f2ed95 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -53,7 +53,7 @@ levelcost: 100 # Island level calculation formula # blocks - the sum total of all block values, less any death penalty # level_cost - in a linear equation, the value of one level -# This formula can include +,=,*,/,sqrt,^,sin,cos,tan. Result will always be rounded to a long integer +# This formula can include +,=,*,/,sqrt,^,sin,cos,tan,log (natural log). Result will always be rounded to a long integer # for example, an alternative non-linear option could be: 3 * sqrt(blocks / level_cost) level-calc: blocks / level_cost # From fba73948c62cf705240aaab42a10f309f1c30a67 Mon Sep 17 00:00:00 2001 From: evlad Date: Tue, 17 Jan 2023 00:00:40 +0100 Subject: [PATCH 049/106] feat: add island total points + placeholder (#264) * feat: add island total points + placeholder * Update IslandLevels.java --- src/main/java/world/bentobox/level/Level.java | 8 ++++++++ .../world/bentobox/level/LevelsManager.java | 2 ++ .../calculators/IslandLevelCalculator.java | 3 ++- .../bentobox/level/calculators/Results.java | 20 +++++++++++++++++-- .../level/commands/IslandLevelCommand.java | 2 +- .../bentobox/level/objects/IslandLevels.java | 20 +++++++++++++++++++ 6 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 66303de..06bd690 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -36,6 +36,7 @@ import world.bentobox.level.config.ConfigSettings; import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.JoinLeaveListener; +import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.listeners.MigrationListener; import world.bentobox.level.objects.LevelsData; import world.bentobox.level.objects.TopTenData; @@ -226,6 +227,13 @@ private void registerPlaceholders(GameModeAddon gm) { getPlugin().getPlaceholdersManager().registerPlaceholder(this, gm.getDescription().getName().toLowerCase() + "_island_level_raw", user -> String.valueOf(getManager().getIslandLevel(gm.getOverWorld(), user.getUniqueId()))); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_island_total_points", + user -> { + IslandLevels data = getManager().getLevelsData(this.getIslands().getIsland(gm.getOverWorld(), user)); + return data.getTotalPoints()+""; + }); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, gm.getDescription().getName().toLowerCase() + "_points_to_next_level", user -> getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId())); diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index 08352de..4c09796 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -181,6 +181,7 @@ private boolean fireIslandLevelCalcEvent(UUID targetPlayer, Island island, Resul 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)); } @@ -432,6 +433,7 @@ private void setIslandResults(World world, @NonNull UUID owner, Results r) { 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 diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 09f6218..b7d7787 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -52,7 +52,7 @@ public class IslandLevelCalculator { private static final String LINE_BREAK = "=================================="; - public static final long MAX_AMOUNT = 10000; + 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, @@ -601,6 +601,7 @@ public void tidyUp() { } long blockAndDeathPoints = this.results.rawBlockCount.get(); + this.results.totalPoints.set(blockAndDeathPoints); if (this.addon.getSettings().getDeathPenalty() > 0) { diff --git a/src/main/java/world/bentobox/level/calculators/Results.java b/src/main/java/world/bentobox/level/calculators/Results.java index ec57548..c2bf739 100644 --- a/src/main/java/world/bentobox/level/calculators/Results.java +++ b/src/main/java/world/bentobox/level/calculators/Results.java @@ -36,6 +36,7 @@ public enum Result { AtomicInteger deathHandicap = new AtomicInteger(0); AtomicLong pointsToNextLevel = new AtomicLong(0); AtomicLong initialLevel = new AtomicLong(0); + AtomicLong totalPoints = new AtomicLong(0); final Result state; public Results(Result state) { @@ -93,6 +94,21 @@ public void setPointsToNextLevel(long points) { pointsToNextLevel.set(points); } + /** + * @return the totalPoints + */ + public long getTotalPoints() { + return totalPoints.get(); + } + + /** + * Set the total points + * @param points + */ + public void setTotalPoints(long points) { + totalPoints.set(points); + } + public long getInitialLevel() { return initialLevel.get(); } @@ -109,7 +125,7 @@ public String toString() { return "Results [report=" + report + ", mdCount=" + mdCount + ", uwCount=" + uwCount + ", ncCount=" + ncCount + ", ofCount=" + ofCount + ", rawBlockCount=" + rawBlockCount + ", underWaterBlockCount=" + underWaterBlockCount + ", level=" + level + ", deathHandicap=" + deathHandicap - + ", pointsToNextLevel=" + pointsToNextLevel + ", initialLevel=" + initialLevel + "]"; + + ", pointsToNextLevel=" + pointsToNextLevel + ", totalPoints=" + totalPoints + ", initialLevel=" + initialLevel + "]"; } /** * @return the mdCount @@ -130,4 +146,4 @@ public Result getState() { return state; } -} \ No newline at end of file +} diff --git a/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java b/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java index d2b1a98..3d1420e 100644 --- a/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java +++ b/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java @@ -110,7 +110,7 @@ private void showResult(User user, UUID playerUUID, Island island, long oldLevel user.sendMessage("island.level.deaths", "[number]", String.valueOf(results.getDeathHandicap())); } // Send player how many points are required to reach next island level - if (results.getPointsToNextLevel() >= 0 && results.getPointsToNextLevel() < 10000) { + if (results.getPointsToNextLevel() >= 0) { user.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(results.getPointsToNextLevel())); } // Tell other team members diff --git a/src/main/java/world/bentobox/level/objects/IslandLevels.java b/src/main/java/world/bentobox/level/objects/IslandLevels.java index 7dd0123..10cbdc4 100644 --- a/src/main/java/world/bentobox/level/objects/IslandLevels.java +++ b/src/main/java/world/bentobox/level/objects/IslandLevels.java @@ -49,6 +49,12 @@ public class IslandLevels implements DataObject { @Expose private long maxLevel; + /** + * Total points + */ + @Expose + private long totalPoints; + /** * Underwater count */ @@ -133,6 +139,20 @@ public void setPointsToNextLevel(long pointsToNextLevel) { this.pointsToNextLevel = pointsToNextLevel; } + /** + * @return the pointsToNextLevel + */ + public long getTotalPoints() { + return totalPoints; + } + + /** + * @param totalPoints the totalPoints to set + */ + public void setTotalPoints(long totalPoints) { + this.totalPoints = totalPoints; + } + /** * Get the maximum level ever set using {@link #setLevel(long)} * @return the maxLevel From dc9d460e1ed695bec4f60130419482ae233690d0 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 16 Jan 2023 15:07:00 -0800 Subject: [PATCH 050/106] Fix JavaDoc --- src/main/java/world/bentobox/level/objects/IslandLevels.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/level/objects/IslandLevels.java b/src/main/java/world/bentobox/level/objects/IslandLevels.java index 10cbdc4..816376d 100644 --- a/src/main/java/world/bentobox/level/objects/IslandLevels.java +++ b/src/main/java/world/bentobox/level/objects/IslandLevels.java @@ -140,7 +140,7 @@ public void setPointsToNextLevel(long pointsToNextLevel) { } /** - * @return the pointsToNextLevel + * @return the totalPoints */ public long getTotalPoints() { return totalPoints; From f1db2a92848e483897939b8f7dd40829041b9602 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Mon, 23 Jan 2023 20:44:30 +0200 Subject: [PATCH 051/106] Translate zh-CN.yml via GitLocalize (#276) Co-authored-by: dawnTak --- src/main/resources/locales/zh-CN.yml | 179 +++++++++++++++++++++------ 1 file changed, 144 insertions(+), 35 deletions(-) diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index f7e4348..c268f5b 100755 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -2,53 +2,162 @@ admin: level: parameters: "" - description: 计算某玩家的岛屿等级 + description: 计算指定玩家的岛屿等级 sethandicap: - parameters: "<玩家> <让分>" - description: 设置孤岛障碍,通常是首发岛的水平 - changed: "&a初始离岛差由[number]更改为[new_number]。" - invalid-level: "&c差点。使用整数。" + parameters: " " + description: 设置偏差值,通常用于调整新建的初始岛屿等级为零。实际岛屿等级 - = 计算的岛屿等级 + changed: "&a 岛屿的偏差值从 [number] 更改为 [new_number]" + invalid-level: "&c 偏差值无效,请使用整数" levelstatus: - description: 显示要扫描的队列中有多少岛 - islands-in-queue: "&a列队中的列队:[人数]" + description: 显示等级计算队列中的岛屿 + islands-in-queue: "&a 列队中的岛屿:[number]" top: description: 显示前十名 - unknown-world: "&c未知世界!" + unknown-world: "&c 未知的世界!" display: "&f[rank]. &a[name] &7- &b[level]" remove: - description: 将玩家移出前十 + description: 将玩家移出前十名 parameters: "" island: level: parameters: "[player]" - description: 计算你或玩家 [player] 的岛屿等级 - calculating: "&a计算等级中..." - estimated-wait: "&a 预计等待时间: [number] 秒" - in-queue: "&a您是队列中的数字[number]" - island-level-is: "&a岛屿等级为 &b[level]" - required-points-to-next-level: "&a还需 [points] 才能升到下一级" + description: 计算你或指定玩家 [player] 的岛屿等级 + calculating: "&a 等级计算中..." + estimated-wait: "&a 预计等待时间:[number] 秒" + in-queue: "&a 你处于队列中第 [number] 个" + island-level-is: "&a 岛屿等级为 &b[level]" + required-points-to-next-level: "&a 还需 [points] 点数才能到达下一级" deaths: "&c([number] 次死亡)" - cooldown: "&c再等 &b[time] &c秒才能再次使用" - in-progress: "&6岛级计算正在进行中..." - time-out: "&c等级计算花了太长时间。请稍后再试。" + cooldown: "&c 还需等待 &b[time] &c秒才能再次使用该指令" + in-progress: "&6 岛级等级正在计算中..." + time-out: "&c 等级计算超时。请稍后再试" top: description: 显示前十名 - gui-title: "&a前十" + gui-title: "&a 前十" gui-heading: "&6[name]: &B[rank]" - island-level: "&B等级 [level]" - warp-to: "&A正传送到 [name] 的岛屿" + island-level: "&b 等级 [level]" + warp-to: "&a 正在传送到 [name] 的岛屿" level-details: - above-sea-level-blocks: 海拔以上的街区 - spawners: 产卵者 - underwater-blocks: 水下积木 - all-blocks: 所有块 - no-island: "&c没有岛!" - names-island: "[名字]的小岛" - syntax: "[名称] x [数字]" - hint: "&c运行级别以查看阻止报告" - value: - description: 查看某方块的价值 - success: "&7本方块的价值: &e[value]" - success-underwater: "&7本方块的水下价值: &e[value]" - empty-hand: "&c你手里没有方块" - no-value: "&c这个东西一文不值." + above-sea-level-blocks: 海平面以上的方块 + spawners: 刷怪笼 + underwater-blocks: 水下的方块 + all-blocks: 所有方块 + no-island: "&c 没有岛屿!" + names-island: "[name] 的岛屿" + syntax: "[name] x [number]" + hint: "&c 运行level指令查看方块报告" +level: + commands: + value: + parameters: "[hand|]" + description: 显示方块的价值。在末尾添加 'hand' 可显示手中方块的价值 + gui: + titles: + top: "&0&l 岛屿排行榜" + detail-panel: "&0&l [name] 的岛屿" + value-panel: "&0&l 方块价值" + buttons: + island: + empty: "&f&l 第 [name] 名" + name: "&f&l [name]" + description: |- + [owner] + [members] + [place] + [level] + owners-island: "[player] 的岛屿" + owner: "&7&l 岛主:&r&b [player]" + members-title: "&7&l 成员:" + member: "&b - [player]" + unknown: 未知 + place: "&7第 &7&o[number] &r&7名" + level: "&7 等级: &o [number]" + material: + name: "&f&l [number] x [material]" + description: |- + [description] + [count] + [value] + [calculated] + [limit] + [id] + id: "&7 方块ID:&e [id]" + value: "&7 方块价值:&e [number]" + limit: "&7 方块限制:&e [number]" + count: "&7 方块数量:&e [number]" + calculated: "&7 计算值:&e [number]" + all_blocks: + name: "&f&l 所有方块" + description: "&7 显示岛屿上所有的方块" + above_sea_level: + name: "&f&l 方块在海平面以上的价值" + description: |- + &7 只显示所有 + &7 海平面以上的方块 + underwater: + name: "&f&l 海平面以下的方块" + description: |- + &7 只显示所有 + &7 海平面以下的方块 + spawner: + name: "&f&l 刷怪笼" + description: "&7 只显示刷怪笼" + filters: + name: + name: "&f&l 按名称排序" + description: "&7 通过名称排序所有的方块" + value: + name: "&f&l 按价值排序" + description: "&7 通过价值排序所有的方块" + count: + name: "&f&l 按数量排序" + description: "&7 通过数量排序所有方块" + value: + name: "&f&l [material]" + description: |- + [description] + [value] + [underwater] + [limit] + [id] + id: "&7 方块ID:&e [id]" + value: "&7 方块价值:&e [number]" + underwater: "&7 方块海平面下价值:&e [number]" + limit: "&7 方块限制:&e [number]" + previous: + name: "&f&l 上一页" + description: "&7 切换到第 [number] 页" + next: + name: "&f&l 下一页" + description: "&7 切换到第 [number] 页" + search: + name: "&f&l 搜索" + description: "&7 搜索特定的内容" + search: "&b 搜索值:[value]" + tips: + click-to-view: "&e 点击 &7 查看" + click-to-previous: "&e 点击 &7 查看上一页" + click-to-next: "&e 点击 &7 查看下一页" + click-to-select: "&e 点击 &7 选择" + left-click-to-cycle-up: "&e 左键点击 &7 向上循环" + right-click-to-cycle-down: "&e 右键点击 &7 向下循环" + left-click-to-change: "&e 左键点击 &7 编辑" + right-click-to-clear: "&e 右键点击 &7 清除" + click-to-asc: "&e 点击 &7 以升序排序" + click-to-desc: "&e 点击 &7 以降序排序" + click-to-warp: "&e 点击 &7 去岛屿传送点" + click-to-visit: "&e 点击 &7 参观" + right-click-to-visit: "&e 右键点击 &7 查看" + conversations: + prefix: "&l&6 [BentoBox]: &r" + no-data: "&c 运行level指令查看方块报告" + cancel-string: cancel + exit-string: cancel, exit, quit + write-search: "&e 请输入要搜索的值. (输入 'cancel' 退出)" + search-updated: "&a 搜索值已更新" + cancelled: "&c 对话已取消!" + no-value: "&c 这件物品一文不值" + unknown-item: "&c 物品 '[material]' 在游戏中不存在" + value: "&7 物品 '[material]' 的价值:&e[value]" + value-underwater: "&7 物品 '[material]' 在海平面以下的价值:&e[value]" + empty-hand: "&c 你的手中没有拿着方块" From 29b148052ae312868e33cbe962ac02886262c528 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 14:46:02 +0200 Subject: [PATCH 052/106] Translate nl.yml via GitLocalize (#277) Co-authored-by: DevSolaris --- src/main/resources/locales/nl.yml | 165 ++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/main/resources/locales/nl.yml diff --git a/src/main/resources/locales/nl.yml b/src/main/resources/locales/nl.yml new file mode 100644 index 0000000..bd3fb47 --- /dev/null +++ b/src/main/resources/locales/nl.yml @@ -0,0 +1,165 @@ +--- +admin: + level: + parameters: "" + description: bereken het eiland level voor een speler + sethandicap: + parameters: " " + description: stel handicap in voor het eiland, normaal gesproken het level van + het starter eiland. + changed: "&a Initiële handicap is veranderd van [number] naar [new_number]." + invalid-level: "&c Ongeldige handicap. Gebruik een getal." + levelstatus: + description: laat zien hoeveel eilanden er in de wachtrij staan voor het scannen + islands-in-queue: "&a Aantal eilanden in de wachtrij: [number]" + top: + description: Laat de top tien zien + unknown-world: "&c Ongeldige wereld!" + display: "&f[rank]. &a[name] &7- &b[level]" + remove: + description: verwijder speler van de top tien + parameters: "" +island: + level: + parameters: "[speler]" + description: bereken het eiland level voor [player] + calculating: "&a Level aan het berekenen..." + estimated-wait: "&a Verwachtte wachttijd: [number] seconde" + in-queue: "&a Jij staat op plek [number] in de wachtrij" + island-level-is: "&a Eiland level is &b[level]" + required-points-to-next-level: "&a [points] punten nodig voor het volgende level" + deaths: "&c([number] doodgegaan)" + cooldown: "&c Je moet nog &b[time] &c seconden wachten tot je dit weer kan doen." + in-progress: "&6 Eiland level wordt berekend..." + time-out: "&c De level berekening duurde te lang. Probeer het later opnieuw." + top: + description: Toon de Top tien + gui-title: "&a Top tien" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Level [level]" + warp-to: "&A Teleporteren naar [name]'s eiland" + level-details: + above-sea-level-blocks: 'Blokken boven zeeniveau ' + spawners: Monsterkooien + underwater-blocks: Blokken onder zeeniveau + all-blocks: Alle blokken + no-island: "&c Geen eiland!" + names-island: "[name]'s eiland" + syntax: "[name] x [number]" + hint: "&c Gebruik level om het blokkenrapport te zien" +level: + commands: + value: + parameters: "[hand|]" + description: toont de waarde van blokken. Voeg 'hand' toe aan het einde om de + waarde te laten zien van het item in je hand. + gui: + titles: + top: "&0&l Top eilanden" + detail-panel: "&0&l [name]'s eiland" + value-panel: "&0&l Blok waardes" + buttons: + island: + empty: "&f&l [name]. plaats" + name: "&f&l [name]" + description: |- + [owner] + [members] + [place] + [level] + owners-island: "[player]'s Eiland" + owner: "&7&l Eigenaar: &r&b [player]" + members-title: "&7&l Leden:" + member: "&b - [player]" + unknown: onbekend + place: "&7&o [number]. &r&7 plaats" + level: "&7 Level: &o [number]" + material: + name: "&f&l [number] x [material]" + description: |- + [description] + [count] + [value] + [calculated] + [limit] + [id] + id: "&7 Blok id: &e [id]" + value: "&7 Block waarde: &e [number]" + limit: "&7 Block limiet: &e [number]" + count: "&7 Aantal blokken: &e [number]" + calculated: "&7 Berekende waarde: &e [number]" + all_blocks: + name: "&f&l Alle Blokken" + description: "&7 Toon alle blokken \n&7 op het eiland." + above_sea_level: + name: "&f&l Blokken boven zeeniveau" + description: |- + &7 Toon alleen blokken + &7 die boven zeeniveau zijn + underwater: + name: "&f&l Blokken onder zeeniveau" + description: |- + &7 Toon alleen blokken + &7 die onder zeeniveau zijn + spawner: + name: "&f&l Monsterkooien" + description: "&7 Toon alleen monsterkooien." + filters: + name: + name: "&f&l Sorteer aan de hand van naam" + description: "&7 Sorteer alle blokken aan de hand van naam." + value: + name: "&f&l Sorteer aan de hand van waarde" + description: "&7 Sorteer alle blokken aan de hand van waarde." + count: + name: "&f&l Sorteer aan de hand van aantal" + description: "&7 Sorteer alle blokken aan de hand van aantal." + value: + name: "&f&l [material]" + description: |- + [description] + [value] + [underwater] + [limit] + [id] + id: "&7 Blok id: &e [id]" + value: "&7 Block waarrde: &e [number]" + underwater: "&7 Onder zeeniveau: &e [number]" + limit: "&7 Blok limiet: &e [number]" + previous: + name: "&f&l Vorige pagina" + description: "&7 Ga naar pagina [number]" + next: + name: "&f&l Volgende pagina" + description: "&7 Ga naar pagina [number]" + search: + name: "&f&l Zoek" + description: "&7 Zoek voor een \n&7 specifieke waarde." + search: "&b Waarde: [value]" + tips: + click-to-view: "&e Klik &7 om te zien." + click-to-previous: "&e Klik &7 om de vorige pagina te zien." + click-to-next: "&e Klik &7 om de volgende pagina te zien." + click-to-select: "&e Klik &7 om te selecteren." + left-click-to-cycle-up: "&e Linker Klik &7 om door te lopen." + right-click-to-cycle-down: "&e Rechter Klik &7 om terug door te lopen." + left-click-to-change: "&e Linker Klik &7 om bij te werken." + right-click-to-clear: "&e Linker Klik &7 om te verwijderen." + click-to-asc: "&e Klik &7 om te toenemend te sorteren." + click-to-desc: "&e Klik &7 om te afnemenend te sorteren." + click-to-warp: "&e Klik &7 om te teleporteren." + click-to-visit: "&e Klik &7 om te bezoeken." + right-click-to-visit: "&e Rechter Klik &7 om te bezoeken." + conversations: + prefix: "&l&6 [BentoBox]: &r" + no-data: "&c Gebruik level om het blokkenrapport te zien." + cancel-string: stop + exit-string: stop + write-search: "&e Schrijf een zoekopdracht. (Schrijf 'stop' om te zoeken)" + search-updated: "&a Zoekopdracht bijgewerkt." + cancelled: "&c Conversatie gestopt!" + no-value: "&c Dit item heeft geen waarde." + unknown-item: "&c '[material]' bestaat niet in het spel." + value: "&7 De waarde van '[material]' is: &e[value]" + value-underwater: "&7 The waarde van '[material]' onder zeeniveau: &e[value]" + empty-hand: "&c Je hebt geen blok vast" From f469e377027a614de3f3628396bde161b94df127 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 9 Feb 2023 15:15:15 -0800 Subject: [PATCH 053/106] Add ${argLine} to get jacoco coverage --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5290900..6173780 100644 --- a/pom.xml +++ b/pom.xml @@ -284,6 +284,7 @@ 3.0.0-M5 + ${argLine} --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED @@ -423,4 +424,4 @@ - \ No newline at end of file + From 42249a8fc951844cf80b7a1f1e8fdd14f6c1171f Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 9 Feb 2023 17:05:27 -0800 Subject: [PATCH 054/106] Updated Jacoco POM --- pom.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 6173780..abde9c7 100644 --- a/pom.xml +++ b/pom.xml @@ -397,7 +397,7 @@ org.jacoco jacoco-maven-plugin - 0.8.3 + 0.8.7 true @@ -408,16 +408,21 @@ - pre-unit-test + prepare-agent prepare-agent - post-unit-test + report report + + + XML + + From a493c12f6eb8a6ffc9b8214ecfc915fb058cce64 Mon Sep 17 00:00:00 2001 From: DevSolaris <105156235+DevSolaris@users.noreply.github.com> Date: Fri, 10 Feb 2023 04:31:35 +0100 Subject: [PATCH 055/106] Add shulker to in chest count (#275) --- .../calculators/IslandLevelCalculator.java | 18 +++++++++++------- .../bentobox/level/config/ConfigSettings.java | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index b7d7787..35743c2 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -24,13 +24,11 @@ import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.World.Environment; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Container; -import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.*; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Slab; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; import org.bukkit.scheduler.BukkitTask; import com.bgsoftware.wildstacker.api.WildStackerAPI; @@ -445,10 +443,16 @@ private void scanChests(Chunk chunk) { } private void countItemStack(ItemStack i) { - if (i != null && i.getType().isBlock()) { - for (int c = 0; c < i.getAmount(); c++) { - checkBlock(i.getType(), false); + 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); } + + checkBlock(i.getType(), false); } } diff --git a/src/main/java/world/bentobox/level/config/ConfigSettings.java b/src/main/java/world/bentobox/level/config/ConfigSettings.java index 3e8099e..7be3364 100644 --- a/src/main/java/world/bentobox/level/config/ConfigSettings.java +++ b/src/main/java/world/bentobox/level/config/ConfigSettings.java @@ -119,6 +119,12 @@ public class ConfigSettings implements ConfigObject { @ConfigComment("Shows large level values rounded down, e.g., 10,345 -> 10k") @ConfigEntry(path = "shorthand") private boolean shorthand = false; + @ConfigComment("") + @ConfigComment("Include Shulker Box content in chests in level calculations.") + @ConfigComment("Will count blocks in Shulker Boxes inside of chests.") + @ConfigComment("NOTE: include-chests needs to be enabled for this to work!.") + @ConfigEntry(path = "include-shulkers-in-chest") + private boolean includeShulkersInChest = false; /** @@ -385,4 +391,17 @@ public void setLogReportToConsole(boolean logReportToConsole) { this.logReportToConsole = logReportToConsole; } + /** + * @return includeShulkersInChest + */ + public boolean isIncludeShulkersInChest() { + return includeShulkersInChest; + } + + /** + * @param includeShulkersInChest the includeChests to set + */ + public void setIncludeShulkersInChest(boolean includeShulkersInChest) { + this.includeShulkersInChest = includeShulkersInChest; + } } From 0cdb15403be6d786a342c8fdf7132294f668cad7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 9 Feb 2023 19:32:13 -0800 Subject: [PATCH 056/106] Sonar Cloud code smell clean up (#278) --- src/main/java/world/bentobox/level/Level.java | 22 +- .../world/bentobox/level/LevelsManager.java | 9 +- .../calculators/IslandLevelCalculator.java | 55 ++-- .../level/commands/AdminTopRemoveCommand.java | 3 +- .../level/commands/IslandValueCommand.java | 25 +- .../bentobox/level/panels/DetailsPanel.java | 292 +++++++++--------- .../bentobox/level/panels/TopLevelPanel.java | 243 ++++++++------- .../bentobox/level/panels/ValuePanel.java | 284 ++++++++--------- .../level/util/ConversationUtils.java | 45 +-- .../java/world/bentobox/level/util/Utils.java | 22 +- .../bentobox/level/LevelsManagerTest.java | 6 +- .../admin/AdminTopRemoveCommandTest.java | 6 +- 12 files changed, 516 insertions(+), 496 deletions(-) diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 06bd690..2cf79c3 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -36,8 +36,8 @@ import world.bentobox.level.config.ConfigSettings; import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.JoinLeaveListener; -import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.listeners.MigrationListener; +import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.objects.LevelsData; import world.bentobox.level.objects.TopTenData; import world.bentobox.level.requests.LevelRequestHandler; @@ -176,20 +176,14 @@ private void hookExtensions() { this.visitHook = (VisitAddon) addon; this.log("Level Addon hooked into Visit addon."); - }, () -> - { - this.visitHook = null; - }); + }, () -> this.visitHook = null); // Try to find Warps addon and if it does not exist, display a warning this.getAddonByName("Warps").ifPresentOrElse(addon -> { this.warpHook = (Warp) addon; this.log("Level Addon hooked into Warps addon."); - }, () -> - { - this.warpHook = null; - }); + }, () -> this.warpHook = null); } @@ -230,9 +224,9 @@ private void registerPlaceholders(GameModeAddon gm) { getPlugin().getPlaceholdersManager().registerPlaceholder(this, gm.getDescription().getName().toLowerCase() + "_island_total_points", user -> { - IslandLevels data = getManager().getLevelsData(this.getIslands().getIsland(gm.getOverWorld(), user)); - return data.getTotalPoints()+""; - }); + IslandLevels data = getManager().getLevelsData(this.getIslands().getIsland(gm.getOverWorld(), user)); + return data.getTotalPoints()+""; + }); getPlugin().getPlaceholdersManager().registerPlaceholder(this, gm.getDescription().getName().toLowerCase() + "_points_to_next_level", @@ -478,7 +472,7 @@ public long getInitialIslandLevel(@NonNull Island island) { * @param playerUUID - the target island member's UUID * @deprecated Do not use this anymore. Use getManager().calculateLevel(playerUUID, island) */ - @Deprecated + @Deprecated(since="2.3.0", forRemoval=true) public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) { Island island = getIslands().getIsland(world, playerUUID); if (island != null) getManager().calculateLevel(playerUUID, island); @@ -490,7 +484,7 @@ public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID * @return LevelsData object or null if not found. Only island levels are set! * @deprecated Do not use this anymore. Use {@link #getIslandLevel(World, UUID)} */ - @Deprecated + @Deprecated(since="2.3.0", forRemoval=true) public LevelsData getLevelsData(UUID targetPlayer) { LevelsData ld = new LevelsData(targetPlayer); getPlugin().getAddonsManager().getGameModeAddons().stream() diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index 4c09796..e40ba49 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -152,8 +152,6 @@ public CompletableFuture calculateLevel(UUID targetPlayer, Island islan 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)) { - System.out.println("results are null or event canceled"); - result.complete(null); } // Save result @@ -337,7 +335,7 @@ public int getRank(@NonNull World world, UUID uuid) { .filter(e -> addon.getIslands().isOwner(world, e.getKey())) .filter(l -> l.getValue() > 0) .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); - return stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).collect(Collectors.toList()).size() + 1; + return (int) (stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).count() + 1); } /** @@ -363,10 +361,7 @@ public void loadTopTens() { addon.getIslands().getIslandById(il.getUniqueId()).ifPresent(i -> this.addToTopTen(i, il.getLevel())); } }); - topTenLists.keySet().forEach(w -> { - addon.log("Generated rankings for " + w.getName()); - }); - + topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName())); }); } diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 35743c2..9d36dc8 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -1,11 +1,11 @@ package world.bentobox.level.calculators; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -65,9 +65,10 @@ public class IslandLevelCalculator { * @param str - equation to evaluate * @return value of equation */ - private static double eval(final String str) { + private static double eval(final String str) throws IOException { return new Object() { - int pos = -1, ch; + int pos = -1; + int ch; boolean eat(int charToEat) { while (ch == ' ') nextChar(); @@ -82,10 +83,10 @@ void nextChar() { ch = (++pos < str.length()) ? str.charAt(pos) : -1; } - double parse() { + double parse() throws IOException { nextChar(); double x = parseExpression(); - if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch); + if (pos < str.length()) throw new IOException("Unexpected: " + (char)ch); return x; } @@ -95,7 +96,7 @@ void nextChar() { // factor = `+` factor | `-` factor | `(` expression `)` // | number | functionName factor | factor `^` factor - double parseExpression() { + double parseExpression() throws IOException { double x = parseTerm(); for (;;) { if (eat('+')) x += parseTerm(); // addition @@ -104,7 +105,7 @@ void nextChar() { } } - double parseFactor() { + double parseFactor() throws IOException { if (eat('+')) return parseFactor(); // unary plus if (eat('-')) return -parseFactor(); // unary minus @@ -137,10 +138,10 @@ void nextChar() { x = Math.log(x); break; default: - throw new RuntimeException("Unknown function: " + func); + throw new IOException("Unknown function: " + func); } } else { - throw new RuntimeException("Unexpected: " + (char)ch); + throw new IOException("Unexpected: " + (char)ch); } if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation @@ -148,7 +149,7 @@ void nextChar() { return x; } - double parseTerm() { + double parseTerm() throws IOException { double x = parseFactor(); for (;;) { if (eat('*')) x *= parseFactor(); // multiplication @@ -161,7 +162,7 @@ void nextChar() { private final Level addon; private final Queue> chunksToCheck; private final Island island; - private final HashMap limitCount; + private final Map limitCount; private final CompletableFuture r; @@ -190,7 +191,7 @@ public IslandLevelCalculator(Level addon, Island island, CompletableFuture(addon.getBlockConfig().getBlockLimits()); + this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits()); // Get the initial island level results.initialLevel.set(addon.getInitialIslandLevel(island)); // Set up the worlds @@ -221,7 +222,15 @@ public IslandLevelCalculator(Level addon, Island island, CompletableFuture aChest = AdvancedChestsAPI.getChestManager().getAdvancedChest(bs.getLocation()); if (aChest != null && aChest.getChestType().getName().equals("NORMAL")) { @@ -437,7 +446,7 @@ private void scanChests(Chunk chunk) { } } // Regular chest - ((Container)bs).getSnapshotInventory().forEach(this::countItemStack); + container.getSnapshotInventory().forEach(this::countItemStack); } } } @@ -513,7 +522,7 @@ private void scanAsync(ChunkPair cp) { } // 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, x + cp.chunkSnapshot.getX() * 16,y,z + cp.chunkSnapshot.getZ() * 16)); + stackedBlocks.add(new Location(cp.world, (double)x + cp.chunkSnapshot.getX() * 16, y, (double)z + cp.chunkSnapshot.getZ() * 16)); } // Scan chests if (addon.getSettings().isIncludeChests() && CHESTS.contains(blockData.getMaterial())) { @@ -564,21 +573,21 @@ public CompletableFuture scanNextChunk() { } private Collection sortedReport(int total, Multiset materialCount) { - Collection r = new ArrayList<>(); + Collection result = new ArrayList<>(); Iterable> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount).entrySet(); for (Entry en : entriesSortedByCount) { Material type = en.getElement(); int value = getValue(type); - r.add(type.toString() + ":" + result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = " + (value * en.getCount())); total += (value * en.getCount()); } - r.add("Subtotal = " + total); - r.add(LINE_BREAK); - return r; + result.add("Subtotal = " + total); + result.add(LINE_BREAK); + return result; } @@ -638,7 +647,7 @@ boolean isNotZeroIsland() { public void scanIsland(Pipeliner pipeliner) { // Scan the next chunk - scanNextChunk().thenAccept(r -> { + scanNextChunk().thenAccept(result -> { if (!Bukkit.isPrimaryThread()) { addon.getPlugin().logError("scanChunk not on Primary Thread!"); } @@ -653,7 +662,7 @@ public void scanIsland(Pipeliner pipeliner) { } return; } - if (Boolean.TRUE.equals(r) && !pipeliner.getTask().isCancelled()) { + if (Boolean.TRUE.equals(result) && !pipeliner.getTask().isCancelled()) { // scanNextChunk returns true if there are more chunks to scan scanIsland(pipeliner); } else { diff --git a/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java b/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java index 4e1c29a..b54ca3e 100644 --- a/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java +++ b/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java @@ -2,7 +2,6 @@ import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.localization.TextVariables; @@ -60,6 +59,6 @@ public boolean execute(User user, String label, List args) { @Override public Optional> tabComplete(User user, String alias, List args) { return Optional.of(addon.getManager().getTopTen(getWorld(), Level.TEN).keySet().stream().map(addon.getPlayers()::getName) - .filter(n -> !n.isEmpty()).collect(Collectors.toList())); + .filter(n -> !n.isEmpty()).toList()); } } diff --git a/src/main/java/world/bentobox/level/commands/IslandValueCommand.java b/src/main/java/world/bentobox/level/commands/IslandValueCommand.java index 7e04a53..e6de8e4 100644 --- a/src/main/java/world/bentobox/level/commands/IslandValueCommand.java +++ b/src/main/java/world/bentobox/level/commands/IslandValueCommand.java @@ -19,6 +19,7 @@ public class IslandValueCommand extends CompositeCommand { + private static final String MATERIAL = "[material]"; private final Level addon; @@ -45,7 +46,7 @@ public boolean execute(User user, String label, List args) if (args.size() > 1) { this.showHelp(this, user); - return true; + return false; } if (args.isEmpty()) @@ -73,8 +74,8 @@ else if (args.get(0).equalsIgnoreCase("HAND")) if (material == null) { Utils.sendMessage(user, - user.getTranslation(this.getWorld(), "level.conversations.unknown-item", - "[material]", args.get(0))); + user.getTranslation(this.getWorld(), "level.conversations.unknown-item", + MATERIAL, args.get(0))); } else { @@ -98,24 +99,24 @@ private void printValue(User user, Material material) if (value != null) { Utils.sendMessage(user, - user.getTranslation(this.getWorld(), "level.conversations.value", - "[value]", String.valueOf(value), - "[material]", Utils.prettifyObject(material, user))); + user.getTranslation(this.getWorld(), "level.conversations.value", + "[value]", String.valueOf(value), + MATERIAL, Utils.prettifyObject(material, user))); double underWater = this.addon.getSettings().getUnderWaterMultiplier(); if (underWater > 1.0) { Utils.sendMessage(user, - user.getTranslation(this.getWorld(),"level.conversations.success-underwater", - "[value]", (underWater * value) + ""), - "[material]", Utils.prettifyObject(material, user)); + user.getTranslation(this.getWorld(),"level.conversations.success-underwater", + "[value]", (underWater * value) + ""), + MATERIAL, Utils.prettifyObject(material, user)); } } else { Utils.sendMessage(user, - user.getTranslation(this.getWorld(),"level.conversations.no-value")); + user.getTranslation(this.getWorld(),"level.conversations.no-value")); } } @@ -132,8 +133,8 @@ public Optional> tabComplete(User user, String alias, List } List options = new ArrayList<>(Arrays.stream(Material.values()). - filter(Material::isBlock). - map(Material::name).toList()); + filter(Material::isBlock). + map(Material::name).toList()); options.add("HAND"); diff --git a/src/main/java/world/bentobox/level/panels/DetailsPanel.java b/src/main/java/world/bentobox/level/panels/DetailsPanel.java index 5a13682..395e5ed 100644 --- a/src/main/java/world/bentobox/level/panels/DetailsPanel.java +++ b/src/main/java/world/bentobox/level/panels/DetailsPanel.java @@ -1,16 +1,23 @@ package world.bentobox.level.panels; -import com.google.common.base.Enums; +import java.io.File; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import org.bukkit.Material; import org.bukkit.World; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; -import java.io.File; -import java.util.*; -import java.util.stream.Collectors; + +import com.google.common.base.Enums; import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.TemplatedPanel; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; @@ -18,7 +25,6 @@ import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.hooks.LangUtilsHook; import world.bentobox.bentobox.util.Pair; import world.bentobox.level.Level; import world.bentobox.level.objects.IslandLevels; @@ -43,8 +49,8 @@ public class DetailsPanel * @param user User who opens panel */ private DetailsPanel(Level addon, - World world, - User user) + World world, + User user) { this.addon = addon; this.world = world; @@ -121,99 +127,93 @@ private void updateFilters() switch (this.activeTab) { - case ALL_BLOCKS -> { - Map materialCountMap = new EnumMap<>(Material.class); + case ALL_BLOCKS -> { + Map materialCountMap = new EnumMap<>(Material.class); - materialCountMap.putAll(this.levelsData.getMdCount()); + materialCountMap.putAll(this.levelsData.getMdCount()); - // Add underwater blocks. - this.levelsData.getUwCount().forEach((material, count) -> { - materialCountMap.put(material, - materialCountMap.computeIfAbsent(material, key -> 0) + count); - }); + // Add underwater blocks. + this.levelsData.getUwCount().forEach((material, count) -> materialCountMap.put(material, + materialCountMap.computeIfAbsent(material, key -> 0) + count)); - materialCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey())). - forEachOrdered(entry -> - this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); - } - case ABOVE_SEA_LEVEL -> { - this.levelsData.getMdCount().entrySet().stream().sorted((Map.Entry.comparingByKey())). - forEachOrdered(entry -> - this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); - } - case UNDERWATER -> { - this.levelsData.getUwCount().entrySet().stream().sorted((Map.Entry.comparingByKey())). - forEachOrdered(entry -> - this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); - } - case SPAWNER -> { - int aboveWater = this.levelsData.getMdCount().getOrDefault(Material.SPAWNER, 0); - int underWater = this.levelsData.getUwCount().getOrDefault(Material.SPAWNER, 0); + materialCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey())). + forEachOrdered(entry -> + this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); + } + case ABOVE_SEA_LEVEL -> this.levelsData.getMdCount().entrySet().stream().sorted((Map.Entry.comparingByKey())) + .forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); - // TODO: spawners need some touch... - this.materialCountList.add(new Pair<>(Material.SPAWNER, underWater + aboveWater)); - } + case UNDERWATER -> this.levelsData.getUwCount().entrySet().stream().sorted((Map.Entry.comparingByKey())) + .forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); + + case SPAWNER -> { + int aboveWater = this.levelsData.getMdCount().getOrDefault(Material.SPAWNER, 0); + int underWater = this.levelsData.getUwCount().getOrDefault(Material.SPAWNER, 0); + + // TODO: spawners need some touch... + this.materialCountList.add(new Pair<>(Material.SPAWNER, underWater + aboveWater)); + } } Comparator> sorter; switch (this.activeFilter) { - case COUNT -> + case COUNT -> + { + sorter = (o1, o2) -> { - sorter = (o1, o2) -> + if (o1.getValue().equals(o2.getValue())) { - if (o1.getValue().equals(o2.getValue())) - { - String o1Name = Utils.prettifyObject(o1.getKey(), this.user); - String o2Name = Utils.prettifyObject(o2.getKey(), this.user); + String o1Name = Utils.prettifyObject(o1.getKey(), this.user); + String o2Name = Utils.prettifyObject(o2.getKey(), this.user); - return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); - } - else - { - return Integer.compare(o2.getValue(), o1.getValue()); - } - }; - } - case VALUE -> - { - sorter = (o1, o2) -> + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + } + else { - int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o1.getKey(), 0); - int o1Count = blockLimit > 0 ? Math.min(o1.getValue(), blockLimit) : o1.getValue(); + return Integer.compare(o2.getValue(), o1.getValue()); + } + }; + } + case VALUE -> + { + sorter = (o1, o2) -> + { + int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o1.getKey(), 0); + int o1Count = blockLimit > 0 ? Math.min(o1.getValue(), blockLimit) : o1.getValue(); - blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o2.getKey(), 0); - int o2Count = blockLimit > 0 ? Math.min(o2.getValue(), blockLimit) : o2.getValue(); + blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o2.getKey(), 0); + int o2Count = blockLimit > 0 ? Math.min(o2.getValue(), blockLimit) : o2.getValue(); - long o1Value = (long) o1Count * + long o1Value = (long) o1Count * this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0); - long o2Value = (long) o2Count * + long o2Value = (long) o2Count * this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0); - if (o1Value == o2Value) - { - String o1Name = Utils.prettifyObject(o1.getKey(), this.user); - String o2Name = Utils.prettifyObject(o2.getKey(), this.user); - - return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); - } - else - { - return Long.compare(o2Value, o1Value); - } - }; - } - default -> - { - sorter = (o1, o2) -> + if (o1Value == o2Value) { String o1Name = Utils.prettifyObject(o1.getKey(), this.user); String o2Name = Utils.prettifyObject(o2.getKey(), this.user); return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); - }; - } + } + else + { + return Long.compare(o2Value, o1Value); + } + }; + } + default -> + { + sorter = (o1, o2) -> + { + String o1Name = Utils.prettifyObject(o1.getKey(), this.user); + String o2Name = Utils.prettifyObject(o2.getKey(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + }; + } } this.materialCountList.sort(sorter); @@ -222,9 +222,9 @@ private void updateFilters() } -// --------------------------------------------------------------------- -// Section: Tab Button Type -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Tab Button Type + // --------------------------------------------------------------------- /** @@ -262,23 +262,21 @@ private PanelItem createTabButton(ItemTemplateRecord template, TemplatedPanel.It List activeActions = new ArrayList<>(template.actions()); activeActions.removeIf(action -> - "VIEW".equalsIgnoreCase(action.actionType()) && this.activeTab == tab); + "VIEW".equalsIgnoreCase(action.actionType()) && this.activeTab == tab); // Add Click handler builder.clickHandler((panel, user, clickType, i) -> { for (ItemTemplateRecord.ActionRecords action : activeActions) { - if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + && "VIEW".equalsIgnoreCase(action.actionType())) { - if ("VIEW".equalsIgnoreCase(action.actionType())) - { - this.activeTab = tab; + this.activeTab = tab; - // Update filters. - this.updateFilters(); - this.build(); - } + // Update filters. + this.updateFilters(); + this.build(); } } @@ -287,10 +285,10 @@ private PanelItem createTabButton(ItemTemplateRecord template, TemplatedPanel.It // Collect tooltips. List tooltips = activeActions.stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -398,10 +396,10 @@ else if ("SELECT".equalsIgnoreCase(action.actionType())) // Collect tooltips. List tooltips = activeActions.stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -417,9 +415,9 @@ else if ("SELECT".equalsIgnoreCase(action.actionType())) } -// --------------------------------------------------------------------- -// Section: Create common buttons -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Create common buttons + // --------------------------------------------------------------------- /** @@ -434,7 +432,7 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I long size = this.materialCountList.size(); if (size <= slot.amountMap().getOrDefault("BLOCK", 1) || - 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) + 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) { // There are no next elements return null; @@ -448,7 +446,7 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I { ItemStack clone = template.icon().clone(); - if ((Boolean) template.dataMap().getOrDefault("indexing", false)) + if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) { clone.setAmount(nextPageIndex); } @@ -464,7 +462,7 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), - "[number]", String.valueOf(nextPageIndex))); + TextVariables.NUMBER, String.valueOf(nextPageIndex))); } // Add ClickHandler @@ -472,13 +470,11 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I { for (ItemTemplateRecord.ActionRecords action : template.actions()) { - if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) && + "NEXT".equalsIgnoreCase(action.actionType())) { - if ("NEXT".equalsIgnoreCase(action.actionType())) - { - this.pageIndex++; - this.build(); - } + this.pageIndex++; + this.build(); } } @@ -488,10 +484,10 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I // Collect tooltips. List tooltips = template.actions().stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -528,7 +524,7 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan { ItemStack clone = template.icon().clone(); - if ((Boolean) template.dataMap().getOrDefault("indexing", false)) + if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) { clone.setAmount(previousPageIndex); } @@ -544,7 +540,7 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), - "[number]", String.valueOf(previousPageIndex))); + TextVariables.NUMBER, String.valueOf(previousPageIndex))); } // Add ClickHandler @@ -552,13 +548,11 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan { for (ItemTemplateRecord.ActionRecords action : template.actions()) { - if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + && "PREVIOUS".equalsIgnoreCase(action.actionType())) { - if ("PREVIOUS".equalsIgnoreCase(action.actionType())) - { - this.pageIndex--; - this.build(); - } + this.pageIndex--; + this.build(); } } @@ -568,10 +562,10 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan // Collect tooltips. List tooltips = template.actions().stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -585,9 +579,9 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan } -// --------------------------------------------------------------------- -// Section: Create Material Button -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Create Material Button + // --------------------------------------------------------------------- /** @@ -625,7 +619,7 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPan * @return PanelItem for generator tier. */ private PanelItem createMaterialButton(ItemTemplateRecord template, - Pair materialCount) + Pair materialCount) { PanelItemBuilder builder = new PanelItemBuilder(); @@ -646,30 +640,30 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, if (template.title() != null) { builder.name(this.user.getTranslation(this.world, template.title(), - "[number]", String.valueOf(materialCount.getValue()), - "[material]", Utils.prettifyObject(materialCount.getKey(), this.user))); + TextVariables.NUMBER, String.valueOf(materialCount.getValue()), + "[material]", Utils.prettifyObject(materialCount.getKey(), this.user))); } String description = Utils.prettifyDescription(materialCount.getKey(), this.user); final String reference = "level.gui.buttons.material."; String blockId = this.user.getTranslationOrNothing(reference + "id", - "[id]", materialCount.getKey().name()); + "[id]", materialCount.getKey().name()); int blockValue = this.addon.getBlockConfig().getBlockValues().getOrDefault(materialCount.getKey(), 0); String value = blockValue > 0 ? this.user.getTranslationOrNothing(reference + "value", - "[number]", String.valueOf(blockValue)) : ""; + TextVariables.NUMBER, String.valueOf(blockValue)) : ""; int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(materialCount.getKey(), 0); String limit = blockLimit > 0 ? this.user.getTranslationOrNothing(reference + "limit", - "[number]", String.valueOf(blockLimit)) : ""; + TextVariables.NUMBER, String.valueOf(blockLimit)) : ""; String count = this.user.getTranslationOrNothing(reference + "count", - "[number]", String.valueOf(materialCount.getValue())); + TextVariables.NUMBER, String.valueOf(materialCount.getValue())); long calculatedValue = (long) Math.min(blockLimit > 0 ? blockLimit : Integer.MAX_VALUE, materialCount.getValue()) * blockValue; String valueText = calculatedValue > 0 ? this.user.getTranslationOrNothing(reference + "calculated", - "[number]", String.valueOf(calculatedValue)) : ""; + TextVariables.NUMBER, String.valueOf(calculatedValue)) : ""; if (template.description() != null) { @@ -680,18 +674,18 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, "[calculated]", valueText, "[limit]", limit, "[count]", count). - replaceAll("(?m)^[ \\t]*\\r?\\n", ""). - replaceAll("(? { - Island island = this.addon.getIslandsManager().getIsland(this.world, entry.getKey()); - return new IslandTopRecord(island, entry.getValue()); - }). - collect(Collectors.toList()); + map(entry -> { + Island island = this.addon.getIslandsManager().getIsland(this.world, entry.getKey()); + return new IslandTopRecord(island, entry.getValue()); + }). + collect(Collectors.toList()); } @@ -82,9 +88,9 @@ public void build() } -// --------------------------------------------------------------------- -// Section: Methods -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Methods + // --------------------------------------------------------------------- /** @@ -100,8 +106,6 @@ private PanelItem createFallback(ItemTemplateRecord template, long index) return null; } - final String reference = "level.gui.buttons.island."; - PanelItemBuilder builder = new PanelItemBuilder(); if (template.icon() != null) @@ -112,18 +116,18 @@ private PanelItem createFallback(ItemTemplateRecord template, long index) if (template.title() != null) { builder.name(this.user.getTranslation(this.world, template.title(), - "[name]", String.valueOf(index))); + TextVariables.NAME, String.valueOf(index))); } else { - builder.name(this.user.getTranslation(this.world, reference, - "[name]", String.valueOf(index))); + builder.name(this.user.getTranslation(this.world, REFERENCE, + TextVariables.NAME, String.valueOf(index))); } if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), - "[number]", String.valueOf(index))); + TextVariables.NUMBER, String.valueOf(index))); } builder.amount(index != 0 ? (int) index : 1); @@ -189,23 +193,23 @@ private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord { switch (action.actionType().toUpperCase()) { - case "WARP" -> { - return island.getOwner() == null || + case "WARP" -> { + return island.getOwner() == null || this.addon.getWarpHook() == null || !this.addon.getWarpHook().getWarpSignsManager().hasWarp(this.world, island.getOwner()); - } - case "VISIT" -> { - return island.getOwner() == null || + } + case "VISIT" -> { + return island.getOwner() == null || this.addon.getVisitHook() == null || !this.addon.getVisitHook().getAddonManager().preprocessTeleportation(this.user, island); - } - case "VIEW" -> { - return island.getOwner() == null || + } + case "VIEW" -> { + return island.getOwner() == null || !island.getMemberSet(RanksManager.MEMBER_RANK).contains(this.user.getUniqueId()); - } - default -> { - return false; - } + } + default -> { + return false; + } } }); @@ -218,33 +222,38 @@ private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord { switch (action.actionType().toUpperCase()) { - case "WARP" -> { - this.user.closeInventory(); - this.addon.getWarpHook().getWarpSignsManager().warpPlayer(this.world, this.user, island.getOwner()); - } - case "VISIT" -> { - // The command call implementation solves necessity to check for all visits options, - // like cool down, confirmation and preprocess in single go. Would it be better to write - // all logic here? - - this.addon.getPlugin().getIWM().getAddon(this.world). - flatMap(GameModeAddon::getPlayerCommand).ifPresent(command -> - { - String mainCommand = - this.addon.getVisitHook().getSettings().getPlayerMainCommand(); - - if (!mainCommand.isBlank()) - { - this.user.closeInventory(); - this.user.performCommand(command.getTopLabel() + " " + mainCommand + " " + island.getOwner()); - } - }); - } - case "VIEW" -> { + case "WARP" -> { + this.user.closeInventory(); + this.addon.getWarpHook().getWarpSignsManager().warpPlayer(this.world, this.user, island.getOwner()); + } + case "VISIT" -> + // The command call implementation solves necessity to check for all visits options, + // like cool down, confirmation and preprocess in single go. Would it be better to write + // all logic here? + + this.addon.getPlugin().getIWM().getAddon(this.world). + flatMap(GameModeAddon::getPlayerCommand).ifPresent(command -> + { + String mainCommand = + this.addon.getVisitHook().getSettings().getPlayerMainCommand(); + + if (!mainCommand.isBlank()) + { this.user.closeInventory(); - // Open Detailed GUI. - DetailsPanel.openPanel(this.addon, this.world, this.user); + this.user.performCommand(command.getTopLabel() + " " + mainCommand + " " + island.getOwner()); } + }); + + case "VIEW" -> { + this.user.closeInventory(); + // Open Detailed GUI. + DetailsPanel.openPanel(this.addon, this.world, this.user); + } + // Catch default + default -> { + this.user.closeInventory(); + addon.logError("Unknown action type " + action.actionType().toUpperCase()); + } } } } @@ -254,10 +263,10 @@ private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord // Collect tooltips. List tooltips = activeActions.stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -278,22 +287,21 @@ private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord * @param template the template * @param island the island */ - private void populateIslandTitle(PanelItemBuilder builder, - ItemTemplateRecord template, - Island island) + private void populateIslandTitle(PanelItemBuilder builder, + ItemTemplateRecord template, + Island island) { - final String reference = "level.gui.buttons.island."; // Get Island Name String nameText; if (island.getName() == null || island.getName().isEmpty()) { - nameText = this.user.getTranslation(reference + "owners-island", - "[player]", - island.getOwner() == null ? - this.user.getTranslation(reference + "unknown") : - this.addon.getPlayers().getName(island.getOwner())); + nameText = this.user.getTranslation(REFERENCE + "owners-island", + PLAYER, + island.getOwner() == null ? + this.user.getTranslation(REFERENCE + "unknown") : + this.addon.getPlayers().getName(island.getOwner())); } else { @@ -304,11 +312,11 @@ private void populateIslandTitle(PanelItemBuilder builder, if (template.title() != null && !template.title().isBlank()) { builder.name(this.user.getTranslation(this.world, template.title(), - "[name]", nameText)); + TextVariables.NAME, nameText)); } else { - builder.name(this.user.getTranslation(reference + "name", "[name]", nameText)); + builder.name(this.user.getTranslation(REFERENCE + "name", TextVariables.NAME, nameText)); } } @@ -321,11 +329,11 @@ private void populateIslandTitle(PanelItemBuilder builder, * @param island the island */ private void populateIslandIcon(PanelItemBuilder builder, - ItemTemplateRecord template, - Island island) + ItemTemplateRecord template, + Island island) { User owner = island.getOwner() == null ? null : User.getInstance(island.getOwner()); - + // Get permission or island icon String permissionIcon = owner == null ? null : Utils.getPermissionValue(owner, this.iconPermission, null); @@ -376,20 +384,18 @@ else if (owner != null) * @param islandTopRecord the top record object * @param index place index. */ - private void populateIslandDescription(PanelItemBuilder builder, - ItemTemplateRecord template, - Island island, - IslandTopRecord islandTopRecord, - int index) + private void populateIslandDescription(PanelItemBuilder builder, + ItemTemplateRecord template, + Island island, + IslandTopRecord islandTopRecord, + int index) { - final String reference = "level.gui.buttons.island."; - // Get Owner Name - String ownerText = this.user.getTranslation(reference + "owner", - "[player]", - island.getOwner() == null ? - this.user.getTranslation(reference + "unknown") : - this.addon.getPlayers().getName(island.getOwner())); + String ownerText = this.user.getTranslation(REFERENCE + "owner", + PLAYER, + island.getOwner() == null ? + this.user.getTranslation(REFERENCE + "unknown") : + this.addon.getPlayers().getName(island.getOwner())); // Get Members Text String memberText; @@ -397,11 +403,11 @@ private void populateIslandDescription(PanelItemBuilder builder, if (island.getMemberSet().size() > 1) { StringBuilder memberBuilder = new StringBuilder( - this.user.getTranslationOrNothing(reference + "members-title")); + this.user.getTranslationOrNothing(REFERENCE + "members-title")); for (UUID uuid : island.getMemberSet()) { - User user = User.getInstance(uuid); + User member = User.getInstance(uuid); if (memberBuilder.length() > 0) { @@ -409,8 +415,8 @@ private void populateIslandDescription(PanelItemBuilder builder, } memberBuilder.append( - this.user.getTranslationOrNothing(reference + "member", - "[player]", user.getName())); + this.user.getTranslationOrNothing(REFERENCE + "member", + PLAYER, member.getName())); } memberText = memberBuilder.toString(); @@ -420,11 +426,11 @@ private void populateIslandDescription(PanelItemBuilder builder, memberText = ""; } - String placeText = this.user.getTranslation(reference + "place", - "[number]", String.valueOf(index)); + String placeText = this.user.getTranslation(REFERENCE + "place", + TextVariables.NUMBER, String.valueOf(index)); - String levelText = this.user.getTranslation(reference + "level", - "[number]", this.addon.getManager().formatLevel(islandTopRecord.level())); + String levelText = this.user.getTranslation(REFERENCE + "level", + TextVariables.NUMBER, this.addon.getManager().formatLevel(islandTopRecord.level())); // Template specific description is always more important than custom one. if (template.description() != null && !template.description().isBlank()) @@ -434,26 +440,26 @@ private void populateIslandDescription(PanelItemBuilder builder, "[members]", memberText, "[level]", levelText, "[place]", placeText). - replaceAll("(?m)^[ \\t]*\\r?\\n", ""). - replaceAll("(? level to island -> level. @@ -505,9 +516,9 @@ public static void openPanel(Level addon, User user, World world, String permiss private record IslandTopRecord(Island island, Long level) {} -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- /** * This variable allows to access addon object. diff --git a/src/main/java/world/bentobox/level/panels/ValuePanel.java b/src/main/java/world/bentobox/level/panels/ValuePanel.java index fdc04ae..e97ca54 100644 --- a/src/main/java/world/bentobox/level/panels/ValuePanel.java +++ b/src/main/java/world/bentobox/level/panels/ValuePanel.java @@ -1,25 +1,29 @@ package world.bentobox.level.panels; -import com.google.common.base.Enums; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + import org.bukkit.Material; import org.bukkit.World; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; -import java.io.File; -import java.util.*; -import java.util.function.Consumer; -import java.util.stream.Collectors; + +import com.google.common.base.Enums; import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.TemplatedPanel; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.hooks.LangUtilsHook; -import world.bentobox.bentobox.util.Pair; import world.bentobox.level.Level; import world.bentobox.level.util.ConversationUtils; import world.bentobox.level.util.Utils; @@ -30,6 +34,7 @@ */ public class ValuePanel { + // --------------------------------------------------------------------- // Section: Internal Constructor // --------------------------------------------------------------------- @@ -43,8 +48,8 @@ public class ValuePanel * @param user User who opens panel */ private ValuePanel(Level addon, - World world, - User user) + World world, + User user) { this.addon = addon; this.world = world; @@ -52,18 +57,18 @@ private ValuePanel(Level addon, this.activeFilter = Filter.NAME_ASC; this.materialRecordList = Arrays.stream(Material.values()). - filter(Material::isBlock). - filter(m -> !m.name().startsWith("LEGACY_")). - map(material -> - { - Integer value = this.addon.getBlockConfig().getValue(this.world, material); - Integer limit = this.addon.getBlockConfig().getBlockLimits().get(material); + filter(Material::isBlock). + filter(m -> !m.name().startsWith("LEGACY_")). + map(material -> + { + Integer value = this.addon.getBlockConfig().getValue(this.world, material); + Integer limit = this.addon.getBlockConfig().getBlockLimits().get(material); - return new MaterialRecord(material, - value != null ? value : 0, - limit != null ? limit : 0); - }). - collect(Collectors.toList()); + return new MaterialRecord(material, + value != null ? value : 0, + limit != null ? limit : 0); + }). + collect(Collectors.toList()); this.elementList = new ArrayList<>(Material.values().length); this.searchText = ""; @@ -86,7 +91,7 @@ private void build() panelBuilder.registerTypeBuilder("NEXT", this::createNextButton); panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton); - panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton); + panelBuilder.registerTypeBuilder(BLOCK, this::createMaterialButton); panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton); panelBuilder.registerTypeBuilder("SEARCH", this::createSearchButton); @@ -105,60 +110,60 @@ private void updateFilters() switch (this.activeFilter) { - case VALUE_ASC -> + case VALUE_ASC -> + + sorter = (o1, o2) -> + { + if (o1.value().equals(o2.value())) { - sorter = (o1, o2) -> - { - if (o1.value().equals(o2.value())) - { - String o1Name = Utils.prettifyObject(o1.material(), this.user); - String o2Name = Utils.prettifyObject(o2.material(), this.user); + String o1Name = Utils.prettifyObject(o1.material(), this.user); + String o2Name = Utils.prettifyObject(o2.material(), this.user); - return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); - } - else - { - return Integer.compare(o1.value(), o2.value()); - } - }; + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); } - case VALUE_DESC -> + else { - sorter = (o1, o2) -> - { - if (o1.value().equals(o2.value())) - { - String o1Name = Utils.prettifyObject(o1.material(), this.user); - String o2Name = Utils.prettifyObject(o2.material(), this.user); - - return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); - } - else - { - return Integer.compare(o2.value(), o1.value()); - } - }; + return Integer.compare(o1.value(), o2.value()); } - case NAME_DESC -> + }; + + case VALUE_DESC -> + + sorter = (o1, o2) -> + { + if (o1.value().equals(o2.value())) { - sorter = (o1, o2) -> - { - String o1Name = Utils.prettifyObject(o1.material(), this.user); - String o2Name = Utils.prettifyObject(o2.material(), this.user); + String o1Name = Utils.prettifyObject(o1.material(), this.user); + String o2Name = Utils.prettifyObject(o2.material(), this.user); - return String.CASE_INSENSITIVE_ORDER.compare(o2Name, o1Name); - }; + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); } - default -> + else { - sorter = (o1, o2) -> - { - String o1Name = Utils.prettifyObject(o1.material(), this.user); - String o2Name = Utils.prettifyObject(o2.material(), this.user); - - return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); - }; + return Integer.compare(o2.value(), o1.value()); } + }; + + case NAME_DESC -> + + sorter = (o1, o2) -> + { + String o1Name = Utils.prettifyObject(o1.material(), this.user); + String o2Name = Utils.prettifyObject(o2.material(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o2Name, o1Name); + }; + + default -> + + sorter = (o1, o2) -> + { + String o1Name = Utils.prettifyObject(o1.material(), this.user); + String o2Name = Utils.prettifyObject(o2.material(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + }; + } this.materialRecordList.sort(sorter); @@ -168,12 +173,12 @@ private void updateFilters() this.elementList = new ArrayList<>(this.materialRecordList.size()); final String text = this.searchText.toLowerCase(); - this.materialRecordList.forEach(record -> + this.materialRecordList.forEach(rec -> { - if (record.material.name().toLowerCase().contains(text) || - Utils.prettifyObject(record.material(), this.user).toLowerCase().contains(text)) + if (rec.material.name().toLowerCase().contains(text) || + Utils.prettifyObject(rec.material(), this.user).toLowerCase().contains(text)) { - this.elementList.add(record); + this.elementList.add(rec); } }); } @@ -186,9 +191,9 @@ private void updateFilters() } -// --------------------------------------------------------------------- -// Section: Tab Button Type -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Tab Button Type + // --------------------------------------------------------------------- /** @@ -224,7 +229,7 @@ private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel List activeActions = new ArrayList<>(template.actions()); activeActions.removeIf(action -> - "CLEAR".equalsIgnoreCase(action.actionType()) && this.searchText.isBlank()); + "CLEAR".equalsIgnoreCase(action.actionType()) && this.searchText.isBlank()); // Add Click handler builder.clickHandler((panel, user, clickType, i) -> @@ -257,9 +262,9 @@ else if ("INPUT".equalsIgnoreCase(action.actionType())) // start conversation ConversationUtils.createStringInput(consumer, - user, - user.getTranslation("level.conversations.write-search"), - user.getTranslation("level.conversations.search-updated")); + user, + user.getTranslation("level.conversations.write-search"), + user.getTranslation("level.conversations.search-updated")); } } } @@ -269,10 +274,10 @@ else if ("INPUT".equalsIgnoreCase(action.actionType())) // Collect tooltips. List tooltips = activeActions.stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -336,7 +341,7 @@ private PanelItem createFilterButton(ItemTemplateRecord template, TemplatedPanel if (this.activeFilter.name().startsWith(filterName)) { return this.activeFilter.name().endsWith("ASC") && "ASC".equalsIgnoreCase(action.actionType()) || - this.activeFilter.name().endsWith("DESC") && "DESC".equalsIgnoreCase(action.actionType()); + this.activeFilter.name().endsWith("DESC") && "DESC".equalsIgnoreCase(action.actionType()); } else { @@ -375,10 +380,10 @@ else if ("DESC".equalsIgnoreCase(action.actionType())) // Collect tooltips. List tooltips = activeActions.stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -394,9 +399,9 @@ else if ("DESC".equalsIgnoreCase(action.actionType())) } -// --------------------------------------------------------------------- -// Section: Create common buttons -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Create common buttons + // --------------------------------------------------------------------- /** @@ -410,8 +415,8 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I { long size = this.elementList.size(); - if (size <= slot.amountMap().getOrDefault("BLOCK", 1) || - 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) + if (size <= slot.amountMap().getOrDefault(BLOCK, 1) || + 1.0 * size / slot.amountMap().getOrDefault(BLOCK, 1) <= this.pageIndex + 1) { // There are no next elements return null; @@ -425,7 +430,7 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I { ItemStack clone = template.icon().clone(); - if ((Boolean) template.dataMap().getOrDefault("indexing", false)) + if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) { clone.setAmount(nextPageIndex); } @@ -441,7 +446,7 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), - "[number]", String.valueOf(nextPageIndex))); + TextVariables.NUMBER, String.valueOf(nextPageIndex))); } // Add ClickHandler @@ -449,13 +454,11 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I { for (ItemTemplateRecord.ActionRecords action : template.actions()) { - if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + && "NEXT".equalsIgnoreCase(action.actionType())) { - if ("NEXT".equalsIgnoreCase(action.actionType())) - { - this.pageIndex++; - this.build(); - } + this.pageIndex++; + this.build(); } } @@ -465,10 +468,10 @@ private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.I // Collect tooltips. List tooltips = template.actions().stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -505,7 +508,7 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan { ItemStack clone = template.icon().clone(); - if ((Boolean) template.dataMap().getOrDefault("indexing", false)) + if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) { clone.setAmount(previousPageIndex); } @@ -521,7 +524,7 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan if (template.description() != null) { builder.description(this.user.getTranslation(this.world, template.description(), - "[number]", String.valueOf(previousPageIndex))); + TextVariables.NUMBER, String.valueOf(previousPageIndex))); } // Add ClickHandler @@ -529,13 +532,11 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan { for (ItemTemplateRecord.ActionRecords action : template.actions()) { - if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + && "PREVIOUS".equalsIgnoreCase(action.actionType())) { - if ("PREVIOUS".equalsIgnoreCase(action.actionType())) - { - this.pageIndex--; - this.build(); - } + this.pageIndex--; + this.build(); } } @@ -545,10 +546,10 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan // Collect tooltips. List tooltips = template.actions().stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null). + map(action -> this.user.getTranslation(this.world, action.tooltip())). + filter(text -> !text.isBlank()). + collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -562,9 +563,9 @@ private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPan } -// --------------------------------------------------------------------- -// Section: Create Material Button -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Create Material Button + // --------------------------------------------------------------------- /** @@ -582,7 +583,7 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPan return null; } - int index = this.pageIndex * slot.amountMap().getOrDefault("BLOCK", 1) + slot.slot(); + int index = this.pageIndex * slot.amountMap().getOrDefault(BLOCK, 1) + slot.slot(); if (index >= this.elementList.size()) { @@ -602,7 +603,7 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPan * @return PanelItem for generator tier. */ private PanelItem createMaterialButton(ItemTemplateRecord template, - MaterialRecord materialRecord) + MaterialRecord materialRecord) { PanelItemBuilder builder = new PanelItemBuilder(); @@ -623,24 +624,24 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, if (template.title() != null) { builder.name(this.user.getTranslation(this.world, template.title(), - "[material]", Utils.prettifyObject(materialRecord.material(), this.user))); + "[material]", Utils.prettifyObject(materialRecord.material(), this.user))); } String description = Utils.prettifyDescription(materialRecord.material(), this.user); final String reference = "level.gui.buttons.material."; String blockId = this.user.getTranslationOrNothing(reference + "id", - "[id]", materialRecord.material().name()); + "[id]", materialRecord.material().name()); String value = this.user.getTranslationOrNothing(reference + "value", - "[number]", String.valueOf(materialRecord.value())); + TextVariables.NUMBER, String.valueOf(materialRecord.value())); String underWater; if (this.addon.getSettings().getUnderWaterMultiplier() > 1.0) { underWater = this.user.getTranslationOrNothing(reference + "underwater", - "[number]", String.valueOf(materialRecord.value() * this.addon.getSettings().getUnderWaterMultiplier())); + TextVariables.NUMBER, String.valueOf(materialRecord.value() * this.addon.getSettings().getUnderWaterMultiplier())); } else { @@ -648,7 +649,7 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, } String limit = materialRecord.limit() > 0 ? this.user.getTranslationOrNothing(reference + "limit", - "[number]", String.valueOf(materialRecord.limit())) : ""; + TextVariables.NUMBER, String.valueOf(materialRecord.limit())) : ""; if (template.description() != null) { @@ -658,13 +659,13 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, "[value]", value, "[underwater]", underWater, "[limit]", limit). - replaceAll("(?m)^[ \\t]*\\r?\\n", ""). - replaceAll("(? { - System.out.println("Material: " + materialRecord.material()); + addon.log("Material: " + materialRecord.material()); return true; }); @@ -672,9 +673,9 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, } -// --------------------------------------------------------------------- -// Section: Other Methods -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Other Methods + // --------------------------------------------------------------------- /** @@ -686,16 +687,16 @@ private PanelItem createMaterialButton(ItemTemplateRecord template, * @param user User who opens panel */ public static void openPanel(Level addon, - World world, - User user) + World world, + User user) { new ValuePanel(addon, world, user).build(); } -// --------------------------------------------------------------------- -// Section: Enums -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Enums + // --------------------------------------------------------------------- /** @@ -726,10 +727,15 @@ private record MaterialRecord(Material material, Integer value, Integer limit) { } + // --------------------------------------------------------------------- + // Section: Constants + // --------------------------------------------------------------------- + + private static final String BLOCK = "BLOCK"; -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- /** * This variable allows to access addon object. diff --git a/src/main/java/world/bentobox/level/util/ConversationUtils.java b/src/main/java/world/bentobox/level/util/ConversationUtils.java index ad9dbcc..968f19e 100644 --- a/src/main/java/world/bentobox/level/util/ConversationUtils.java +++ b/src/main/java/world/bentobox/level/util/ConversationUtils.java @@ -7,10 +7,16 @@ package world.bentobox.level.util; -import org.bukkit.conversations.*; +import java.util.function.Consumer; + +import org.bukkit.conversations.ConversationAbandonedListener; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.ConversationFactory; +import org.bukkit.conversations.MessagePrompt; +import org.bukkit.conversations.Prompt; +import org.bukkit.conversations.StringPrompt; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import java.util.function.Consumer; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; @@ -18,10 +24,11 @@ public class ConversationUtils { -// --------------------------------------------------------------------- -// Section: Conversation API implementation -// --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // Section: Conversation API implementation + // --------------------------------------------------------------------- + private ConversationUtils() {} // Private constructor as this is a utility class only with static methods /** * This method will close opened gui and writes question in chat. After players answers on question in chat, message @@ -32,9 +39,9 @@ public class ConversationUtils * @param user User who is targeted with current confirmation. */ public static void createStringInput(Consumer consumer, - User user, - @NonNull String question, - @Nullable String successMessage) + User user, + @NonNull String question, + @Nullable String successMessage) { // Text input message. StringPrompt stringPrompt = new StringPrompt() @@ -56,16 +63,16 @@ public static void createStringInput(Consumer consumer, }; new ConversationFactory(BentoBox.getInstance()). - withPrefix(context -> user.getTranslation("level.conversations.prefix")). - withFirstPrompt(stringPrompt). - // On cancel conversation will be closed. - withLocalEcho(false). - withTimeout(90). - withEscapeSequence(user.getTranslation("level.conversations.cancel-string")). - // Use null value in consumer to detect if user has abandoned conversation. - addConversationAbandonedListener(ConversationUtils.getAbandonListener(consumer, user)). - buildConversation(user.getPlayer()). - begin(); + withPrefix(context -> user.getTranslation("level.conversations.prefix")). + withFirstPrompt(stringPrompt). + // On cancel conversation will be closed. + withLocalEcho(false). + withTimeout(90). + withEscapeSequence(user.getTranslation("level.conversations.cancel-string")). + // Use null value in consumer to detect if user has abandoned conversation. + addConversationAbandonedListener(ConversationUtils.getAbandonListener(consumer, user)). + buildConversation(user.getPlayer()). + begin(); } @@ -111,7 +118,7 @@ private static ConversationAbandonedListener getAbandonListener(Consumer cons consumer.accept(null); // send cancell message abandonedEvent.getContext().getForWhom().sendRawMessage( - user.getTranslation("level.conversations.prefix") + + user.getTranslation("level.conversations.prefix") + user.getTranslation("level.conversations.cancelled")); } }; diff --git a/src/main/java/world/bentobox/level/util/Utils.java b/src/main/java/world/bentobox/level/util/Utils.java index 6d177a1..1433666 100644 --- a/src/main/java/world/bentobox/level/util/Utils.java +++ b/src/main/java/world/bentobox/level/util/Utils.java @@ -7,10 +7,10 @@ package world.bentobox.level.util; +import java.util.List; + import org.bukkit.Material; import org.bukkit.permissions.PermissionAttachmentInfo; -import java.util.List; -import java.util.stream.Collectors; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.hooks.LangUtilsHook; @@ -18,6 +18,10 @@ public class Utils { + private static final String LEVEL_MATERIALS = "level.materials."; + + private Utils() {} // Private constructor as this is a utility class only with static methods + /** * This method sends a message to the user with appended "prefix" text before message. * @param user User who receives message. @@ -27,7 +31,7 @@ public class Utils public static void sendMessage(User user, String translationText, String... parameters) { user.sendMessage(user.getTranslation( "level.conversations.prefix") + - user.getTranslation( translationText, parameters)); + user.getTranslation( translationText, parameters)); } @@ -52,9 +56,9 @@ public static String getPermissionValue(User user, String permissionPrefix, Stri String permPrefix = permissionPrefix + "."; List permissions = user.getEffectivePermissions().stream(). - map(PermissionAttachmentInfo::getPermission). - filter(permission -> permission.startsWith(permPrefix)). - collect(Collectors.toList()); + map(PermissionAttachmentInfo::getPermission). + filter(permission -> permission.startsWith(permPrefix)). + toList(); for (String permission : permissions) { @@ -154,7 +158,7 @@ public static String prettifyObject(Material object, User user) // materials: // [material]: // name: [name] - String translation = user.getTranslationOrNothing("level.materials." + object.name().toLowerCase() + ".name"); + String translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase() + ".name"); if (!translation.isEmpty()) { @@ -167,7 +171,7 @@ public static String prettifyObject(Material object, User user) // materials: // [material]: [name] - translation = user.getTranslationOrNothing("level.materials." + object.name().toLowerCase()); + translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase()); if (!translation.isEmpty()) { @@ -211,7 +215,7 @@ public static String prettifyDescription(Material object, User user) // materials: // [material]: // description: [text] - String translation = user.getTranslationOrNothing("level.materials." + object.name().toLowerCase() + ".description"); + String translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase() + ".description"); if (!translation.isEmpty()) { diff --git a/src/test/java/world/bentobox/level/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java index f6b5d54..5b18aa5 100644 --- a/src/test/java/world/bentobox/level/LevelsManagerTest.java +++ b/src/test/java/world/bentobox/level/LevelsManagerTest.java @@ -166,7 +166,7 @@ public void setUp() throws Exception { // Default to uuid's being island owners when(im.isOwner(eq(world), any())).thenReturn(true); when(im.getOwner(any(), any(UUID.class))).thenAnswer(in -> in.getArgument(1, UUID.class)); - when(im.getIsland(eq(world), eq(uuid))).thenReturn(island); + when(im.getIsland(world, uuid)).thenReturn(island); when(im.getIslandById(anyString())).thenReturn(Optional.of(island)); // Player @@ -392,8 +392,8 @@ public void testLoadTopTens() { Bukkit.getScheduler(); verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture()); task.getValue().run(); - verify(addon).log(eq("Generating rankings")); - verify(addon).log(eq("Generated rankings for bskyblock-world")); + verify(addon).log("Generating rankings"); + verify(addon).log("Generated rankings for bskyblock-world"); } diff --git a/src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java b/src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java index 4d02459..71070a4 100644 --- a/src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java +++ b/src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java @@ -164,7 +164,7 @@ public void testSetup() { @Test public void testCanExecuteWrongArgs() { assertFalse(atrc.canExecute(user, "delete", Collections.emptyList())); - verify(user).sendMessage(eq("commands.help.header"), eq(TextVariables.LABEL), eq("BSkyBlock")); + verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "BSkyBlock"); } /** @@ -174,7 +174,7 @@ public void testCanExecuteWrongArgs() { public void testCanExecuteUnknown() { when(pm.getUser(anyString())).thenReturn(null); assertFalse(atrc.canExecute(user, "delete", Collections.singletonList("tastybento"))); - verify(user).sendMessage(eq("general.errors.unknown-player"), eq(TextVariables.NAME), eq("tastybento")); + verify(user).sendMessage("general.errors.unknown-player", TextVariables.NAME, "tastybento"); } /** @@ -193,7 +193,7 @@ public void testExecuteUserStringListOfString() { testCanExecuteKnown(); assertTrue(atrc.execute(user, "delete", Collections.singletonList("tastybento"))); verify(manager).removeEntry(any(World.class), eq(uuid)); - verify(user).sendMessage(eq("general.success")); + verify(user).sendMessage("general.success"); } } From 713a409584fa2995f98807b6d63e277f596c2f61 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Feb 2023 11:31:49 -0800 Subject: [PATCH 057/106] Refactor placeholders (#279) * Update ReadMe * Fix Jacoco * Remove unused imports * Remove placeholders from main class Created a separate class for cleaner code and added a test class. --- README.md | 14 +- src/main/java/world/bentobox/level/Level.java | 132 +------- .../bentobox/level/PlaceholderManager.java | 176 +++++++++++ .../java/world/bentobox/level/LevelTest.java | 8 - .../level/PlaceholderManagerTest.java | 290 ++++++++++++++++++ 5 files changed, 478 insertions(+), 142 deletions(-) create mode 100644 src/main/java/world/bentobox/level/PlaceholderManager.java create mode 100644 src/test/java/world/bentobox/level/PlaceholderManagerTest.java diff --git a/README.md b/README.md index c746a33..be52184 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ # Level -[![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/Level)](https://ci.codemc.org/job/BentoBoxWorld/job/Level/) +[![Build Status](https://ci.codemc.org/buildStatus/icon?job=BentoBoxWorld/Level)](https://ci.codemc.org/job/BentoBoxWorld/job/Level/)[ +![Bugs](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Level&metric=bugs)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Level) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Level&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Level) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Level&metric=ncloc)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Level) -## Note: Java 16 and Minecraft 17, or higher are now required +## About -Add-on for BentoBox to calculate island levels for BSkyBlock and AcidIsland. This add-on will work +Add-on for BentoBox to calculate island levels for BentoBox game modes like BSkyBlock and AcidIsland. It counts blocks and assigns a value to them. +Players gain levels by accumulating points and can lose levels too if their points go down. This add-on will work for game modes listed in the config.yml. +Full documentation for Level can be found at [docs.bentobox.world](https://docs.bentobox.world/en/latest/addons/Level/). + +Official download releases are at [download.bentobox.world](download.bentobox.world). + ## How to use 1. Place the level addon jar in the addons folder of the BentoBox plugin diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 2cf79c3..78c2a5b 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -3,11 +3,8 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.World; @@ -22,7 +19,6 @@ import world.bentobox.bentobox.api.configuration.Config; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; import world.bentobox.level.calculators.Pipeliner; import world.bentobox.level.commands.AdminLevelCommand; @@ -37,9 +33,7 @@ import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.JoinLeaveListener; import world.bentobox.level.listeners.MigrationListener; -import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.objects.LevelsData; -import world.bentobox.level.objects.TopTenData; import world.bentobox.level.requests.LevelRequestHandler; import world.bentobox.level.requests.TopTenRequestHandler; import world.bentobox.visit.VisitAddon; @@ -121,7 +115,7 @@ public void onEnable() { .forEach(gm -> { log("Level hooking into " + gm.getDescription().getName()); registerCommands(gm); - registerPlaceholders(gm); + new PlaceholderManager(this).registerPlaceholders(gm); registeredGameModes.add(gm); }); // Register request handlers @@ -212,130 +206,6 @@ public static int compareVersions(String version1, String version2) { return comparisonResult; } - private void registerPlaceholders(GameModeAddon gm) { - if (getPlugin().getPlaceholdersManager() == null) return; - // Island Level - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_level", - user -> getManager().getIslandLevelString(gm.getOverWorld(), user.getUniqueId())); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_level_raw", - user -> String.valueOf(getManager().getIslandLevel(gm.getOverWorld(), user.getUniqueId()))); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_total_points", - user -> { - IslandLevels data = getManager().getLevelsData(this.getIslands().getIsland(gm.getOverWorld(), user)); - return data.getTotalPoints()+""; - }); - - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_points_to_next_level", - user -> getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId())); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_level_max", - user -> String.valueOf(getManager().getIslandMaxLevel(gm.getOverWorld(), user.getUniqueId()))); - - // Visited Island Level - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_visited_island_level", user -> getVisitedIslandLevel(gm, user)); - - // Register Top Ten Placeholders - for (int i = 1; i < 11; i++) { - final int rank = i; - // Name - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_top_name_" + i, u -> getRankName(gm.getOverWorld(), rank)); - // Island Name - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_top_island_name_" + i, u -> getRankIslandName(gm.getOverWorld(), rank)); - // Members - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_top_members_" + i, u -> getRankMembers(gm.getOverWorld(), rank)); - // Level - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank)); - } - - // Personal rank - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_rank_value", u -> getRankValue(gm.getOverWorld(), u)); - } - - String getRankName(World world, int rank) { - if (rank < 1) rank = 1; - if (rank > TEN) rank = TEN; - return getPlayers().getName(getManager().getTopTen(world, TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null)); - } - - String getRankIslandName(World world, int rank) { - if (rank < 1) rank = 1; - if (rank > TEN) rank = TEN; - UUID owner = getManager().getTopTen(world, TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null); - if (owner != null) { - Island island = getIslands().getIsland(world, owner); - if (island != null) { - return island.getName() == null ? "" : island.getName(); - } - } - return ""; - } - - String getRankMembers(World world, int rank) { - if (rank < 1) rank = 1; - if (rank > TEN) rank = TEN; - UUID owner = getManager().getTopTen(world, TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null); - if (owner != null) { - Island island = getIslands().getIsland(world, owner); - if (island != null) { - // Sort members by rank - return island.getMembers().entrySet().stream() - .filter(e -> e.getValue() >= RanksManager.MEMBER_RANK) - .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) - .map(Map.Entry::getKey) - .map(getPlayers()::getName) - .collect(Collectors.joining(",")); - } - } - return ""; - } - - String getRankLevel(World world, int rank) { - if (rank < 1) rank = 1; - if (rank > TEN) rank = TEN; - return getManager() - .formatLevel(getManager() - .getTopTen(world, TEN) - .values() - .stream() - .skip(rank - 1L) - .limit(1L) - .findFirst() - .orElse(null)); - } - - /** - * Return the rank of the player in a world - * @param world world - * @param user player - * @return rank where 1 is the top rank. - */ - String getRankValue(World world, User user) { - if (user == null) { - return ""; - } - // Get the island level for this user - long level = getManager().getIslandLevel(world, user.getUniqueId()); - return String.valueOf(getManager().getTopTenLists().getOrDefault(world, new TopTenData(world)).getTopTen().values().stream().filter(l -> l > level).count() + 1); - } - - String getVisitedIslandLevel(GameModeAddon gm, User user) { - if (user == null || !gm.inWorld(user.getLocation())) return ""; - return getIslands().getIslandAt(user.getLocation()) - .map(island -> getManager().getIslandLevelString(gm.getOverWorld(), island.getOwner())) - .orElse("0"); - } - - private void registerCommands(GameModeAddon gm) { gm.getAdminCommand().ifPresent(adminCommand -> { new AdminLevelCommand(this, adminCommand); diff --git a/src/main/java/world/bentobox/level/PlaceholderManager.java b/src/main/java/world/bentobox/level/PlaceholderManager.java new file mode 100644 index 0000000..5e02a2c --- /dev/null +++ b/src/main/java/world/bentobox/level/PlaceholderManager.java @@ -0,0 +1,176 @@ +package world.bentobox.level; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.bukkit.World; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.PlaceholdersManager; +import world.bentobox.bentobox.managers.RanksManager; +import world.bentobox.level.objects.IslandLevels; +import world.bentobox.level.objects.TopTenData; + +/** + * Handles Level placeholders + * @author tastybento + * + */ +public class PlaceholderManager { + + private final Level addon; + private final BentoBox plugin; + + public PlaceholderManager(Level addon) { + this.addon = addon; + this.plugin = addon.getPlugin(); + } + + protected void registerPlaceholders(GameModeAddon gm) { + if (plugin.getPlaceholdersManager() == null) return; + PlaceholdersManager bpm = plugin.getPlaceholdersManager(); + // Island Level + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_island_level", + user -> addon.getManager().getIslandLevelString(gm.getOverWorld(), user.getUniqueId())); + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_island_level_raw", + user -> String.valueOf(addon.getManager().getIslandLevel(gm.getOverWorld(), user.getUniqueId()))); + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_island_total_points", + user -> { + IslandLevels data = addon.getManager().getLevelsData(addon.getIslands().getIsland(gm.getOverWorld(), user)); + return data.getTotalPoints()+""; + }); + + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_points_to_next_level", + user -> addon.getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId())); + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_island_level_max", + user -> String.valueOf(addon.getManager().getIslandMaxLevel(gm.getOverWorld(), user.getUniqueId()))); + + // Visited Island Level + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_visited_island_level", user -> getVisitedIslandLevel(gm, user)); + + // Register Top Ten Placeholders + for (int i = 1; i < 11; i++) { + final int rank = i; + // Name + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_top_name_" + i, u -> getRankName(gm.getOverWorld(), rank)); + // Island Name + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_top_island_name_" + i, u -> getRankIslandName(gm.getOverWorld(), rank)); + // Members + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_top_members_" + i, u -> getRankMembers(gm.getOverWorld(), rank)); + // Level + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank)); + } + + // Personal rank + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_rank_value", u -> getRankValue(gm.getOverWorld(), u)); + } + + /** + * Get the name of the player who holds the rank in this world + * @param world world + * @param rank rank 1 to 10 + * @return rank name + */ + String getRankName(World world, int rank) { + if (rank < 1) rank = 1; + if (rank > Level.TEN) rank = Level.TEN; + return addon.getPlayers().getName(addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null)); + } + + /** + * Get the island name for this rank + * @param world world + * @param rank rank 1 to 10 + * @return name of island or nothing if there isn't one + */ + String getRankIslandName(World world, int rank) { + if (rank < 1) rank = 1; + if (rank > Level.TEN) rank = Level.TEN; + UUID owner = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null); + if (owner != null) { + Island island = addon.getIslands().getIsland(world, owner); + if (island != null) { + return island.getName() == null ? "" : island.getName(); + } + } + return ""; + } + + /** + * Gets a comma separated string of island member names + * @param world world + * @param rank rank to request + * @return comma separated string of island member names + */ + String getRankMembers(World world, int rank) { + if (rank < 1) rank = 1; + if (rank > Level.TEN) rank = Level.TEN; + UUID owner = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null); + if (owner != null) { + Island island = addon.getIslands().getIsland(world, owner); + if (island != null) { + // Sort members by rank + return island.getMembers().entrySet().stream() + .filter(e -> e.getValue() >= RanksManager.MEMBER_RANK) + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) + .map(Map.Entry::getKey) + .map(addon.getPlayers()::getName) + .collect(Collectors.joining(",")); + } + } + return ""; + } + + String getRankLevel(World world, int rank) { + if (rank < 1) rank = 1; + if (rank > Level.TEN) rank = Level.TEN; + return addon.getManager() + .formatLevel(addon.getManager() + .getTopTen(world, Level.TEN) + .values() + .stream() + .skip(rank - 1L) + .limit(1L) + .findFirst() + .orElse(null)); + } + + /** + * Return the rank of the player in a world + * @param world world + * @param user player + * @return rank where 1 is the top rank. + */ + private String getRankValue(World world, User user) { + if (user == null) { + return ""; + } + // Get the island level for this user + long level = addon.getManager().getIslandLevel(world, user.getUniqueId()); + return String.valueOf(addon.getManager().getTopTenLists().getOrDefault(world, new TopTenData(world)).getTopTen().values().stream().filter(l -> l > level).count() + 1); + } + + String getVisitedIslandLevel(GameModeAddon gm, User user) { + if (user == null || !gm.inWorld(user.getWorld())) return ""; + return addon.getIslands().getIslandAt(user.getLocation()) + .map(island -> addon.getManager().getIslandLevelString(gm.getOverWorld(), island.getOwner())) + .orElse("0"); + } + +} diff --git a/src/test/java/world/bentobox/level/LevelTest.java b/src/test/java/world/bentobox/level/LevelTest.java index c94d10c..656c865 100644 --- a/src/test/java/world/bentobox/level/LevelTest.java +++ b/src/test/java/world/bentobox/level/LevelTest.java @@ -300,12 +300,4 @@ public void testGetSettings() { assertEquals(100, s.getLevelCost()); } - /** - * Test method for {@link world.bentobox.level.Level#getRankLevel(World, int)}. - */ - @Test - public void testRankLevel() { - addon.onEnable(); - assertEquals("",addon.getRankLevel(world, 1)); - } } diff --git a/src/test/java/world/bentobox/level/PlaceholderManagerTest.java b/src/test/java/world/bentobox/level/PlaceholderManagerTest.java new file mode 100644 index 0000000..b780b8e --- /dev/null +++ b/src/test/java/world/bentobox/level/PlaceholderManagerTest.java @@ -0,0 +1,290 @@ +package world.bentobox.level; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.bukkit.Location; +import org.bukkit.World; +import org.eclipse.jdt.annotation.NonNull; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.stubbing.Answer; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.AddonDescription; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.PlaceholdersManager; +import world.bentobox.bentobox.managers.PlayersManager; +import world.bentobox.bentobox.managers.RanksManager; +import world.bentobox.level.objects.IslandLevels; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({BentoBox.class}) +public class PlaceholderManagerTest { + + @Mock + private Level addon; + @Mock + private GameModeAddon gm; + @Mock + private BentoBox plugin; + + private PlaceholderManager pm; + @Mock + private PlaceholdersManager bpm; + @Mock + private LevelsManager lm; + @Mock + private World world; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private User user; + private Map names = new HashMap<>(); + private static final List NAMES = List.of("tasty", "bento", "fred", "bonne", "cyprien", "mael", "joe", "horacio", "steph", "vicky"); + private Map islands = new HashMap<>(); + private Map map = new HashMap<>(); + private @NonNull IslandLevels data; + @Mock + private PlayersManager players; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + when(addon.getPlugin()).thenReturn(plugin); + + // Users + when(addon.getPlayers()).thenReturn(players); + // Users + when(user.getWorld()).thenReturn(world); + when(user.getLocation()).thenReturn(mock(Location.class)); + + for (int i = 0; i < Level.TEN; i++) { + UUID uuid = UUID.randomUUID(); + names.put(uuid, NAMES.get(i)); + map.put(uuid, (long)(100 - i)); + Island is = new Island(); + is.setOwner(uuid); + is.setName(NAMES.get(i) + "'s island"); + islands.put(uuid, is); + + } + // Sort + map = map.entrySet().stream() + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) + .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + when(players.getName(any())).thenAnswer((Answer) invocation -> names.getOrDefault(invocation.getArgument(0, UUID.class), "unknown")); + Map members = new HashMap<>(); + map.forEach((uuid, l) -> members.put(uuid, RanksManager.MEMBER_RANK)); + islands.values().forEach(i -> i.setMembers(members)); + + + // Placeholders manager for plugin + when(plugin.getPlaceholdersManager()).thenReturn(bpm); + + // Game mode + AddonDescription desc = new AddonDescription.Builder("bentobox", "AOneBlock", "1.3").description("test").authors("tasty").build(); + when(gm.getDescription()).thenReturn(desc); + when(gm.getOverWorld()).thenReturn(world); + when(gm.inWorld(world)).thenReturn(true); + + // Islands + when(im.getIsland(any(World.class), any(User.class))).thenReturn(island); + when(im.getIslandAt(any(Location.class))).thenReturn(Optional.of(island)); + when(im.getIsland(any(World.class), any(UUID.class))).thenAnswer((Answer) invocation -> islands.get(invocation.getArgument(1, UUID.class))); + when(addon.getIslands()).thenReturn(im); + + // Levels Manager + when(lm.getIslandLevel(any(), any())).thenReturn(1234567L); + when(lm.getIslandLevelString(any(), any())).thenReturn("1234567"); + when(lm.getPointsToNextString(any(), any())).thenReturn("1234567"); + when(lm.getIslandMaxLevel(any(), any())).thenReturn(987654L); + when(lm.getTopTen(world, Level.TEN)).thenReturn(map); + when(lm.formatLevel(any())).thenAnswer((Answer) invocation -> invocation.getArgument(0, Long.class).toString()); + + data = new IslandLevels("uniqueId"); + data.setTotalPoints(12345678); + when(lm.getLevelsData(island)).thenReturn(data); + when(addon.getManager()).thenReturn(lm); + + pm = new PlaceholderManager(addon); + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#PlaceholderManager(world.bentobox.level.Level)}. + */ + @Test + public void testPlaceholderManager() { + verify(addon).getPlugin(); + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#registerPlaceholders(world.bentobox.bentobox.api.addons.GameModeAddon)}. + */ + @Test + public void testRegisterPlaceholders() { + pm.registerPlaceholders(gm); + // Island Level + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level"), any()); + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level_raw"), any()); + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_total_points"), any()); + + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_points_to_next_level"), any()); + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level_max"), any()); + + // Visited Island Level + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_visited_island_level"), any()); + + // Register Top Ten Placeholders + for (int i = 1; i < 11; i++) { + // Name + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_name_" + i), any()); + // Island Name + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_island_name_" + i), any()); + // Members + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_members_" + i), any()); + // Level + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_value_" + i), any()); + } + + // Personal rank + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_rank_value"), any()); + + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#getRankName(org.bukkit.World, int)}. + */ + @Test + public void testGetRankName() { + // Test extremes + assertEquals("tasty", pm.getRankName(world, 0)); + assertEquals("vicky", pm.getRankName(world, 100)); + // Test the ranks + int rank = 1; + for (String name : NAMES) { + assertEquals(name, pm.getRankName(world, rank++)); + } + + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#getRankIslandName(org.bukkit.World, int)}. + */ + @Test + public void testGetRankIslandName() { + // Test extremes + assertEquals("tasty's island", pm.getRankIslandName(world, 0)); + assertEquals("vicky's island", pm.getRankIslandName(world, 100)); + // Test the ranks + int rank = 1; + for (String name : NAMES) { + assertEquals(name + "'s island", pm.getRankIslandName(world, rank++)); + } + + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#getRankMembers(org.bukkit.World, int)}. + */ + @Test + public void testGetRankMembers() { + // Test extremes + check(1, pm.getRankMembers(world, 0)); + check(2, pm.getRankMembers(world, 100)); + // Test the ranks + for (int rank = 1; rank < 11; rank++) { + check(3, pm.getRankMembers(world, rank)); + } + } + + void check(int indicator, String list) { + for (String n : NAMES) { + assertTrue(n + " is missing for twst " + indicator, list.contains(n)); + } + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#getRankLevel(org.bukkit.World, int)}. + */ + @Test + public void testGetRankLevel() { + // Test extremes + assertEquals("100", pm.getRankLevel(world, 0)); + assertEquals("91", pm.getRankLevel(world, 100)); + // Test the ranks + for (int rank = 1; rank < 11; rank++) { + assertEquals(String.valueOf(101 - rank), pm.getRankLevel(world, rank)); + } + + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. + */ + @Test + public void testGetVisitedIslandLevelNullUser() { + assertEquals("", pm.getVisitedIslandLevel(gm, null)); + + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. + */ + @Test + public void testGetVisitedIslandLevelUserNotInWorld() { + // Another world + when(user.getWorld()).thenReturn(mock(World.class)); + assertEquals("", pm.getVisitedIslandLevel(gm, user)); + + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. + */ + @Test + public void testGetVisitedIslandLevel() { + assertEquals("1234567", pm.getVisitedIslandLevel(gm, user)); + + } + + /** + * Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. + */ + @Test + public void testGetVisitedIslandLevelNoIsland() { + when(im.getIslandAt(any(Location.class))).thenReturn(Optional.empty()); + assertEquals("0", pm.getVisitedIslandLevel(gm, user)); + + } + +} From c093796a6eea80ffa3d7d68fc641c65ed27f7733 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 25 Mar 2023 09:53:41 -0700 Subject: [PATCH 058/106] Remove dependency --- src/main/java/world/bentobox/level/LevelPladdon.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/level/LevelPladdon.java b/src/main/java/world/bentobox/level/LevelPladdon.java index 2e8032e..f320761 100644 --- a/src/main/java/world/bentobox/level/LevelPladdon.java +++ b/src/main/java/world/bentobox/level/LevelPladdon.java @@ -1,6 +1,5 @@ package world.bentobox.level; -import org.bukkit.plugin.java.annotation.dependency.Dependency; import org.bukkit.plugin.java.annotation.plugin.ApiVersion; import org.bukkit.plugin.java.annotation.plugin.Plugin; @@ -12,9 +11,8 @@ * @author tastybento * */ -@Plugin(name="Pladdon", version="1.0") +@Plugin(name="Level", version="1.0") @ApiVersion(ApiVersion.Target.v1_16) -@Dependency(value = "BentoBox") public class LevelPladdon extends Pladdon { @Override public Addon getAddon() { From 9c42a8d0073f862eb273145bcf3a518b0b1ae3f3 Mon Sep 17 00:00:00 2001 From: ceze88 Date: Wed, 5 Apr 2023 13:14:16 +0200 Subject: [PATCH 059/106] Add UltimateStacker hook for stacked blocks (#281) --- pom.xml | 11 ++++++++++ src/main/java/world/bentobox/level/Level.java | 14 ++++++++++++ .../calculators/IslandLevelCalculator.java | 22 +++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/pom.xml b/pom.xml index abde9c7..8c8f5a2 100644 --- a/pom.xml +++ b/pom.xml @@ -149,6 +149,11 @@ rosewood-repo https://repo.rosewooddev.io/repository/public/ + + + songoda-public + https://repo.songoda.com/repository/public/ + @@ -234,6 +239,12 @@ 1.3.0 provided + + com.songoda + UltimateStacker + 2.3.3 + provided + diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 78c2a5b..61789e5 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -58,6 +58,7 @@ public class Level extends Addon { private boolean stackersEnabled; private boolean advChestEnabled; private boolean roseStackersEnabled; + private boolean ultimateStackerEnabled; private final List registeredGameModes = new ArrayList<>(); /** @@ -146,6 +147,12 @@ public void onEnable() { if (roseStackersEnabled) { log("Hooked into RoseStackers."); } + + // Check if UltimateStacker is enabled + ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker"); + if (ultimateStackerEnabled) { + log("Hooked into UltimateStacker."); + } } @Override @@ -403,6 +410,13 @@ public boolean isRoseStackersEnabled() { return roseStackersEnabled; } + /** + * @return the ultimateStackerEnabled + */ + public boolean isUltimateStackerEnabled() { + return ultimateStackerEnabled; + } + /** * Method Level#getVisitHook returns the visitHook of this object. * diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 9d36dc8..9c2a6cf 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -16,6 +16,9 @@ import java.util.concurrent.CompletableFuture; 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.Chunk; import org.bukkit.ChunkSnapshot; @@ -524,6 +527,25 @@ private void scanAsync(ChunkPair cp) { 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); + + 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); From 78c67b6d2ffa26852f3319f8f4e16f6349d34a48 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sat, 8 Apr 2023 18:22:41 +0300 Subject: [PATCH 060/106] Create plugin.yml (#282) * Create plugin.yml The annotations do not provide the option to define the version dynamically from maven. This should fix that. * Remove Spigot Plugin Annotations * Remove plugin-annotation repo --- pom.xml | 5 ----- src/main/java/world/bentobox/level/LevelPladdon.java | 5 ----- src/main/resources/plugin.yml | 9 +++++++++ 3 files changed, 9 insertions(+), 10 deletions(-) create mode 100644 src/main/resources/plugin.yml diff --git a/pom.xml b/pom.xml index 8c8f5a2..4ca2e0f 100644 --- a/pom.xml +++ b/pom.xml @@ -164,11 +164,6 @@ ${spigot.version} provided - - org.spigotmc - plugin-annotations - 1.2.3-SNAPSHOT - org.mockito diff --git a/src/main/java/world/bentobox/level/LevelPladdon.java b/src/main/java/world/bentobox/level/LevelPladdon.java index f320761..356833c 100644 --- a/src/main/java/world/bentobox/level/LevelPladdon.java +++ b/src/main/java/world/bentobox/level/LevelPladdon.java @@ -1,8 +1,5 @@ package world.bentobox.level; -import org.bukkit.plugin.java.annotation.plugin.ApiVersion; -import org.bukkit.plugin.java.annotation.plugin.Plugin; - import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Pladdon; @@ -11,8 +8,6 @@ * @author tastybento * */ -@Plugin(name="Level", version="1.0") -@ApiVersion(ApiVersion.Target.v1_16) public class LevelPladdon extends Pladdon { @Override public Addon getAddon() { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..42542f9 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,9 @@ +name: BentoBox-Level +main: world.bentobox.level.LevelPladdon +version: ${project.version}${build.number} +api-version: "1.19" + +authors: [tastybento] +contributors: ["The BentoBoxWorld Community"] +website: https://bentobox.world +description: ${project.description} From 3c79c68e80fdd9a1b23a4c3102f09105ca77ec78 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 15 Apr 2023 11:38:19 -0700 Subject: [PATCH 061/106] Updated dependencies --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4ca2e0f..599fd60 100644 --- a/pom.xml +++ b/pom.xml @@ -58,8 +58,8 @@ 2.0.9 - 1.19.2-R0.1-SNAPSHOT - 1.20.0 + 1.19.4-R0.1-SNAPSHOT + 1.23.0 1.12.0 From ad3d02c29a6a6ce9e71c4843f331e725024d2cd1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 15 Apr 2023 16:45:58 -0700 Subject: [PATCH 062/106] Version 2.10.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 599fd60..ba1242c 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ -LOCAL - 2.10.0 + 2.10.1 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From 5a66bf162bce14789dd6edd0949601942504a5d1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 17 May 2023 15:30:38 -0700 Subject: [PATCH 063/106] Add blocks that should be zero by default as they are available on the ocean floor. https://github.com/BentoBoxWorld/Level/issues/284 --- src/main/resources/blockconfig.yml | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/main/resources/blockconfig.yml b/src/main/resources/blockconfig.yml index 47e33da..d1580ac 100644 --- a/src/main/resources/blockconfig.yml +++ b/src/main/resources/blockconfig.yml @@ -778,6 +778,58 @@ worlds: ANDESITE: 0 DIORITE: 0 acidisland_world: + BRAIN_CORAL: 0 + BRAIN_CORAL_BLOCK: 0 + BRAIN_CORAL_FAN: 0 + BRAIN_CORAL_WALL_FAN: 0 + BUBBLE_CORAL: 0 + BUBBLE_CORAL_BLOCK: 0 + BUBBLE_CORAL_FAN: 0 + BUBBLE_CORAL_WALL_FAN: 0 + DEAD_BRAIN_CORAL: 0 + DEAD_BRAIN_CORAL_BLOCK: 0 + DEAD_BRAIN_CORAL_FAN: 0 + DEAD_BRAIN_CORAL_WALL_FAN: 0 + DEAD_BUBBLE_CORAL: 0 + DEAD_BUBBLE_CORAL_BLOCK: 0 + DEAD_BUBBLE_CORAL_FAN: 0 + DEAD_BUBBLE_CORAL_WALL_FAN: 0 + FIRE_CORAL: 0 + FIRE_CORAL_BLOCK: 0 + FIRE_CORAL_FAN: 0 + FIRE_CORAL_WALL_FAN: 0 + DEAD_FIRE_CORAL: 0 + DEAD_FIRE_CORAL_BLOCK: 0 + DEAD_FIRE_CORAL_FAN: 0 + DEAD_FIRE_CORAL_WALL_FAN: 0 + HORN_CORAL: 0 + HORN_CORAL_BLOCK: 0 + HORN_CORAL_FAN: 0 + HORN_CORAL_WALL_FAN: 0 + DEAD_HORN_CORAL: 0 + DEAD_HORN_CORAL_BLOCK: 0 + DEAD_HORN_CORAL_FAN: 0 + DEAD_HORN_CORAL_WALL_FAN: 0 + TUBE_CORAL: 0 + TUBE_CORAL_BLOCK: 0 + TUBE_CORAL_FAN: 0 + TUBE_CORAL_WALL_FAN: 0 + DEAD_TUBE_CORAL: 0 + DEAD_TUBE_CORAL_BLOCK: 0 + DEAD_TUBE_CORAL_FAN: 0 + DEAD_TUBE_CORAL_WALL_FAN: 0 SAND: 0 SANDSTONE: 0 + RED_SAND: 0 ICE: 0 + AMETHYST_CLUSTER: 0 + AMETHYST_BLOCK: 0 + LARGE_AMETHYST_BUD: 0 + MEDIUM_AMETHYST_BUD: 0 + SMALL_AMETHYST_BUD: 0 + BUDDING_AMETHYST: 0 + SEA_PICKLE: 0 + CALCITE: 0 + TALL_SEAGRASS: 0 + SEAGRASS: 0 + SMOOTH_BASALT: 0 From dfd30d148b1a73f8c5197b0e243d66bfc57b5635 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 09:46:22 -0700 Subject: [PATCH 064/106] Chinese (#288) * Translate zh-CN.yml via GitLocalize * Translate zh-CN.yml via GitLocalize --------- Co-authored-by: Jeansou Co-authored-by: dawnTak --- src/main/resources/locales/zh-CN.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index c268f5b..d015525 100755 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -5,7 +5,7 @@ admin: description: 计算指定玩家的岛屿等级 sethandicap: parameters: " " - description: 设置偏差值,通常用于调整新建的初始岛屿等级为零。实际岛屿等级 - = 计算的岛屿等级 + description: 设置偏差值,通常用于抵消初始岛屿等级,来保证岛屿等级从零开始。实际岛屿等级 - = 最终的岛屿等级 changed: "&a 岛屿的偏差值从 [number] 更改为 [new_number]" invalid-level: "&c 偏差值无效,请使用整数" levelstatus: @@ -90,7 +90,7 @@ level: name: "&f&l 所有方块" description: "&7 显示岛屿上所有的方块" above_sea_level: - name: "&f&l 方块在海平面以上的价值" + name: "&f&l 海平面以上的方块" description: |- &7 只显示所有 &7 海平面以上的方块 @@ -122,7 +122,7 @@ level: [id] id: "&7 方块ID:&e [id]" value: "&7 方块价值:&e [number]" - underwater: "&7 方块海平面下价值:&e [number]" + underwater: "&7 海平面以下方块的价值:&e [number]" limit: "&7 方块限制:&e [number]" previous: name: "&f&l 上一页" From f8d50a43d69319931edd28cbdb8eac52b3c6db71 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 09:46:35 -0700 Subject: [PATCH 065/106] Translate id.yml via GitLocalize (#287) Co-authored-by: Dusty --- src/main/resources/locales/id.yml | 162 +++++++++++++++++++++++++----- 1 file changed, 139 insertions(+), 23 deletions(-) diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index 4347616..9492265 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -2,54 +2,170 @@ admin: level: parameters: "" - description: hitung level pulau untuk player + description: hitung level pulau untuk pemain sethandicap: parameters: " " - description: mengatur handicap pulau, biasanya tingkat pulau pemula - changed: "& Handicap pulau awal diubah dari [number] menjadi [new_number]." - invalid-level: "& c Handicap tidak valid. Gunakan angka bulat." + description: mengatur handicap pulau, biasanya level pulau pemula + changed: "&a Handicap pulau awal diubah dari [number] menjadi [new_number]." + invalid-level: "&c Handicap tidak valid. Gunakan angka bulat." levelstatus: - description: menunjukkan berapa pulau yang menunggu pindaian - islands-in-queue: "&a Pulau di dalam menunggu: [number]" + description: menunjukkan berapa banyak pulau dalam antrian untuk pemindaian + islands-in-queue: "&a Pulau di dalam antrian: [number]" top: description: menunjukkan daftar sepuluh besar - unknown-world: "&c World tidak ditemukan!" + unknown-world: "&c Dunia tidak ditemukan!" display: "&f[rank]. &a[name] &7- &b[level]" remove: - description: menghilangkan player dari sepuluh besar + description: menghapus pemain dari sepuluh besar parameters: "" island: level: parameters: "[player]" - description: hitung level pulau Anda atau tunjukkan level [player] + description: hitung level pulau kamu atau melihat level [player] calculating: "&a Menghitung level..." - estimated-wait: "&a Waktu tunggu perkiraan: [number] detik" - in-queue: "&aAnda berada pada posisi [number] pada urutan menunggu" + estimated-wait: "&a Perkiraan menunggu: [number] detik" + in-queue: "&a Kamu berada pada antrian nomor [number]" island-level-is: "&a Level pulau adalah &b[level]" required-points-to-next-level: "&a [points] poin dibutuhkan hingga level selanjutnya" deaths: "&c([number] kematian)" - cooldown: "&c Anda harus menunggu &b[time] &c detik sebelum Anda dapat melakukannya + cooldown: "&c Kamu harus menunggu &b[time] &c detik sebelum kamu dapat melakukannya lagi" in-progress: "&6 Perhitungan level pulau sedang dijalankan..." time-out: "&c Perhitungan level pulau terlalu lama. Coba lagi nanti." top: - description: menunjukkan sepuluh besar + description: menunjukkan Sepuluh Besar gui-title: "&a Sepuluh Besar" gui-heading: "&6[name]: &B[rank]" island-level: "&b Level [level]" - warp-to: "&A Warping ke pulau milik [name]" + warp-to: "&A Warp ke pulau [name]" level-details: above-sea-level-blocks: Blok di atas permukaan laut spawners: Spawner underwater-blocks: Blok di bawah permukaan laut all-blocks: Semua blok - no-island: "&c Tidak terdapat pulau!" - names-island: Pulau milik [name] + no-island: "&c Tidak ada pulau!" + names-island: Pulau [name] syntax: "[name] x [number]" - hint: "& c Jalankan perintah level untuk melihat laporan blok" - value: - description: menunjukkan nilai dari apapun blok - success: "&7 Nilai blok ini adalah: &e[value]" - success-underwater: "&7 Nilai blok ini di bawah permukaan laut adalah: &e[value]" - empty-hand: "&c Tidak ada balok di tangan Anda" - no-value: "&c Benda itu tidak bernilai." + hint: "&c Jalankan perintah level untuk melihat laporan blok" +level: + commands: + value: + parameters: "[hand|]" + description: menunjukkan nilai blok. Tambah 'hand' di akhir untuk menjukkan + nilai item di tangan. + gui: + titles: + top: "&0&l Pulau Terbaik" + detail-panel: "&0&l Pulau [name]" + value-panel: "&0&l Nilai Blok" + buttons: + island: + empty: "&f&l [name]. place" + name: "&f&l [name]" + description: |- + [owner] + [members] + [place] + [level] + owners-island: Pulau [player] + owner: "&7&l Pemilik: &r&b [player]" + members-title: "&7&l Anggota:" + member: "&b - [player]" + unknown: tidak diketahui + place: "&r&7Peringkat &7&o [number]." + level: "&7 Level: &o [number]" + material: + name: "&f&l [number] x [material]" + description: |- + [description] + [count] + [value] + [calculated] + [limit] + [id] + id: "&7 Id blok: &e [id]" + value: "&7 Nilai blok: &e [number]" + limit: "&7 Batas blok: &e [number]" + count: "&7 Jumlah blok: &e [number]" + calculated: "&7 Nilai yang dihitung: &e [number]" + all_blocks: + name: "&f&l Semua blok" + description: |- + &7 Tampilkan semua blok + &7 di pulau. + above_sea_level: + name: "&f&l Blok Diatas Permukaan Laut" + description: |- + &7 Hanya mengampilkan blok + &7 yang berada di atas + &7 permukaan laut. + underwater: + name: "&f&l Blok Di bawah Permukaan Laut" + description: |- + &7 Hanya menampilkan blok + &7 yang berada di bawah + &7 permukaan laut. + spawner: + name: "&f&l Spawner" + description: "&7 Hanya tampilkan spawner." + filters: + name: + name: "&f&l Urut berdasarkan Nama" + description: "&7 Mengurutkan semua blok berdasarkan nama." + value: + name: "&f&l Urut berdasarkan Nilai" + description: "&7 Mengurutkan semua blok berdasarkan nilainya." + count: + name: "&f&l Urut berdasarkan Jumlah" + description: "&7 Mengurutkan semua blok berdasarkan jumlahnya." + value: + name: "&f&l [material]" + description: |- + [description] + [value] + [underwater] + [limit] + [id] + id: "&7 Id blok: &e [id]" + value: "&7 Nilai blok: &e [number]" + underwater: "&7 Dibawah permukaan laut: &e [number]" + limit: "&7 Batas block: &e [number]" + previous: + name: "&f&l Halaman sebelumnya" + description: "&7 Beralih ke halaman [number]" + next: + name: "&f&l Halaman selanjutnya" + description: "&7 Beralih ke halaman [number]" + search: + name: "&f&l Cari" + description: |- + &7 Mencari nilai yang + &7 spesifik. + search: "&b Nilai: [value]" + tips: + click-to-view: "&e Klik &7 untuk melihat." + click-to-previous: "&e Klik &7 untuk melihat halaman sebelumnya." + click-to-next: "&e Klik &7 untuk melihat halaman selanjutnya." + click-to-select: "&e Klik &7 untuk memilih." + left-click-to-cycle-up: "&e Klik Kiri &7 untuk memutar ke atas." + right-click-to-cycle-down: "&e Klik Kanan &7 memutar ke bawah." + left-click-to-change: "&e Klik Kiri &7 untuk mengubah." + right-click-to-clear: "&e Klik Kanan &7 untuk membersihkan." + click-to-asc: "&e Klik &7 untuk mengurutkan dalam urutan menaik." + click-to-desc: "&e Klik &7 untuk mengurutkan dalam urutan menurun." + click-to-warp: "&e Klik &7 untuk warp." + click-to-visit: "&e Klik &7 untuk mengunjungi." + right-click-to-visit: "&e Klik Kanan &7 untuk mengunjungi." + conversations: + prefix: "&l&6 [BentoBox]: &r" + no-data: "&c Jalankan perintah level untuk melihat laporan blok" + cancel-string: batal + exit-string: batal, keluar, berhenti + write-search: "&e Tolong masukkan pencarian nilai. (Ketik 'batal' untuk keluar)" + search-updated: "&a Nilai pencarian diperbarui." + cancelled: "&c Percakapan dibatalkan!" + no-value: "&c Item itu tidak ada nilai." + unknown-item: "&c '[material]' tidak ada di dalam permainan." + value: "&7 Nilai dari '[material]' adalah: &e[value]" + value-underwater: "&7Nilai dari '[material]' di bawah permukaan laut: &e[value]" + empty-hand: "&c Tidak ada blok di tangan mu" From 30217ba509a78ec824e2ca75151561f5d37aa730 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 09:46:48 -0700 Subject: [PATCH 066/106] French (#286) * Translate fr.yml via GitLocalize * Translate fr.yml via GitLocalize * Translate fr.yml via GitLocalize --------- Co-authored-by: gitlocalize-app[bot] <55277160+gitlocalize-app[bot]@users.noreply.github.com> Co-authored-by: organizatsiya Co-authored-by: Florian CUNY From 951b0f5549522576d25381d3ab695d93efcc1c42 Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 09:47:02 -0700 Subject: [PATCH 067/106] Spanish (#285) * Translate es.yml via GitLocalize * Translate es.yml via GitLocalize --------- Co-authored-by: ChrissTM03 Co-authored-by: Espan --- src/main/resources/locales/es.yml | 125 ++++++++++++++++-------------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index 7f8616b..e6a9bd2 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -1,94 +1,79 @@ -########################################################################################################### -# Este es un archivo YML. Tenga cuidado al editar. Revisa tus ediciones en un verificador de YAML como # -# el de http://yaml-online-parser.appspot.com # -########################################################################################################### - +--- admin: level: parameters: "" - description: "Calcula el nivel de la isla del jugador" + description: Calcula el nivel de la isla del jugador sethandicap: - parameters: - description: "Define la desventaja de la isla, usualmente el nivel inicial para nuevas islas" + parameters: " " + description: Define la desventaja de la isla, usualmente el nivel inicial para + nuevas islas changed: "&aDesventaja inicial de la isla cambiado de [number] a [new_number]." invalid-level: "&cNúmero no válido. Usa un número entero." levelstatus: - description: "Muestra cuantas islas hay en la cola para escanear" + description: Muestra cuantas islas hay en la cola para escanear islands-in-queue: "&aIslas en cola: [number]" top: - description: "Muestra la lista de las diez primeras islas" + description: Muestra la lista de las diez primeras islas unknown-world: "&c¡Mundo desconocido!" display: "&f[rank]. &a[name] &7- &b[level]" remove: - description: "Elimina a un jugador de los diez primeros" - parameters: "" - - + description: Elimina a un jugador de los diez primeros + parameters: "" island: - level: + level: parameters: "[player]" - description: "Calcula tu nivel de isla o muestra el nivel de [player]" + description: Calcula tu nivel de isla o muestra el nivel de [player] calculating: "&aCalculando nivel..." estimated-wait: "&aEspera estimada: [number] segundos" in-queue: "&aEstás en el puesto [number] de la cola" island-level-is: "&aNivel de isla es de &b[level]" - required-points-to-next-level: "&a[points] Puntos requeridos hasta el siguiente nivel." + required-points-to-next-level: "&a[points] Puntos requeridos hasta el siguiente + nivel." deaths: "&c([number] Muertes)" cooldown: "&cDebes esperar &b[time] &csegundos para poder volver a hacer esto." in-progress: "&6El Calculo del nivel de la islas está en progreso..." time-out: "&cEl calculo del nivel de la isla está tardando. Intente más tarde." - top: - description: "Muestra el top de islas" + description: Muestra el top de islas gui-title: "&aTop diez" gui-heading: "&6[name]: &b[rank]" island-level: "&bNivel [level]" warp-to: "&aLlevándote a la isla de [name]" - level-details: - above-sea-level-blocks: "Bloques sobre el nivel del mar" - spawners: "Spawners" - underwater-blocks: "Bloques debajo del nivel del mar" - all-blocks: "Todos los bloques" + above-sea-level-blocks: Bloques sobre el nivel del mar + spawners: Spawners + underwater-blocks: Bloques debajo del nivel del mar + all-blocks: Todos los bloques no-island: "&c¡Sin isla!" - names-island: "Isla de [name]" + names-island: Isla de [name] syntax: "[name] x [number]" hint: "&cEscriba /level para ver el recuento de bloques" - - value: - description: "Muestra el valor de un bloque en la mano" - success: "&7El valor del este bloque es: &e[value]" - success-underwater: "&7El valor de este bloque debajo del nivel del mar es: &e[value]" - empty-hand: "&cNo hay bloques en tu mano." - no-value: "&cEste objeto no tiene valor." - level: + commands: + value: + parameters: "[hand|]" + description: muestra el valor de los bloques. Añade 'hand' al final para mostrar + el valor del bloque de la mano. gui: titles: top: "&0&lTop de islas" detail-panel: "&0&lIsla de [name]" + value-panel: "&0&l Valores de los Bloques" buttons: island: - empty: '&f&l[name]. lugar' - name: '&f&l[name]' + empty: "&f&l[name]. lugar" + name: "&f&l[name]" description: |- [owner] [members] [place] [level] - # Text that is replacing [name] if island do not have a name - owners-island: "Isla de [player]" - # Text for [owner] in description. + owners-island: Isla de [player] owner: "&7&l Dueño: &r&b[player]" - # Title before listing members for [members] in description members-title: "&7&l Miembros:" - # List each member under the title for [members] in description member: "&b - [player]" - # Name of unknown player. unknown: " desconocido" - # Section for parsing [place] place: "&7&o [number]. &r&7lugar" - # Section for parsing [level] level: "&7 Nivel: &o[number]" material: name: "&f&l[number] x [material]" @@ -123,31 +108,41 @@ level: &7 nivel del mar. spawner: name: "&f&lSpawners" - description: |- - &7Mostrar solo spawners. + description: "&7Mostrar solo spawners." filters: name: name: "&f&lOrdenar por nombre" - description: |- - &7Ordenar todos los bloques por nombre. + description: "&7Ordenar todos los bloques por nombre." value: name: "&f&lOrdenar por valor" - description: |- - &7Ordenar todos los bloques por valor. + description: "&7Ordenar todos los bloques por valor." count: name: "&f&lOrdenar por cantidad" - description: |- - &7Ordenar todos los bloques por cantidad. - # Button that is used in multi-page GUIs which allows to return to previous page. + description: "&7Ordenar todos los bloques por cantidad." + value: + name: "&f&l [material]" + description: |- + [description] + [value] + [underwater] + [limit] + [id] + id: "&7 ID de Bloque: &e [id]" + value: "&7 Valor del Bloque: &e [number]" + underwater: "&7 Por debajo del nivel del mar: &e [number]" + limit: "&7 Límite de bloque: &e [number]" previous: name: "&f&lPágina anterior" - description: |- - &7Cambiar a la página [number] - # Button that is used in multi-page GUIs which allows to go to next page. + description: "&7Cambiar a la página [number]" next: name: "&f&lSiguiente página" + description: "&7Cambiar a la página [number]" + search: + name: "&f&l Buscar" description: |- - &7Cambiar a la página [number] + &7 Buscar un determinado + &7 valor. + search: "&b Valor: [value]" tips: click-to-view: "&eClic &7para ver." click-to-previous: "&eClic &7 para ir a la página anterior." @@ -155,7 +150,23 @@ level: click-to-select: "&eClic &7 para seleccionar." left-click-to-cycle-up: "&eClic izquierdo &7para ir hacia arriba." right-click-to-cycle-down: "&eClic derecho &7para ir hacia abajo." + left-click-to-change: "&e Clic Izquierdo &7 para editar." + right-click-to-clear: "&e Clic Derecho &7 para borrar." + click-to-asc: "&e Clic &7 para ordenar de forma creciente." + click-to-desc: "&e Clic &7 para ordenar de forma decreciente." + click-to-warp: "&e Clic &7 para teletransportarse." + click-to-visit: "&e Clic &7 para visitar." + right-click-to-visit: "&e Clic Derecho &7 para visitar." conversations: - # Prefix for messages that are send from server. prefix: "&l&6[BentoBox]: &r" no-data: "&cEscriba /level para ver el recuento de bloques." + cancel-string: cancelar + exit-string: cancelar, salir, abandonar + write-search: "&e Introduce un valor de búsqueda. (Escribe 'cancel' para salir)" + search-updated: "&a Valor de búsqueda actualizado." + cancelled: "&c ¡Conversación cancelada!" + no-value: "&c Ese ítem no tiene valor." + unknown-item: "&c El '[material]' no existe en el juego." + value: "&7 El valor de '[material]' es: &e[value]" + value-underwater: "&7 El valor de '[material]' por debajo del nivel del mar: &e[value]" + empty-hand: "&c No hay bloques en tu mano" From 1b20605791f90147fd1c9da8515d08359208edf4 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 3 Jun 2023 09:15:32 -0700 Subject: [PATCH 068/106] Version 2.11.0 --- pom.xml | 30 +++++++++---------- src/main/java/world/bentobox/level/Level.java | 6 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index ba1242c..d204ec7 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ -LOCAL - 2.10.1 + 2.11.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io @@ -127,18 +127,6 @@ - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots - - - codemc-repo - https://repo.codemc.org/repository/maven-public/ - - - codemc-public - https://repo.codemc.org/repository/maven-public/ - jitpack.io @@ -154,6 +142,18 @@ songoda-public https://repo.songoda.com/repository/public/ + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots + + + codemc-repo + https://repo.codemc.org/repository/maven-public/ + + + codemc-public + https://repo.codemc.org/repository/maven-public/ + @@ -331,10 +331,10 @@ maven-javadoc-plugin 3.0.1 + none false - -Xdoclint:none ${java.home}/bin/javadoc - 16 + 17 diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 61789e5..0d02802 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -190,9 +190,9 @@ private void hookExtensions() /** * Compares versions - * @param version1 - * @param version2 - * @return <0 if version 1 is older than version 2, =0 if the same, >0 if version 1 is newer than version 2 + * @param version1 version 1 + * @param version2 version 2 + * @return {@code <0 if version 1 is older than version 2, =0 if the same, >0 if version 1 is newer than version 2} */ public static int compareVersions(String version1, String version2) { int comparisonResult = 0; From deb09992f10f18477f69cf6369c19cfbf08392b1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 24 Jun 2023 13:45:07 -0700 Subject: [PATCH 069/106] Update Github Build script --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c771fd5..ee925e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,21 +11,21 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Set up JDK 17 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: java-version: 17 - name: Cache SonarCloud packages - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache Maven packages - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} From 1b02f112207aad911fdc89678f9d05f623c62eca Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 24 Jun 2023 13:56:20 -0700 Subject: [PATCH 070/106] Added distribution required for Github Action --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ee925e9..825b18d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,7 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v3 with: + distribution: 'adopt' java-version: 17 - name: Cache SonarCloud packages uses: actions/cache@v3 From a3537f5b8019c8c74486654d4155d19eb76a1b1d Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 10 Jul 2023 21:23:33 -0700 Subject: [PATCH 071/106] Update Jacoco --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d204ec7..8eb8847 100644 --- a/pom.xml +++ b/pom.xml @@ -403,7 +403,7 @@ org.jacoco jacoco-maven-plugin - 0.8.7 + 0.8.11 true From 2b0a6d82ef161bb88537b471bec4627cea8f65bb Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 10 Jul 2023 21:44:22 -0700 Subject: [PATCH 072/106] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d204ec7..5dab9e6 100644 --- a/pom.xml +++ b/pom.xml @@ -403,7 +403,7 @@ org.jacoco jacoco-maven-plugin - 0.8.7 + 0.8.10 true From 913eed9c77a4d439fc85f18cd0bc35755231391a Mon Sep 17 00:00:00 2001 From: PapiCapi <49530141+PapiCapi@users.noreply.github.com> Date: Sat, 7 Oct 2023 01:14:45 +0200 Subject: [PATCH 073/106] Add config option to disable plugin hooks (#291) * Update UltimateStacker dependency * Add config option to disable plugin hooks --- pom.xml | 2 +- src/main/java/world/bentobox/level/Level.java | 46 +++++++++++-------- .../bentobox/level/config/ConfigSettings.java | 15 ++++++ 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index 5dab9e6..d511296 100644 --- a/pom.xml +++ b/pom.xml @@ -237,7 +237,7 @@ com.songoda UltimateStacker - 2.3.3 + 2.4.0 provided diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 0d02802..1639b05 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -126,32 +126,42 @@ public void onEnable() { // Check if WildStackers is enabled on the server // I only added support for counting blocks into the island level // Someone else can PR if they want spawners added to the Leveling system :) - stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker"); - if (stackersEnabled) { - log("Hooked into WildStackers."); + if ( !settings.getDisabledPluginHooks().contains("WildStacker") ) { + stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker"); + if (stackersEnabled) { + log("Hooked into WildStackers."); + } } + // Check if AdvancedChests is enabled on the server - Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests"); - advChestEnabled = advChest != null; - if (advChestEnabled) { - // Check version - if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) { - log("Hooked into AdvancedChests."); - } else { - logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + " - requires version 23.0 or later"); - advChestEnabled = false; + if ( !settings.getDisabledPluginHooks().contains("AdvancedChests") ) { + Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests"); + advChestEnabled = advChest != null; + if (advChestEnabled) { + // Check version + if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) { + log("Hooked into AdvancedChests."); + } else { + logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + " - requires version 23.0 or later"); + advChestEnabled = false; + } } } + // Check if RoseStackers is enabled - roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker"); - if (roseStackersEnabled) { - log("Hooked into RoseStackers."); + if ( !settings.getDisabledPluginHooks().contains("RoseStacker") ) { + roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker"); + if (roseStackersEnabled) { + log("Hooked into RoseStackers."); + } } // Check if UltimateStacker is enabled - ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker"); - if (ultimateStackerEnabled) { - log("Hooked into UltimateStacker."); + if ( !settings.getDisabledPluginHooks().contains("UltimateStacker") ) { + ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker"); + if (ultimateStackerEnabled) { + log("Hooked into UltimateStacker."); + } } } diff --git a/src/main/java/world/bentobox/level/config/ConfigSettings.java b/src/main/java/world/bentobox/level/config/ConfigSettings.java index 7be3364..c287d2a 100644 --- a/src/main/java/world/bentobox/level/config/ConfigSettings.java +++ b/src/main/java/world/bentobox/level/config/ConfigSettings.java @@ -1,5 +1,6 @@ package world.bentobox.level.config; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -126,6 +127,12 @@ public class ConfigSettings implements ConfigObject { @ConfigEntry(path = "include-shulkers-in-chest") private boolean includeShulkersInChest = false; + @ConfigComment("") + @ConfigComment("Disables hooking with other plugins.") + @ConfigComment("Example: disabled-plugin-hooks: [UltimateStacker, RoseStacker]") + @ConfigEntry(path = "disabled-plugin-hooks") + private List disabledPluginHooks = new ArrayList<>(); + /** * @return the gameModes @@ -404,4 +411,12 @@ public boolean isIncludeShulkersInChest() { public void setIncludeShulkersInChest(boolean includeShulkersInChest) { this.includeShulkersInChest = includeShulkersInChest; } + + public List getDisabledPluginHooks() { + return disabledPluginHooks; + } + + public void setDisabledPluginHooks(List disabledPluginHooks) { + this.disabledPluginHooks = disabledPluginHooks; + } } From 8f567cc328c48c5b0ed7537b40cd6d2b0b91c5b7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 12 Nov 2023 09:42:43 -0800 Subject: [PATCH 074/106] Use 2.0.0 BentoBox API --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8eb8847..19d5023 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 2.0.9 1.19.4-R0.1-SNAPSHOT - 1.23.0 + 2.0.0-SNAPSHOT 1.12.0 From eb71b35c5c5ba05694c38b387121bc6efa222e46 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 18 Nov 2023 13:06:44 -0800 Subject: [PATCH 075/106] Version 2.12.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b3fefdc..0bab5c3 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ -LOCAL - 2.11.0 + 2.12.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From 3a3c8a320c905a75c2690745e819484f3482fd50 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 18 Nov 2023 14:41:18 -0800 Subject: [PATCH 076/106] Adds an admin stats command. See #293 --- src/main/java/world/bentobox/level/Level.java | 803 ++++++++-------- .../world/bentobox/level/LevelsManager.java | 858 +++++++++--------- .../level/commands/AdminStatsCommand.java | 94 ++ src/main/resources/locales/en-US.yml | 14 +- 4 files changed, 955 insertions(+), 814 deletions(-) create mode 100644 src/main/java/world/bentobox/level/commands/AdminStatsCommand.java diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 1639b05..f7a1734 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -24,6 +24,7 @@ import world.bentobox.level.commands.AdminLevelCommand; import world.bentobox.level.commands.AdminLevelStatusCommand; import world.bentobox.level.commands.AdminSetInitialLevelCommand; +import world.bentobox.level.commands.AdminStatsCommand; import world.bentobox.level.commands.AdminTopCommand; import world.bentobox.level.commands.IslandLevelCommand; import world.bentobox.level.commands.IslandTopCommand; @@ -39,411 +40,411 @@ import world.bentobox.visit.VisitAddon; import world.bentobox.warps.Warp; - /** * @author tastybento * */ public class Level extends Addon { - // The 10 in top ten - public static final int TEN = 10; - - // Settings - private ConfigSettings settings; - private Config configObject = new Config<>(this, ConfigSettings.class); - private BlockConfig blockConfig; - private Pipeliner pipeliner; - private LevelsManager manager; - private boolean stackersEnabled; - private boolean advChestEnabled; - private boolean roseStackersEnabled; - private boolean ultimateStackerEnabled; - private final List registeredGameModes = new ArrayList<>(); - - /** - * Local variable that stores if warpHook is present. - */ - private Warp warpHook; - - /** - * Local variable that stores if visitHook is present. - */ - private VisitAddon visitHook; - - - @Override - public void onLoad() { - // Save the default config from config.yml - saveDefaultConfig(); - if (loadSettings()) { - // Disable - logError("Level settings could not load! Addon disabled."); - setState(State.DISABLED); - } else { - configObject.saveConfigObject(settings); - } - - // Save existing panels. - this.saveResource("panels/top_panel.yml", false); - this.saveResource("panels/detail_panel.yml", false); - this.saveResource("panels/value_panel.yml", false); - } - - private boolean loadSettings() { - // Load settings again to get worlds - settings = configObject.loadConfigObject(); - - return settings == null; - } - - @Override - public void onEnable() { - loadBlockSettings(); - // Start pipeline - pipeliner = new Pipeliner(this); - // Start Manager - manager = new LevelsManager(this); - // Register listeners - this.registerListener(new IslandActivitiesListeners(this)); - this.registerListener(new JoinLeaveListener(this)); - this.registerListener(new MigrationListener(this)); - - // Register commands for GameModes - registeredGameModes.clear(); - getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) - .forEach(gm -> { - log("Level hooking into " + gm.getDescription().getName()); - registerCommands(gm); - new PlaceholderManager(this).registerPlaceholders(gm); - registeredGameModes.add(gm); - }); - // Register request handlers - registerRequestHandler(new LevelRequestHandler(this)); - registerRequestHandler(new TopTenRequestHandler(this)); - - // Check if WildStackers is enabled on the server - // I only added support for counting blocks into the island level - // Someone else can PR if they want spawners added to the Leveling system :) - if ( !settings.getDisabledPluginHooks().contains("WildStacker") ) { - stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker"); - if (stackersEnabled) { - log("Hooked into WildStackers."); - } - } - - // Check if AdvancedChests is enabled on the server - if ( !settings.getDisabledPluginHooks().contains("AdvancedChests") ) { - Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests"); - advChestEnabled = advChest != null; - if (advChestEnabled) { - // Check version - if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) { - log("Hooked into AdvancedChests."); - } else { - logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + " - requires version 23.0 or later"); - advChestEnabled = false; - } - } - } - - // Check if RoseStackers is enabled - if ( !settings.getDisabledPluginHooks().contains("RoseStacker") ) { - roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker"); - if (roseStackersEnabled) { - log("Hooked into RoseStackers."); - } - } - - // Check if UltimateStacker is enabled - if ( !settings.getDisabledPluginHooks().contains("UltimateStacker") ) { - ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker"); - if (ultimateStackerEnabled) { - log("Hooked into UltimateStacker."); - } - } - } - - @Override - public void allLoaded() - { - super.allLoaded(); - - if (this.isEnabled()) - { - this.hookExtensions(); - } - } - - - /** - * This method tries to hook into addons and plugins - */ - private void hookExtensions() - { - // Try to find Visit addon and if it does not exist, display a warning - this.getAddonByName("Visit").ifPresentOrElse(addon -> - { - this.visitHook = (VisitAddon) addon; - this.log("Level Addon hooked into Visit addon."); - }, () -> this.visitHook = null); - - // Try to find Warps addon and if it does not exist, display a warning - this.getAddonByName("Warps").ifPresentOrElse(addon -> - { - this.warpHook = (Warp) addon; - this.log("Level Addon hooked into Warps addon."); - }, () -> this.warpHook = null); - } - - - /** - * Compares versions - * @param version1 version 1 - * @param version2 version 2 - * @return {@code <0 if version 1 is older than version 2, =0 if the same, >0 if version 1 is newer than version 2} - */ - public static int compareVersions(String version1, String version2) { - int comparisonResult = 0; - - String[] version1Splits = version1.split("\\."); - String[] version2Splits = version2.split("\\."); - int maxLengthOfVersionSplits = Math.max(version1Splits.length, version2Splits.length); - - for (int i = 0; i < maxLengthOfVersionSplits; i++){ - Integer v1 = i < version1Splits.length ? Integer.parseInt(version1Splits[i]) : 0; - Integer v2 = i < version2Splits.length ? Integer.parseInt(version2Splits[i]) : 0; - int compare = v1.compareTo(v2); - if (compare != 0) { - comparisonResult = compare; - break; - } - } - return comparisonResult; - } - - private void registerCommands(GameModeAddon gm) { - gm.getAdminCommand().ifPresent(adminCommand -> { - new AdminLevelCommand(this, adminCommand); - new AdminTopCommand(this, adminCommand); - new AdminLevelStatusCommand(this, adminCommand); - if (getSettings().isZeroNewIslandLevels()) { - new AdminSetInitialLevelCommand(this, adminCommand); - } - }); - gm.getPlayerCommand().ifPresent(playerCmd -> { - new IslandLevelCommand(this, playerCmd); - new IslandTopCommand(this, playerCmd); - new IslandValueCommand(this, playerCmd); - }); - } - - @Override - public void onDisable() { - // Stop the pipeline - this.getPipeliner().stop(); - } - - private void loadBlockSettings() { - // Save the default blockconfig.yml - this.saveResource("blockconfig.yml", false); - - YamlConfiguration blockValues = new YamlConfiguration(); - try { - File file = new File(this.getDataFolder(), "blockconfig.yml"); - blockValues.load(file); - // Load the block config class - blockConfig = new BlockConfig(this, blockValues, file); - } catch (IOException | InvalidConfigurationException e) { - // Disable - logError("Level blockconfig.yml settings could not load! Addon disabled."); - setState(State.DISABLED); - } - - } - - - /** - * @return the blockConfig - */ - public BlockConfig getBlockConfig() { - return blockConfig; - } - - /** - * @return the settings - */ - public ConfigSettings getSettings() { - return settings; - } - - /** - * @return the pipeliner - */ - public Pipeliner getPipeliner() { - return pipeliner; - } - - /** - * @return the manager - */ - public LevelsManager getManager() { - return manager; - } - - /** - * Set the config settings - used for tests only - * @param configSettings - config settings - */ - void setSettings(ConfigSettings configSettings) { - this.settings = configSettings; - - } - - /** - * @return the stackersEnabled - */ - public boolean isStackersEnabled() { - return stackersEnabled; - } - - /** - * @return the advChestEnabled - */ - public boolean isAdvChestEnabled() { - return advChestEnabled; - } - - /** - * Get level from cache for a player. - * @param targetPlayer - target player UUID - * @return Level of player or zero if player is unknown or UUID is null - */ - public long getIslandLevel(World world, @Nullable UUID targetPlayer) { - return getManager().getIslandLevel(world, targetPlayer); - } - - /** - * Sets the player's level to a value - * @param world - world - * @param targetPlayer - target player - * @param level - level - */ - public void setIslandLevel(World world, UUID targetPlayer, long level) { - getManager().setIslandLevel(world, targetPlayer, level); - } - - /** - * Zeros the initial island level - * @param island - island - * @param level - initial calculated island level - */ - public void setInitialIslandLevel(@NonNull Island island, long level) { - getManager().setInitialIslandLevel(island, level); - } - - /** - * Get the initial island level - * @param island - island - * @return level or 0 by default - */ - public long getInitialIslandLevel(@NonNull Island island) { - return getManager().getInitialLevel(island); - } - - /** - * Calculates a user's island - * @param world - the world where this island is - * @param user - not used! See depecration message - * @param playerUUID - the target island member's UUID - * @deprecated Do not use this anymore. Use getManager().calculateLevel(playerUUID, island) - */ - @Deprecated(since="2.3.0", forRemoval=true) - public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) { - Island island = getIslands().getIsland(world, playerUUID); - if (island != null) getManager().calculateLevel(playerUUID, island); - } - - /** - * Provide the levels data for the target player - * @param targetPlayer - UUID of target player - * @return LevelsData object or null if not found. Only island levels are set! - * @deprecated Do not use this anymore. Use {@link #getIslandLevel(World, UUID)} - */ - @Deprecated(since="2.3.0", forRemoval=true) - public LevelsData getLevelsData(UUID targetPlayer) { - LevelsData ld = new LevelsData(targetPlayer); - getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) - .forEach(gm -> { - if (getSettings().isZeroNewIslandLevels()) { - Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer); - if (island != null) { - ld.setInitialLevel(gm.getOverWorld(), this.getInitialIslandLevel(island)); - } - } - ld.setLevel(gm.getOverWorld(), this.getIslandLevel(gm.getOverWorld(), targetPlayer)); - }); - return ld; - } - - /** - * @return the registeredGameModes - */ - public List getRegisteredGameModes() { - return registeredGameModes; - } - - /** - * Check if Level addon is active in game mode - * @param gm Game Mode Addon - * @return true if active, false if not - */ - public boolean isRegisteredGameMode(GameModeAddon gm) { - return registeredGameModes.contains(gm); - } - - /** - * Checks if Level addon is active in world - * @param world world - * @return true if active, false if not - */ - public boolean isRegisteredGameModeWorld(World world) { - return registeredGameModes.stream().map(GameModeAddon::getOverWorld).anyMatch(w -> Util.sameWorld(world, w)); - } - - /** - * @return the roseStackersEnabled - */ - public boolean isRoseStackersEnabled() { - return roseStackersEnabled; - } - - /** - * @return the ultimateStackerEnabled - */ - public boolean isUltimateStackerEnabled() { - return ultimateStackerEnabled; - } - - /** - * Method Level#getVisitHook returns the visitHook of this object. - * - * @return {@code Visit} of this object, {@code null} otherwise. - */ - public VisitAddon getVisitHook() - { - return this.visitHook; - } - - /** - * Method Level#getWarpHook returns the warpHook of this object. - * - * @return {@code Warp} of this object, {@code null} otherwise. - */ - public Warp getWarpHook() - { - return this.warpHook; - } + // The 10 in top ten + public static final int TEN = 10; + + // Settings + private ConfigSettings settings; + private Config configObject = new Config<>(this, ConfigSettings.class); + private BlockConfig blockConfig; + private Pipeliner pipeliner; + private LevelsManager manager; + private boolean stackersEnabled; + private boolean advChestEnabled; + private boolean roseStackersEnabled; + private boolean ultimateStackerEnabled; + private final List registeredGameModes = new ArrayList<>(); + + /** + * Local variable that stores if warpHook is present. + */ + private Warp warpHook; + + /** + * Local variable that stores if visitHook is present. + */ + private VisitAddon visitHook; + + @Override + public void onLoad() { + // Save the default config from config.yml + saveDefaultConfig(); + if (loadSettings()) { + // Disable + logError("Level settings could not load! Addon disabled."); + setState(State.DISABLED); + } else { + configObject.saveConfigObject(settings); + } + + // Save existing panels. + this.saveResource("panels/top_panel.yml", false); + this.saveResource("panels/detail_panel.yml", false); + this.saveResource("panels/value_panel.yml", false); + } + + private boolean loadSettings() { + // Load settings again to get worlds + settings = configObject.loadConfigObject(); + + return settings == null; + } + + @Override + public void onEnable() { + loadBlockSettings(); + // Start pipeline + pipeliner = new Pipeliner(this); + // Start Manager + manager = new LevelsManager(this); + // Register listeners + this.registerListener(new IslandActivitiesListeners(this)); + this.registerListener(new JoinLeaveListener(this)); + this.registerListener(new MigrationListener(this)); + + // Register commands for GameModes + registeredGameModes.clear(); + getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> { + log("Level hooking into " + gm.getDescription().getName()); + registerCommands(gm); + new PlaceholderManager(this).registerPlaceholders(gm); + registeredGameModes.add(gm); + }); + // Register request handlers + registerRequestHandler(new LevelRequestHandler(this)); + registerRequestHandler(new TopTenRequestHandler(this)); + + // Check if WildStackers is enabled on the server + // I only added support for counting blocks into the island level + // Someone else can PR if they want spawners added to the Leveling system :) + if (!settings.getDisabledPluginHooks().contains("WildStacker")) { + stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker"); + if (stackersEnabled) { + log("Hooked into WildStackers."); + } + } + + // Check if AdvancedChests is enabled on the server + if (!settings.getDisabledPluginHooks().contains("AdvancedChests")) { + Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests"); + advChestEnabled = advChest != null; + if (advChestEnabled) { + // Check version + if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) { + log("Hooked into AdvancedChests."); + } else { + logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + + " - requires version 23.0 or later"); + advChestEnabled = false; + } + } + } + + // Check if RoseStackers is enabled + if (!settings.getDisabledPluginHooks().contains("RoseStacker")) { + roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker"); + if (roseStackersEnabled) { + log("Hooked into RoseStackers."); + } + } + + // Check if UltimateStacker is enabled + if (!settings.getDisabledPluginHooks().contains("UltimateStacker")) { + ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker"); + if (ultimateStackerEnabled) { + log("Hooked into UltimateStacker."); + } + } + } + + @Override + public void allLoaded() { + super.allLoaded(); + + if (this.isEnabled()) { + this.hookExtensions(); + } + } + + /** + * This method tries to hook into addons and plugins + */ + private void hookExtensions() { + // Try to find Visit addon and if it does not exist, display a warning + this.getAddonByName("Visit").ifPresentOrElse(addon -> { + this.visitHook = (VisitAddon) addon; + this.log("Level Addon hooked into Visit addon."); + }, () -> this.visitHook = null); + + // Try to find Warps addon and if it does not exist, display a warning + this.getAddonByName("Warps").ifPresentOrElse(addon -> { + this.warpHook = (Warp) addon; + this.log("Level Addon hooked into Warps addon."); + }, () -> this.warpHook = null); + } + + /** + * Compares versions + * + * @param version1 version 1 + * @param version2 version 2 + * @return {@code <0 if version 1 is older than version 2, =0 if the same, >0 if version 1 is newer than version 2} + */ + public static int compareVersions(String version1, String version2) { + int comparisonResult = 0; + + String[] version1Splits = version1.split("\\."); + String[] version2Splits = version2.split("\\."); + int maxLengthOfVersionSplits = Math.max(version1Splits.length, version2Splits.length); + + for (int i = 0; i < maxLengthOfVersionSplits; i++) { + Integer v1 = i < version1Splits.length ? Integer.parseInt(version1Splits[i]) : 0; + Integer v2 = i < version2Splits.length ? Integer.parseInt(version2Splits[i]) : 0; + int compare = v1.compareTo(v2); + if (compare != 0) { + comparisonResult = compare; + break; + } + } + return comparisonResult; + } + + private void registerCommands(GameModeAddon gm) { + gm.getAdminCommand().ifPresent(adminCommand -> { + new AdminLevelCommand(this, adminCommand); + new AdminTopCommand(this, adminCommand); + new AdminLevelStatusCommand(this, adminCommand); + if (getSettings().isZeroNewIslandLevels()) { + new AdminSetInitialLevelCommand(this, adminCommand); + } + new AdminStatsCommand(this, adminCommand); + }); + gm.getPlayerCommand().ifPresent(playerCmd -> { + new IslandLevelCommand(this, playerCmd); + new IslandTopCommand(this, playerCmd); + new IslandValueCommand(this, playerCmd); + }); + } + + @Override + public void onDisable() { + // Stop the pipeline + this.getPipeliner().stop(); + } + + private void loadBlockSettings() { + // Save the default blockconfig.yml + this.saveResource("blockconfig.yml", false); + + YamlConfiguration blockValues = new YamlConfiguration(); + try { + File file = new File(this.getDataFolder(), "blockconfig.yml"); + blockValues.load(file); + // Load the block config class + blockConfig = new BlockConfig(this, blockValues, file); + } catch (IOException | InvalidConfigurationException e) { + // Disable + logError("Level blockconfig.yml settings could not load! Addon disabled."); + setState(State.DISABLED); + } + + } + + /** + * @return the blockConfig + */ + public BlockConfig getBlockConfig() { + return blockConfig; + } + + /** + * @return the settings + */ + public ConfigSettings getSettings() { + return settings; + } + + /** + * @return the pipeliner + */ + public Pipeliner getPipeliner() { + return pipeliner; + } + + /** + * @return the manager + */ + public LevelsManager getManager() { + return manager; + } + + /** + * Set the config settings - used for tests only + * + * @param configSettings - config settings + */ + void setSettings(ConfigSettings configSettings) { + this.settings = configSettings; + + } + + /** + * @return the stackersEnabled + */ + public boolean isStackersEnabled() { + return stackersEnabled; + } + + /** + * @return the advChestEnabled + */ + public boolean isAdvChestEnabled() { + return advChestEnabled; + } + + /** + * Get level from cache for a player. + * + * @param targetPlayer - target player UUID + * @return Level of player or zero if player is unknown or UUID is null + */ + public long getIslandLevel(World world, @Nullable UUID targetPlayer) { + return getManager().getIslandLevel(world, targetPlayer); + } + + /** + * Sets the player's level to a value + * + * @param world - world + * @param targetPlayer - target player + * @param level - level + */ + public void setIslandLevel(World world, UUID targetPlayer, long level) { + getManager().setIslandLevel(world, targetPlayer, level); + } + + /** + * Zeros the initial island level + * + * @param island - island + * @param level - initial calculated island level + */ + public void setInitialIslandLevel(@NonNull Island island, long level) { + getManager().setInitialIslandLevel(island, level); + } + + /** + * Get the initial island level + * + * @param island - island + * @return level or 0 by default + */ + public long getInitialIslandLevel(@NonNull Island island) { + return getManager().getInitialLevel(island); + } + + /** + * Calculates a user's island + * + * @param world - the world where this island is + * @param user - not used! See depecration message + * @param playerUUID - the target island member's UUID + * @deprecated Do not use this anymore. Use + * getManager().calculateLevel(playerUUID, island) + */ + @Deprecated(since = "2.3.0", forRemoval = true) + public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) { + Island island = getIslands().getIsland(world, playerUUID); + if (island != null) + getManager().calculateLevel(playerUUID, island); + } + + /** + * Provide the levels data for the target player + * + * @param targetPlayer - UUID of target player + * @return LevelsData object or null if not found. Only island levels are set! + * @deprecated Do not use this anymore. Use {@link #getIslandLevel(World, UUID)} + */ + @Deprecated(since = "2.3.0", forRemoval = true) + public LevelsData getLevelsData(UUID targetPlayer) { + LevelsData ld = new LevelsData(targetPlayer); + getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> { + if (getSettings().isZeroNewIslandLevels()) { + Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer); + if (island != null) { + ld.setInitialLevel(gm.getOverWorld(), this.getInitialIslandLevel(island)); + } + } + ld.setLevel(gm.getOverWorld(), this.getIslandLevel(gm.getOverWorld(), targetPlayer)); + }); + return ld; + } + + /** + * @return the registeredGameModes + */ + public List getRegisteredGameModes() { + return registeredGameModes; + } + + /** + * Check if Level addon is active in game mode + * + * @param gm Game Mode Addon + * @return true if active, false if not + */ + public boolean isRegisteredGameMode(GameModeAddon gm) { + return registeredGameModes.contains(gm); + } + + /** + * Checks if Level addon is active in world + * + * @param world world + * @return true if active, false if not + */ + public boolean isRegisteredGameModeWorld(World world) { + return registeredGameModes.stream().map(GameModeAddon::getOverWorld).anyMatch(w -> Util.sameWorld(world, w)); + } + + /** + * @return the roseStackersEnabled + */ + public boolean isRoseStackersEnabled() { + return roseStackersEnabled; + } + + /** + * @return the ultimateStackerEnabled + */ + public boolean isUltimateStackerEnabled() { + return ultimateStackerEnabled; + } + + /** + * Method Level#getVisitHook returns the visitHook of this object. + * + * @return {@code Visit} of this object, {@code null} otherwise. + */ + public VisitAddon getVisitHook() { + return this.visitHook; + } + + /** + * Method Level#getWarpHook returns the warpHook of this object. + * + * @return {@code Warp} of this object, {@code null} otherwise. + */ + public Warp getWarpHook() { + return this.warpHook; + } } diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index e40ba49..087ab0d 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -31,417 +31,453 @@ import world.bentobox.level.objects.LevelsData; import world.bentobox.level.objects.TopTenData; - public class LevelsManager { - private static final String INTOPTEN = "intopten"; - private static final TreeMap LEVELS; - private static final BigInteger THOUSAND = BigInteger.valueOf(1000); - static { - LEVELS = new TreeMap<>(); - - LEVELS.put(THOUSAND, "k"); - LEVELS.put(THOUSAND.pow(2), "M"); - LEVELS.put(THOUSAND.pow(3), "G"); - LEVELS.put(THOUSAND.pow(4), "T"); - } - private final Level addon; - - // Database handler for level data - private final Database handler; - // A cache of island levels. - private final Map levelsCache; - // Top ten lists - private final Map topTenLists; - - - public LevelsManager(Level addon) { - this.addon = addon; - // Get the BentoBox database - // Set up the database handler to store and retrieve data - // Note that these are saved by the BentoBox database - handler = new Database<>(addon, IslandLevels.class); - // Initialize the cache - levelsCache = new HashMap<>(); - // Initialize top ten lists - topTenLists = new ConcurrentHashMap<>(); - } - - 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; - } - }); - } - - /** - * Add a score to the top players list - * @param world - world - * @param targetPlayer - target player - * @param lv - island level - */ - private void addToTopTen(@NonNull World world, @NonNull UUID targetPlayer, long lv) { - // Get top ten - Map topTen = topTenLists.computeIfAbsent(world, k -> new TopTenData(world)).getTopTen(); - // Remove this player from the top list no matter what (we'll put them back later if required) - topTen.remove(targetPlayer); - - // Get the island - Island island = addon.getIslands().getIsland(world, targetPlayer); - if (island != null && island.getOwner() != null && hasTopTenPerm(world, island.getOwner())) { - // Insert the owner into the top ten - topTen.put(island.getOwner(), lv); - } - } - - /** - * Add an island to a top ten - * @param island - island to add - * @param lv - level - * @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.getOwner(), lv); - return true; - } - return false; - } - - /** - * Calculate the island level, set all island member's levels to the result and try to add the owner to the top ten - * @param targetPlayer - uuid of targeted player - owner or team member - * @param island - island to calculate - * @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.getWorld(), island.getOwner(), r); - // Save the island scan details - result.complete(r); - }); - return result; - } - - /** - * Fires the IslandLevelCalculatedEvent and returns true if it is canceled - * @param targetPlayer - target player - * @param island - island - * @param results - results set - * @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)); - } - - /** - * Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k - * @param lvl - long value to represent - * @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); - - 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; - } - - /** - * Get the initial level of the island. Used to zero island levels - * @param island - island - * @return initial level of island - */ - public long getInitialLevel(Island island) { - return getLevelsData(island).getInitialLevel(); - } - - /** - * Get level of island from cache for a player. - * @param world - world where the island is - * @param targetPlayer - target player UUID - * @return Level of the player's island or zero if player is unknown or UUID is 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(); - } - - /** - * Get the maximum level ever given to this island - * @param world - world where the island is - * @param targetPlayer - target player UUID - * @return Max level of the player's island or zero if player is unknown or UUID 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(); - } - - /** - * Returns a formatted string of the target player's island level - * @param world - world where the island is - * @param targetPlayer - target player's UUID - * @return Formatted level of player or zero if player is unknown or UUID is null - */ - public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { - return formatLevel(getIslandLevel(world, targetPlayer)); - } - - /** - * Load a level data for the island from the cache or database. - * @param island - UUID of island - * @return IslandLevels object - */ - @NonNull - public IslandLevels getLevelsData(@NonNull Island island) { - String id = island.getUniqueId(); - if (levelsCache.containsKey(id)) { - return levelsCache.get(id); - } - // Get from database if not in cache - if (handler.objectExists(id)) { - IslandLevels ld = handler.loadObject(id); - if (ld != null) { - levelsCache.put(id, ld); - } else { - handler.deleteID(id); - levelsCache.put(id, new IslandLevels(id)); - } - } else { - levelsCache.put(id, new IslandLevels(id)); - } - // Return cached value - return levelsCache.get(id); - } - - /** - * Get the number of points required until the next level since the last level calc - * @param world - world where the island is - * @param targetPlayer - target player UUID - * @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()); - } - - /** - * Get the top ten for this world. Returns offline players or players with the intopten permission. - * @param world - world requested - * @param size - size of the top ten - * @return sorted top ten map - */ - @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(e -> addon.getIslands().isOwner(world, e.getKey())) - .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 -> !hasTopTenPerm(world, u)); - } - - /** - * @return the topTenLists - */ - protected Map getTopTenLists() { - return topTenLists; - } - - /** - * Get the rank of the player in the rankings - * @param world - world - * @param uuid - player UUID - * @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(e -> addon.getIslands().isOwner(world, e.getKey())) - .filter(l -> l.getValue() > 0) - .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); - return (int) (stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).count() + 1); - } - - /** - * Checks if player has the correct top ten perm to have their level saved - * @param world - * @param targetPlayer - * @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); - } - - /** - * 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())); - }); - } - - /** - * Removes a player from a world's top ten and removes world from player's level data - * @param world - world - * @param uuid - the player's uuid - */ - public void removeEntry(World world, UUID uuid) { - if (topTenLists.containsKey(world)) { - topTenLists.get(world).getTopTen().remove(uuid); - } - - } - - /** - * Set an initial island level - * @param island - the island to set. Must have a non-null world - * @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())); - } - - /** - * Set the island level for the owner of the island that targetPlayer is a member - * @param world - world - * @param targetPlayer - player, may be a team member - * @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(world, targetPlayer, levelsCache.get(id).getLevel()); - } - - } - - /** - * Set the island level for the owner of the island that targetPlayer is a member - * @param world - world - * @param owner - owner of the island - * @param r - results of the calculation - */ - private void setIslandResults(World world, @NonNull UUID owner, Results r) { - // Get the island - Island island = addon.getIslands().getIsland(world, owner); - 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(world, owner, ld.getLevel()); - } - - /** - * Removes island from cache when it is deleted - * @param uniqueId - id of island - */ - public void deleteIsland(String uniqueId) { - levelsCache.remove(uniqueId); - handler.deleteID(uniqueId); - } + private static final String INTOPTEN = "intopten"; + private static final TreeMap LEVELS; + private static final BigInteger THOUSAND = BigInteger.valueOf(1000); + static { + LEVELS = new TreeMap<>(); + + LEVELS.put(THOUSAND, "k"); + LEVELS.put(THOUSAND.pow(2), "M"); + LEVELS.put(THOUSAND.pow(3), "G"); + LEVELS.put(THOUSAND.pow(4), "T"); + } + private final Level addon; + + // Database handler for level data + private final Database handler; + // A cache of island levels. + private final Map levelsCache; + // Top ten lists + private final Map topTenLists; + + public LevelsManager(Level addon) { + this.addon = addon; + // Get the BentoBox database + // Set up the database handler to store and retrieve data + // Note that these are saved by the BentoBox database + handler = new Database<>(addon, IslandLevels.class); + // Initialize the cache + levelsCache = new HashMap<>(); + // Initialize top ten lists + topTenLists = new ConcurrentHashMap<>(); + } + + 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; + } + }); + } + + /** + * Add a score to the top players list + * + * @param world - world + * @param targetPlayer - target player + * @param lv - island level + */ + private void addToTopTen(@NonNull World world, @NonNull UUID targetPlayer, long lv) { + // Get top ten + Map topTen = topTenLists.computeIfAbsent(world, k -> new TopTenData(world)).getTopTen(); + // Remove this player from the top list no matter what (we'll put them back + // later if required) + topTen.remove(targetPlayer); + + // Get the island + Island island = addon.getIslands().getIsland(world, targetPlayer); + if (island != null && island.getOwner() != null && hasTopTenPerm(world, island.getOwner())) { + // Insert the owner into the top ten + topTen.put(island.getOwner(), lv); + } + } + + /** + * Add an island to a top ten + * + * @param island - island to add + * @param lv - level + * @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.getOwner(), lv); + return true; + } + return false; + } + + /** + * Calculate the island level, set all island member's levels to the result and + * try to add the owner to the top ten + * + * @param targetPlayer - uuid of targeted player - owner or team member + * @param island - island to calculate + * @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.getWorld(), island.getOwner(), r); + // Save the island scan details + result.complete(r); + }); + return result; + } + + /** + * Fires the IslandLevelCalculatedEvent and returns true if it is canceled + * + * @param targetPlayer - target player + * @param island - island + * @param results - results set + * @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)); + } + + /** + * Get the string representation of the level. May be converted to shorthand + * notation, e.g., 104556 = 10.5k + * + * @param lvl - long value to represent + * @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); + + 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; + } + + /** + * Get the initial level of the island. Used to zero island levels + * + * @param island - island + * @return initial level of island + */ + public long getInitialLevel(Island island) { + return getLevelsData(island).getInitialLevel(); + } + + /** + * Get level of island from cache for a player. + * + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return Level of the player's island or zero if player is unknown or UUID is + * 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(); + } + + /** + * Get the maximum level ever given to this island + * + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return Max level of the player's island or zero if player is unknown or UUID + * 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(); + } + + /** + * Returns a formatted string of the target player's island level + * + * @param world - world where the island is + * @param targetPlayer - target player's UUID + * @return Formatted level of player or zero if player is unknown or UUID is + * null + */ + public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { + return formatLevel(getIslandLevel(world, targetPlayer)); + } + + /** + * Load a level data for the island from the cache or database. + * + * @param island - UUID of island + * @return IslandLevels object + */ + @NonNull + public IslandLevels getLevelsData(@NonNull Island island) { + String id = island.getUniqueId(); + if (levelsCache.containsKey(id)) { + return levelsCache.get(id); + } + // Get from database if not in cache + if (handler.objectExists(id)) { + IslandLevels ld = handler.loadObject(id); + if (ld != null) { + levelsCache.put(id, ld); + } else { + handler.deleteID(id); + levelsCache.put(id, new IslandLevels(id)); + } + } else { + levelsCache.put(id, new IslandLevels(id)); + } + // Return cached value + return levelsCache.get(id); + } + + /** + * Get the number of points required until the next level since the last level + * calc + * + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @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()); + } + + /** + * Get the top ten for this world. Returns offline players or players with the + * intopten permission. + * + * @param world - world requested + * @param size - size of the top ten + * @return sorted top ten map + */ + @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(e -> addon.getIslands().hasIsland(world, e.getKey())).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 -> !hasTopTenPerm(world, u)); + } + + /** + * @return the topTenLists + */ + public Map getTopTenLists() { + return topTenLists; + } + + /** + * Get the rank of the player in the rankings + * + * @param world - world + * @param uuid - player UUID + * @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(e -> addon.getIslands().isOwner(world, e.getKey())).filter(l -> l.getValue() > 0) + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); + return (int) (stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).count() + 1); + } + + /** + * Checks if player has the correct top ten perm to have their level saved + * + * @param world + * @param targetPlayer + * @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); + } + + /** + * 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())); + }); + } + + /** + * Removes a player from a world's top ten and removes world from player's level + * data + * + * @param world - world + * @param uuid - the player's uuid + */ + public void removeEntry(World world, UUID uuid) { + if (topTenLists.containsKey(world)) { + topTenLists.get(world).getTopTen().remove(uuid); + } + + } + + /** + * Set an initial island level + * + * @param island - the island to set. Must have a non-null world + * @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())); + } + + /** + * Set the island level for the owner of the island that targetPlayer is a + * member + * + * @param world - world + * @param targetPlayer - player, may be a team member + * @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(world, targetPlayer, levelsCache.get(id).getLevel()); + } + + } + + /** + * Set the island level for the owner of the island that targetPlayer is a + * member + * + * @param world - world + * @param owner - owner of the island + * @param r - results of the calculation + */ + private void setIslandResults(World world, @NonNull UUID owner, Results r) { + // Get the island + Island island = addon.getIslands().getIsland(world, owner); + 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(world, owner, ld.getLevel()); + } + + /** + * Removes island from cache when it is deleted + * + * @param uniqueId - id of island + */ + public void deleteIsland(String uniqueId) { + levelsCache.remove(uniqueId); + handler.deleteID(uniqueId); + } } diff --git a/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java b/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java new file mode 100644 index 0000000..3399a1f --- /dev/null +++ b/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java @@ -0,0 +1,94 @@ +package world.bentobox.level.commands; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.bukkit.World; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.level.Level; +import world.bentobox.level.objects.TopTenData; + +public class AdminStatsCommand extends CompositeCommand { + + private final Level level; + + public AdminStatsCommand(Level addon, CompositeCommand parent) { + super(parent, "stats"); + this.level = addon; + new AdminTopRemoveCommand(addon, this); + } + + @Override + public void setup() { + this.setPermission("admin.stats"); + this.setOnlyPlayer(false); + this.setDescription("admin.stats.description"); + } + + @Override + public boolean execute(User user, String label, List args) { + user.sendMessage("admin.stats.title"); + for (Entry en : level.getManager().getTopTenLists().entrySet()) { + user.sendMessage("admin.stats.world", TextVariables.NAME, + level.getPlugin().getIWM().getWorldName(en.getKey())); + Map topTen = en.getValue().getTopTen(); + if (topTen.isEmpty()) { + user.sendMessage("admin.stats.no-data"); + return false; + } + + // Calculating basic statistics + long sum = 0, max = Long.MIN_VALUE, min = Long.MAX_VALUE; + Map levelFrequency = new HashMap<>(); + + for (Long level : topTen.values()) { + sum += level; + max = Math.max(max, level); + min = Math.min(min, level); + levelFrequency.merge(level, 1, Integer::sum); + } + + double average = sum / (double) topTen.size(); + List sortedLevels = topTen.values().stream().sorted().collect(Collectors.toList()); + long median = sortedLevels.get(sortedLevels.size() / 2); + Long mode = Collections.max(levelFrequency.entrySet(), Map.Entry.comparingByValue()).getKey(); + + // Logging basic statistics + user.sendMessage("admin.stats.average-level", TextVariables.NUMBER, String.valueOf(average)); + user.sendMessage("admin.stats.median-level", TextVariables.NUMBER, String.valueOf(median)); + user.sendMessage("admin.stats.mode-level", TextVariables.NUMBER, String.valueOf(mode)); + user.sendMessage("admin.stats.highest-level", TextVariables.NUMBER, String.valueOf(max)); + user.sendMessage("admin.stats.lowest-level", TextVariables.NUMBER, String.valueOf(min)); + + // Grouping data for distribution analysis + Map rangeMap = new TreeMap<>(); + for (Long level : topTen.values()) { + String range = getRange(level); + rangeMap.merge(range, 1, Integer::sum); + } + + // Logging distribution + user.sendMessage("admin.stats.distribution"); + for (Map.Entry entry : rangeMap.entrySet()) { + user.sendMessage( + entry.getKey() + ": " + entry.getValue() + " " + user.getTranslation("admin.stats.islands")); + } + } + return true; + } + + private static String getRange(long level) { + long rangeStart = level / 100 * 100; + long rangeEnd = rangeStart + 99; + return rangeStart + "-" + rangeEnd; + } +} diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 6456818..f71b52d 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -22,8 +22,18 @@ admin: remove: description: "remove player from Top Ten" parameters: "" - - + stats: + description: "show stats on islands on this server" + title: "Server Island Stats" + world: "&a [name]" + no-data: "&c No data to process." + average-level: "Average Island Level: [number]" + median-level: "Median Island Level: [number]" + mode-level: "Mode Island Level: [number]" + highest-level: "Highest Island Level: [number]" + lowest-level: "Lowest Island Level: [number]" + distribution: "Island Level Distribution:" + islands: "islands" island: level: parameters: "[player]" From 1a4077be8c8c03bc7ba69041a21614f11c17e292 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 18 Nov 2023 14:44:49 -0800 Subject: [PATCH 077/106] Update tests --- .../java/world/bentobox/level/LevelTest.java | 447 +++++++++--------- 1 file changed, 222 insertions(+), 225 deletions(-) diff --git a/src/test/java/world/bentobox/level/LevelTest.java b/src/test/java/world/bentobox/level/LevelTest.java index 656c865..5f8074a 100644 --- a/src/test/java/world/bentobox/level/LevelTest.java +++ b/src/test/java/world/bentobox/level/LevelTest.java @@ -72,232 +72,229 @@ */ @SuppressWarnings("deprecation") @RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, BentoBox.class, User.class}) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class }) public class LevelTest { - private static File jFile; - @Mock - private User user; - @Mock - private IslandsManager im; - @Mock - private Island island; - @Mock - private BentoBox plugin; - @Mock - private FlagsManager fm; - @Mock - private GameModeAddon gameMode; - @Mock - private AddonsManager am; - @Mock - private BukkitScheduler scheduler; - - @Mock - private Settings pluginSettings; - - private Level addon; - - @Mock - private Logger logger; - @Mock - private PlaceholdersManager phm; - @Mock - private CompositeCommand cmd; - @Mock - private CompositeCommand adminCmd; - @Mock - private World world; - private UUID uuid; - - @Mock - private PluginManager pim; - @Mock - private BlockConfig blockConfig; - - @BeforeClass - public static void beforeClass() throws IOException { - // Make the addon jar - jFile = new File("addon.jar"); - // Copy over config file from src folder - Path fromPath = Paths.get("src/main/resources/config.yml"); - Path path = Paths.get("config.yml"); - Files.copy(fromPath, path); - // Copy over block config file from src folder - fromPath = Paths.get("src/main/resources/blockconfig.yml"); - path = Paths.get("blockconfig.yml"); - Files.copy(fromPath, path); - try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { - //Added the new files to the jar. - try (FileInputStream fis = new FileInputStream(path.toFile())) { - byte[] buffer = new byte[1024]; - int bytesRead = 0; - JarEntry entry = new JarEntry(path.toString()); - tempJarOutputStream.putNextEntry(entry); - while((bytesRead = fis.read(buffer)) != -1) { - tempJarOutputStream.write(buffer, 0, bytesRead); - } - } - } - } - - /** - * @throws java.lang.Exception - */ - @Before - public void setUp() throws Exception { - // Set up plugin - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); - - // The database type has to be created one line before the thenReturn() to work! - DatabaseType value = DatabaseType.JSON; - when(plugin.getSettings()).thenReturn(pluginSettings); - when(pluginSettings.getDatabaseType()).thenReturn(value); - - //when(plugin.isEnabled()).thenReturn(true); - // Command manager - CommandsManager cm = mock(CommandsManager.class); - when(plugin.getCommandsManager()).thenReturn(cm); - - // Player - Player p = mock(Player.class); - // Sometimes use Mockito.withSettings().verboseLogging() - when(user.isOp()).thenReturn(false); - uuid = UUID.randomUUID(); - when(user.getUniqueId()).thenReturn(uuid); - when(user.getPlayer()).thenReturn(p); - when(user.getName()).thenReturn("tastybento"); - User.setPlugin(plugin); - - // Island World Manager - IslandWorldManager iwm = mock(IslandWorldManager.class); - when(plugin.getIWM()).thenReturn(iwm); - - - - // Player has island to begin with - when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island); - when(plugin.getIslands()).thenReturn(im); - - // Locales - // Return the reference (USE THIS IN THE FUTURE) - when(user.getTranslation(Mockito.anyString())).thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); - - // Server - PowerMockito.mockStatic(Bukkit.class); - Server server = mock(Server.class); - when(Bukkit.getServer()).thenReturn(server); - when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); - when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class)); - - // Addon - addon = new Level(); - File dataFolder = new File("addons/Level"); - addon.setDataFolder(dataFolder); - addon.setFile(jFile); - AddonDescription desc = new AddonDescription.Builder("bentobox", "Level", "1.3").description("test").authors("tastybento").build(); - addon.setDescription(desc); - addon.setSettings(new ConfigSettings()); - // Addons manager - when(plugin.getAddonsManager()).thenReturn(am); - // One game mode - when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode)); - AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test").authors("tasty").build(); - when(gameMode.getDescription()).thenReturn(desc2); - when(gameMode.getOverWorld()).thenReturn(world); - - // Player command - @NonNull - Optional opCmd = Optional.of(cmd); - when(gameMode.getPlayerCommand()).thenReturn(opCmd); - // Admin command - Optional opAdminCmd = Optional.of(adminCmd); - when(gameMode.getAdminCommand()).thenReturn(opAdminCmd); - - // Flags manager - when(plugin.getFlagsManager()).thenReturn(fm); - when(fm.getFlags()).thenReturn(Collections.emptyList()); - - - // Bukkit - PowerMockito.mockStatic(Bukkit.class); - when(Bukkit.getScheduler()).thenReturn(scheduler); - ItemMeta meta = mock(ItemMeta.class); - ItemFactory itemFactory = mock(ItemFactory.class); - when(itemFactory.getItemMeta(any())).thenReturn(meta); - when(Bukkit.getItemFactory()).thenReturn(itemFactory); - UnsafeValues unsafe = mock(UnsafeValues.class); - when(unsafe.getDataVersion()).thenReturn(777); - when(Bukkit.getUnsafe()).thenReturn(unsafe); - when(Bukkit.getPluginManager()).thenReturn(pim); - - // placeholders - when(plugin.getPlaceholdersManager()).thenReturn(phm); - - // World - when(world.getName()).thenReturn("bskyblock-world"); - // Island - when(island.getWorld()).thenReturn(world); - when(island.getOwner()).thenReturn(uuid); - } - - /** - * @throws java.lang.Exception - */ - @After - public void tearDown() throws Exception { - deleteAll(new File("database")); - } - - @AfterClass - public static void cleanUp() throws Exception { - new File("addon.jar").delete(); - new File("config.yml").delete(); - new File("blockconfig.yml").delete(); - deleteAll(new File("addons")); - } - - private static void deleteAll(File file) throws IOException { - if (file.exists()) { - Files.walk(file.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - } - - /** - * Test method for {@link world.bentobox.level.Level#onEnable()}. - */ - @Test - public void testOnEnable() { - addon.onEnable(); - verify(plugin).logWarning("[Level] Level Addon: No such world in blockconfig.yml : acidisland_world"); - verify(plugin).log("[Level] Level hooking into BSkyBlock"); - verify(cmd, times(3)).getAddon(); // 3 commands - verify(adminCmd, times(4)).getAddon(); // Four commands - // Placeholders - verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_island_level"), any()); - verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_visited_island_level"), any()); - verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_points_to_next_level"), any()); - for (int i = 1; i < 11; i++) { - verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_name_" + i), any()); - verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_value_" + i), any()); - } - // Commands - verify(am).registerListener(eq(addon), any(IslandActivitiesListeners.class)); - verify(am).registerListener(eq(addon), any(JoinLeaveListener.class)); - } - - /** - * Test method for {@link world.bentobox.level.Level#getSettings()}. - */ - @Test - public void testGetSettings() { - addon.onEnable(); - ConfigSettings s = addon.getSettings(); - assertEquals(100, s.getLevelCost()); - } + private static File jFile; + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private BentoBox plugin; + @Mock + private FlagsManager fm; + @Mock + private GameModeAddon gameMode; + @Mock + private AddonsManager am; + @Mock + private BukkitScheduler scheduler; + + @Mock + private Settings pluginSettings; + + private Level addon; + + @Mock + private Logger logger; + @Mock + private PlaceholdersManager phm; + @Mock + private CompositeCommand cmd; + @Mock + private CompositeCommand adminCmd; + @Mock + private World world; + private UUID uuid; + + @Mock + private PluginManager pim; + @Mock + private BlockConfig blockConfig; + + @BeforeClass + public static void beforeClass() throws IOException { + // Make the addon jar + jFile = new File("addon.jar"); + // Copy over config file from src folder + Path fromPath = Paths.get("src/main/resources/config.yml"); + Path path = Paths.get("config.yml"); + Files.copy(fromPath, path); + // Copy over block config file from src folder + fromPath = Paths.get("src/main/resources/blockconfig.yml"); + path = Paths.get("blockconfig.yml"); + Files.copy(fromPath, path); + try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { + // Added the new files to the jar. + try (FileInputStream fis = new FileInputStream(path.toFile())) { + byte[] buffer = new byte[1024]; + int bytesRead = 0; + JarEntry entry = new JarEntry(path.toString()); + tempJarOutputStream.putNextEntry(entry); + while ((bytesRead = fis.read(buffer)) != -1) { + tempJarOutputStream.write(buffer, 0, bytesRead); + } + } + } + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); + + // The database type has to be created one line before the thenReturn() to work! + DatabaseType value = DatabaseType.JSON; + when(plugin.getSettings()).thenReturn(pluginSettings); + when(pluginSettings.getDatabaseType()).thenReturn(value); + + // when(plugin.isEnabled()).thenReturn(true); + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + // Player + Player p = mock(Player.class); + // Sometimes use Mockito.withSettings().verboseLogging() + when(user.isOp()).thenReturn(false); + uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + User.setPlugin(plugin); + + // Island World Manager + IslandWorldManager iwm = mock(IslandWorldManager.class); + when(plugin.getIWM()).thenReturn(iwm); + + // Player has island to begin with + when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island); + when(plugin.getIslands()).thenReturn(im); + + // Locales + // Return the reference (USE THIS IN THE FUTURE) + when(user.getTranslation(Mockito.anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + + // Server + PowerMockito.mockStatic(Bukkit.class); + Server server = mock(Server.class); + when(Bukkit.getServer()).thenReturn(server); + when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); + when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class)); + + // Addon + addon = new Level(); + File dataFolder = new File("addons/Level"); + addon.setDataFolder(dataFolder); + addon.setFile(jFile); + AddonDescription desc = new AddonDescription.Builder("bentobox", "Level", "1.3").description("test") + .authors("tastybento").build(); + addon.setDescription(desc); + addon.setSettings(new ConfigSettings()); + // Addons manager + when(plugin.getAddonsManager()).thenReturn(am); + // One game mode + when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode)); + AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test") + .authors("tasty").build(); + when(gameMode.getDescription()).thenReturn(desc2); + when(gameMode.getOverWorld()).thenReturn(world); + + // Player command + @NonNull + Optional opCmd = Optional.of(cmd); + when(gameMode.getPlayerCommand()).thenReturn(opCmd); + // Admin command + Optional opAdminCmd = Optional.of(adminCmd); + when(gameMode.getAdminCommand()).thenReturn(opAdminCmd); + + // Flags manager + when(plugin.getFlagsManager()).thenReturn(fm); + when(fm.getFlags()).thenReturn(Collections.emptyList()); + + // Bukkit + PowerMockito.mockStatic(Bukkit.class); + when(Bukkit.getScheduler()).thenReturn(scheduler); + ItemMeta meta = mock(ItemMeta.class); + ItemFactory itemFactory = mock(ItemFactory.class); + when(itemFactory.getItemMeta(any())).thenReturn(meta); + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + UnsafeValues unsafe = mock(UnsafeValues.class); + when(unsafe.getDataVersion()).thenReturn(777); + when(Bukkit.getUnsafe()).thenReturn(unsafe); + when(Bukkit.getPluginManager()).thenReturn(pim); + + // placeholders + when(plugin.getPlaceholdersManager()).thenReturn(phm); + + // World + when(world.getName()).thenReturn("bskyblock-world"); + // Island + when(island.getWorld()).thenReturn(world); + when(island.getOwner()).thenReturn(uuid); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + deleteAll(new File("database")); + } + + @AfterClass + public static void cleanUp() throws Exception { + new File("addon.jar").delete(); + new File("config.yml").delete(); + new File("blockconfig.yml").delete(); + deleteAll(new File("addons")); + } + + private static void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + } + + /** + * Test method for {@link world.bentobox.level.Level#onEnable()}. + */ + @Test + public void testOnEnable() { + addon.onEnable(); + verify(plugin).logWarning("[Level] Level Addon: No such world in blockconfig.yml : acidisland_world"); + verify(plugin).log("[Level] Level hooking into BSkyBlock"); + verify(cmd, times(3)).getAddon(); // 3 commands + verify(adminCmd, times(5)).getAddon(); // Five commands + // Placeholders + verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_island_level"), any()); + verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_visited_island_level"), any()); + verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_points_to_next_level"), any()); + for (int i = 1; i < 11; i++) { + verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_name_" + i), any()); + verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_value_" + i), any()); + } + // Commands + verify(am).registerListener(eq(addon), any(IslandActivitiesListeners.class)); + verify(am).registerListener(eq(addon), any(JoinLeaveListener.class)); + } + + /** + * Test method for {@link world.bentobox.level.Level#getSettings()}. + */ + @Test + public void testGetSettings() { + addon.onEnable(); + ConfigSettings s = addon.getSettings(); + assertEquals(100, s.getLevelCost()); + } } From 77884f0a11882b61264d7d672b9466a456968ab0 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 18 Nov 2023 14:51:09 -0800 Subject: [PATCH 078/106] Update to BentoBox 2.0.0 API --- .../world/bentobox/level/LevelsManager.java | 2 +- .../bentobox/level/panels/DetailsPanel.java | 1421 ++++++++--------- .../bentobox/level/LevelsManagerTest.java | 536 ++++--- 3 files changed, 924 insertions(+), 1035 deletions(-) diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index 087ab0d..fa8083d 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -356,7 +356,7 @@ public Map getTopTenLists() { public int getRank(@NonNull World world, UUID uuid) { createAndCleanRankings(world); Stream> stream = topTenLists.get(world).getTopTen().entrySet().stream() - .filter(e -> addon.getIslands().isOwner(world, e.getKey())).filter(l -> l.getValue() > 0) + .filter(e -> addon.getIslands().hasIsland(world, e.getKey())).filter(l -> l.getValue() > 0) .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); return (int) (stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).count() + 1); } diff --git a/src/main/java/world/bentobox/level/panels/DetailsPanel.java b/src/main/java/world/bentobox/level/panels/DetailsPanel.java index 395e5ed..fad4988 100644 --- a/src/main/java/world/bentobox/level/panels/DetailsPanel.java +++ b/src/main/java/world/bentobox/level/panels/DetailsPanel.java @@ -1,6 +1,5 @@ package world.bentobox.level.panels; - import java.io.File; import java.util.ArrayList; import java.util.Comparator; @@ -30,775 +29,659 @@ import world.bentobox.level.objects.IslandLevels; import world.bentobox.level.util.Utils; - /** * This class opens GUI that shows generator view for user. */ -public class DetailsPanel -{ - // --------------------------------------------------------------------- - // Section: Internal Constructor - // --------------------------------------------------------------------- - - - /** - * This is internal constructor. It is used internally in current class to avoid creating objects everywhere. - * - * @param addon Level object - * @param world World where user is operating - * @param user User who opens panel - */ - private DetailsPanel(Level addon, - World world, - User user) - { - this.addon = addon; - this.world = world; - this.user = user; - - this.island = this.addon.getIslands().getIsland(world, user); - - if (this.island != null) - { - this.levelsData = this.addon.getManager().getLevelsData(this.island); - } - else - { - this.levelsData = null; - } - - // By default no-filters are active. - this.activeTab = Tab.ALL_BLOCKS; - this.activeFilter = Filter.NAME; - this.materialCountList = new ArrayList<>(Material.values().length); - - this.updateFilters(); - } - - - /** - * This method builds this GUI. - */ - private void build() - { - if (this.island == null || this.levelsData == null) - { - // Nothing to see. - Utils.sendMessage(this.user, this.user.getTranslation("general.errors.no-island")); - return; - } - - if (this.levelsData.getMdCount().isEmpty() && this.levelsData.getUwCount().isEmpty()) - { - // Nothing to see. - Utils.sendMessage(this.user, this.user.getTranslation("level.conversations.no-data")); - return; - } - - // Start building panel. - TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); - panelBuilder.user(this.user); - panelBuilder.world(this.user.getWorld()); - - panelBuilder.template("detail_panel", new File(this.addon.getDataFolder(), "panels")); - - panelBuilder.parameters("[name]", this.user.getName()); - - panelBuilder.registerTypeBuilder("NEXT", this::createNextButton); - panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton); - panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton); - - panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton); - - // Register tabs - panelBuilder.registerTypeBuilder("TAB", this::createTabButton); - - // Register unknown type builder. - panelBuilder.build(); - } - - - /** - * This method updates filter of elements based on tabs. - */ - private void updateFilters() - { - this.materialCountList.clear(); - - switch (this.activeTab) - { - case ALL_BLOCKS -> { - Map materialCountMap = new EnumMap<>(Material.class); - - materialCountMap.putAll(this.levelsData.getMdCount()); - - // Add underwater blocks. - this.levelsData.getUwCount().forEach((material, count) -> materialCountMap.put(material, - materialCountMap.computeIfAbsent(material, key -> 0) + count)); - - materialCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey())). - forEachOrdered(entry -> - this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); - } - case ABOVE_SEA_LEVEL -> this.levelsData.getMdCount().entrySet().stream().sorted((Map.Entry.comparingByKey())) - .forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); - - case UNDERWATER -> this.levelsData.getUwCount().entrySet().stream().sorted((Map.Entry.comparingByKey())) - .forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); - - case SPAWNER -> { - int aboveWater = this.levelsData.getMdCount().getOrDefault(Material.SPAWNER, 0); - int underWater = this.levelsData.getUwCount().getOrDefault(Material.SPAWNER, 0); - - // TODO: spawners need some touch... - this.materialCountList.add(new Pair<>(Material.SPAWNER, underWater + aboveWater)); - } - } - - Comparator> sorter; - - switch (this.activeFilter) - { - case COUNT -> - { - sorter = (o1, o2) -> - { - if (o1.getValue().equals(o2.getValue())) - { - String o1Name = Utils.prettifyObject(o1.getKey(), this.user); - String o2Name = Utils.prettifyObject(o2.getKey(), this.user); - - return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); - } - else - { - return Integer.compare(o2.getValue(), o1.getValue()); - } - }; - } - case VALUE -> - { - sorter = (o1, o2) -> - { - int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o1.getKey(), 0); - int o1Count = blockLimit > 0 ? Math.min(o1.getValue(), blockLimit) : o1.getValue(); - - blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o2.getKey(), 0); - int o2Count = blockLimit > 0 ? Math.min(o2.getValue(), blockLimit) : o2.getValue(); - - long o1Value = (long) o1Count * - this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0); - long o2Value = (long) o2Count * - this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0); - - if (o1Value == o2Value) - { - String o1Name = Utils.prettifyObject(o1.getKey(), this.user); - String o2Name = Utils.prettifyObject(o2.getKey(), this.user); - - return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); - } - else - { - return Long.compare(o2Value, o1Value); - } - }; - } - default -> - { - sorter = (o1, o2) -> - { - String o1Name = Utils.prettifyObject(o1.getKey(), this.user); - String o2Name = Utils.prettifyObject(o2.getKey(), this.user); - - return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); - }; - } - } - - this.materialCountList.sort(sorter); - - this.pageIndex = 0; - } - - - // --------------------------------------------------------------------- - // Section: Tab Button Type - // --------------------------------------------------------------------- - - - /** - * Create tab button panel item. - * - * @param template the template - * @param slot the slot - * @return the panel item - */ - private PanelItem createTabButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) - { - PanelItemBuilder builder = new PanelItemBuilder(); - - if (template.icon() != null) - { - // Set icon - builder.icon(template.icon().clone()); - } - - if (template.title() != null) - { - // Set title - builder.name(this.user.getTranslation(this.world, template.title())); - } - - if (template.description() != null) - { - // Set description - builder.description(this.user.getTranslation(this.world, template.description())); - } - - Tab tab = Enums.getIfPresent(Tab.class, String.valueOf(template.dataMap().get("tab"))).or(Tab.ALL_BLOCKS); - - // Get only possible actions, by removing all inactive ones. - List activeActions = new ArrayList<>(template.actions()); - - activeActions.removeIf(action -> - "VIEW".equalsIgnoreCase(action.actionType()) && this.activeTab == tab); - - // Add Click handler - builder.clickHandler((panel, user, clickType, i) -> - { - for (ItemTemplateRecord.ActionRecords action : activeActions) - { - if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) - && "VIEW".equalsIgnoreCase(action.actionType())) - { - this.activeTab = tab; - - // Update filters. - this.updateFilters(); - this.build(); - } - } - - return true; - }); - - // Collect tooltips. - List tooltips = activeActions.stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); - - // Add tooltips. - if (!tooltips.isEmpty()) - { - // Empty line and tooltips. - builder.description(""); - builder.description(tooltips); - } - - builder.glow(this.activeTab == tab); - - return builder.build(); - } - - - /** - * Create next button panel item. - * - * @param template the template - * @param slot the slot - * @return the panel item - */ - private PanelItem createFilterButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) - { - PanelItemBuilder builder = new PanelItemBuilder(); - - if (template.icon() != null) - { - // Set icon - builder.icon(template.icon().clone()); - } - - Filter filter; - - if (slot.amountMap().getOrDefault("FILTER", 0) > 1) - { - filter = Enums.getIfPresent(Filter.class, String.valueOf(template.dataMap().get("filter"))).or(Filter.NAME); - } - else - { - filter = this.activeFilter; - } - - final String reference = "level.gui.buttons.filters."; - - if (template.title() != null) - { - // Set title - builder.name(this.user.getTranslation(this.world, template.title().replace("[filter]", filter.name().toLowerCase()))); - } - else - { - builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".name")); - } - - if (template.description() != null) - { - // Set description - builder.description(this.user.getTranslation(this.world, template.description().replace("[filter]", filter.name().toLowerCase()))); - } - else - { - builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".description")); - } - - // Get only possible actions, by removing all inactive ones. - List activeActions = new ArrayList<>(template.actions()); - - // Add Click handler - builder.clickHandler((panel, user, clickType, i) -> - { - for (ItemTemplateRecord.ActionRecords action : activeActions) - { - if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) - { - if ("UP".equalsIgnoreCase(action.actionType())) - { - this.activeFilter = Utils.getNextValue(Filter.values(), filter); - - // Update filters. - this.updateFilters(); - this.build(); - } - else if ("DOWN".equalsIgnoreCase(action.actionType())) - { - this.activeFilter = Utils.getPreviousValue(Filter.values(), filter); - - // Update filters. - this.updateFilters(); - this.build(); - } - else if ("SELECT".equalsIgnoreCase(action.actionType())) - { - this.activeFilter = filter; - - // Update filters. - this.updateFilters(); - this.build(); - } - } - } - - return true; - }); - - // Collect tooltips. - List tooltips = activeActions.stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); - - // Add tooltips. - if (!tooltips.isEmpty()) - { - // Empty line and tooltips. - builder.description(""); - builder.description(tooltips); - } - - builder.glow(this.activeFilter == filter); - - return builder.build(); - } - - - // --------------------------------------------------------------------- - // Section: Create common buttons - // --------------------------------------------------------------------- - - - /** - * Create next button panel item. - * - * @param template the template - * @param slot the slot - * @return the panel item - */ - private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) - { - long size = this.materialCountList.size(); - - if (size <= slot.amountMap().getOrDefault("BLOCK", 1) || - 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) - { - // There are no next elements - return null; - } - - int nextPageIndex = this.pageIndex + 2; - - PanelItemBuilder builder = new PanelItemBuilder(); - - if (template.icon() != null) - { - ItemStack clone = template.icon().clone(); - - if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) - { - clone.setAmount(nextPageIndex); - } - - builder.icon(clone); - } - - if (template.title() != null) - { - builder.name(this.user.getTranslation(this.world, template.title())); - } - - if (template.description() != null) - { - builder.description(this.user.getTranslation(this.world, template.description(), - TextVariables.NUMBER, String.valueOf(nextPageIndex))); - } - - // Add ClickHandler - builder.clickHandler((panel, user, clickType, i) -> - { - for (ItemTemplateRecord.ActionRecords action : template.actions()) - { - if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) && - "NEXT".equalsIgnoreCase(action.actionType())) - { - this.pageIndex++; - this.build(); - } - } - - // Always return true. - return true; - }); - - // Collect tooltips. - List tooltips = template.actions().stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); - - // Add tooltips. - if (!tooltips.isEmpty()) - { - // Empty line and tooltips. - builder.description(""); - builder.description(tooltips); - } - - return builder.build(); - } - - - /** - * Create previous button panel item. - * - * @param template the template - * @param slot the slot - * @return the panel item - */ - private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) - { - if (this.pageIndex == 0) - { - // There are no next elements - return null; - } - - int previousPageIndex = this.pageIndex; - - PanelItemBuilder builder = new PanelItemBuilder(); - - if (template.icon() != null) - { - ItemStack clone = template.icon().clone(); - - if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) - { - clone.setAmount(previousPageIndex); - } - - builder.icon(clone); - } - - if (template.title() != null) - { - builder.name(this.user.getTranslation(this.world, template.title())); - } - - if (template.description() != null) - { - builder.description(this.user.getTranslation(this.world, template.description(), - TextVariables.NUMBER, String.valueOf(previousPageIndex))); - } - - // Add ClickHandler - builder.clickHandler((panel, user, clickType, i) -> - { - for (ItemTemplateRecord.ActionRecords action : template.actions()) - { - if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) - && "PREVIOUS".equalsIgnoreCase(action.actionType())) - { - this.pageIndex--; - this.build(); - } - } - - // Always return true. - return true; - }); - - // Collect tooltips. - List tooltips = template.actions().stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); - - // Add tooltips. - if (!tooltips.isEmpty()) - { - // Empty line and tooltips. - builder.description(""); - builder.description(tooltips); - } - - return builder.build(); - } - - - // --------------------------------------------------------------------- - // Section: Create Material Button - // --------------------------------------------------------------------- - - - /** - * Create material button panel item. - * - * @param template the template - * @param slot the slot - * @return the panel item - */ - private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) - { - if (this.materialCountList.isEmpty()) - { - // Does not contain any generators. - return null; - } - - int index = this.pageIndex * slot.amountMap().getOrDefault("BLOCK", 1) + slot.slot(); - - if (index >= this.materialCountList.size()) - { - // Out of index. - return null; - } - - return this.createMaterialButton(template, this.materialCountList.get(index)); - } - - - /** - * This method creates button for material. - * - * @param template the template of the button - * @param materialCount materialCount which button must be created. - * @return PanelItem for generator tier. - */ - private PanelItem createMaterialButton(ItemTemplateRecord template, - Pair materialCount) - { - PanelItemBuilder builder = new PanelItemBuilder(); - - if (template.icon() != null) - { - builder.icon(template.icon().clone()); - } - else - { - builder.icon(PanelUtils.getMaterialItem(materialCount.getKey())); - } - - if (materialCount.getValue() < 64) - { - builder.amount(materialCount.getValue()); - } - - if (template.title() != null) - { - builder.name(this.user.getTranslation(this.world, template.title(), - TextVariables.NUMBER, String.valueOf(materialCount.getValue()), - "[material]", Utils.prettifyObject(materialCount.getKey(), this.user))); - } - - String description = Utils.prettifyDescription(materialCount.getKey(), this.user); - - final String reference = "level.gui.buttons.material."; - String blockId = this.user.getTranslationOrNothing(reference + "id", - "[id]", materialCount.getKey().name()); - - int blockValue = this.addon.getBlockConfig().getBlockValues().getOrDefault(materialCount.getKey(), 0); - String value = blockValue > 0 ? this.user.getTranslationOrNothing(reference + "value", - TextVariables.NUMBER, String.valueOf(blockValue)) : ""; - - int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(materialCount.getKey(), 0); - String limit = blockLimit > 0 ? this.user.getTranslationOrNothing(reference + "limit", - TextVariables.NUMBER, String.valueOf(blockLimit)) : ""; - - String count = this.user.getTranslationOrNothing(reference + "count", - TextVariables.NUMBER, String.valueOf(materialCount.getValue())); - - long calculatedValue = (long) Math.min(blockLimit > 0 ? blockLimit : Integer.MAX_VALUE, materialCount.getValue()) * blockValue; - String valueText = calculatedValue > 0 ? this.user.getTranslationOrNothing(reference + "calculated", - TextVariables.NUMBER, String.valueOf(calculatedValue)) : ""; - - if (template.description() != null) - { - builder.description(this.user.getTranslation(this.world, template.description(), - "[description]", description, - "[id]", blockId, - "[value]", value, - "[calculated]", valueText, - "[limit]", limit, - "[count]", count). - replaceAll("(?m)^[ \\t]*\\r?\\n", ""). - replaceAll("(?> materialCountList; - - /** - * This variable holds current pageIndex for multi-page generator choosing. - */ - private int pageIndex; - - /** - * This variable stores which tab currently is active. - */ - private Tab activeTab; - - /** - * This variable stores active filter for items. - */ - private Filter activeFilter; +public class DetailsPanel { + // --------------------------------------------------------------------- + // Section: Internal Constructor + // --------------------------------------------------------------------- + + /** + * This is internal constructor. It is used internally in current class to avoid + * creating objects everywhere. + * + * @param addon Level object + * @param world World where user is operating + * @param user User who opens panel + */ + private DetailsPanel(Level addon, World world, User user) { + this.addon = addon; + this.world = world; + this.user = user; + + this.island = this.addon.getIslands().getIsland(world, user); + + if (this.island != null) { + this.levelsData = this.addon.getManager().getLevelsData(this.island); + } else { + this.levelsData = null; + } + + // By default no-filters are active. + this.activeTab = Tab.ALL_BLOCKS; + this.activeFilter = Filter.NAME; + this.materialCountList = new ArrayList<>(Material.values().length); + + this.updateFilters(); + } + + /** + * This method builds this GUI. + */ + private void build() { + if (this.island == null || this.levelsData == null) { + // Nothing to see. + Utils.sendMessage(this.user, this.user.getTranslation("general.errors.no-island")); + return; + } + + if (this.levelsData.getMdCount().isEmpty() && this.levelsData.getUwCount().isEmpty()) { + // Nothing to see. + Utils.sendMessage(this.user, this.user.getTranslation("level.conversations.no-data")); + return; + } + + // Start building panel. + TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); + panelBuilder.user(this.user); + panelBuilder.world(this.user.getWorld()); + + panelBuilder.template("detail_panel", new File(this.addon.getDataFolder(), "panels")); + + panelBuilder.parameters("[name]", this.user.getName()); + + panelBuilder.registerTypeBuilder("NEXT", this::createNextButton); + panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton); + panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton); + + panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton); + + // Register tabs + panelBuilder.registerTypeBuilder("TAB", this::createTabButton); + + // Register unknown type builder. + panelBuilder.build(); + } + + /** + * This method updates filter of elements based on tabs. + */ + private void updateFilters() { + this.materialCountList.clear(); + + switch (this.activeTab) { + case ALL_BLOCKS -> { + Map materialCountMap = new EnumMap<>(Material.class); + + materialCountMap.putAll(this.levelsData.getMdCount()); + + // Add underwater blocks. + this.levelsData.getUwCount().forEach((material, count) -> materialCountMap.put(material, + materialCountMap.computeIfAbsent(material, key -> 0) + count)); + + materialCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey())) + .forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); + } + case ABOVE_SEA_LEVEL -> this.levelsData.getMdCount().entrySet().stream().sorted((Map.Entry.comparingByKey())) + .forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); + + case UNDERWATER -> this.levelsData.getUwCount().entrySet().stream().sorted((Map.Entry.comparingByKey())) + .forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()))); + + case SPAWNER -> { + int aboveWater = this.levelsData.getMdCount().getOrDefault(Material.SPAWNER, 0); + int underWater = this.levelsData.getUwCount().getOrDefault(Material.SPAWNER, 0); + + // TODO: spawners need some touch... + this.materialCountList.add(new Pair<>(Material.SPAWNER, underWater + aboveWater)); + } + } + + Comparator> sorter; + + switch (this.activeFilter) { + case COUNT -> { + sorter = (o1, o2) -> { + if (o1.getValue().equals(o2.getValue())) { + String o1Name = Utils.prettifyObject(o1.getKey(), this.user); + String o2Name = Utils.prettifyObject(o2.getKey(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + } else { + return Integer.compare(o2.getValue(), o1.getValue()); + } + }; + } + case VALUE -> { + sorter = (o1, o2) -> { + int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o1.getKey(), 0); + int o1Count = blockLimit > 0 ? Math.min(o1.getValue(), blockLimit) : o1.getValue(); + + blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o2.getKey(), 0); + int o2Count = blockLimit > 0 ? Math.min(o2.getValue(), blockLimit) : o2.getValue(); + + long o1Value = (long) o1Count + * this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0); + long o2Value = (long) o2Count + * this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0); + + if (o1Value == o2Value) { + String o1Name = Utils.prettifyObject(o1.getKey(), this.user); + String o2Name = Utils.prettifyObject(o2.getKey(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + } else { + return Long.compare(o2Value, o1Value); + } + }; + } + default -> { + sorter = (o1, o2) -> { + String o1Name = Utils.prettifyObject(o1.getKey(), this.user); + String o2Name = Utils.prettifyObject(o2.getKey(), this.user); + + return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name); + }; + } + } + + this.materialCountList.sort(sorter); + + this.pageIndex = 0; + } + + // --------------------------------------------------------------------- + // Section: Tab Button Type + // --------------------------------------------------------------------- + + /** + * Create tab button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createTabButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) { + // Set icon + builder.icon(template.icon().clone()); + } + + if (template.title() != null) { + // Set title + builder.name(this.user.getTranslation(this.world, template.title())); + } + + if (template.description() != null) { + // Set description + builder.description(this.user.getTranslation(this.world, template.description())); + } + + Tab tab = Enums.getIfPresent(Tab.class, String.valueOf(template.dataMap().get("tab"))).or(Tab.ALL_BLOCKS); + + // Get only possible actions, by removing all inactive ones. + List activeActions = new ArrayList<>(template.actions()); + + activeActions.removeIf(action -> "VIEW".equalsIgnoreCase(action.actionType()) && this.activeTab == tab); + + // Add Click handler + builder.clickHandler((panel, user, clickType, i) -> { + for (ItemTemplateRecord.ActionRecords action : activeActions) { + if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + && "VIEW".equalsIgnoreCase(action.actionType())) { + this.activeTab = tab; + + // Update filters. + this.updateFilters(); + this.build(); + } + } + + return true; + }); + + // Collect tooltips. + List tooltips = activeActions.stream().filter(action -> action.tooltip() != null) + .map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + builder.glow(this.activeTab == tab); + + return builder.build(); + } + + /** + * Create next button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createFilterButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) { + // Set icon + builder.icon(template.icon().clone()); + } + + Filter filter; + + if (slot.amountMap().getOrDefault("FILTER", 0) > 1) { + filter = Enums.getIfPresent(Filter.class, String.valueOf(template.dataMap().get("filter"))).or(Filter.NAME); + } else { + filter = this.activeFilter; + } + + final String reference = "level.gui.buttons.filters."; + + if (template.title() != null) { + // Set title + builder.name(this.user.getTranslation(this.world, + template.title().replace("[filter]", filter.name().toLowerCase()))); + } else { + builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".name")); + } + + if (template.description() != null) { + // Set description + builder.description(this.user.getTranslation(this.world, + template.description().replace("[filter]", filter.name().toLowerCase()))); + } else { + builder.name( + this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".description")); + } + + // Get only possible actions, by removing all inactive ones. + List activeActions = new ArrayList<>(template.actions()); + + // Add Click handler + builder.clickHandler((panel, user, clickType, i) -> { + for (ItemTemplateRecord.ActionRecords action : activeActions) { + if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) { + if ("UP".equalsIgnoreCase(action.actionType())) { + this.activeFilter = Utils.getNextValue(Filter.values(), filter); + + // Update filters. + this.updateFilters(); + this.build(); + } else if ("DOWN".equalsIgnoreCase(action.actionType())) { + this.activeFilter = Utils.getPreviousValue(Filter.values(), filter); + + // Update filters. + this.updateFilters(); + this.build(); + } else if ("SELECT".equalsIgnoreCase(action.actionType())) { + this.activeFilter = filter; + + // Update filters. + this.updateFilters(); + this.build(); + } + } + } + + return true; + }); + + // Collect tooltips. + List tooltips = activeActions.stream().filter(action -> action.tooltip() != null) + .map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + builder.glow(this.activeFilter == filter); + + return builder.build(); + } + + // --------------------------------------------------------------------- + // Section: Create common buttons + // --------------------------------------------------------------------- + + /** + * Create next button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + long size = this.materialCountList.size(); + + if (size <= slot.amountMap().getOrDefault("BLOCK", 1) + || 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) { + // There are no next elements + return null; + } + + int nextPageIndex = this.pageIndex + 2; + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) { + ItemStack clone = template.icon().clone(); + + if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) { + clone.setAmount(nextPageIndex); + } + + builder.icon(clone); + } + + if (template.title() != null) { + builder.name(this.user.getTranslation(this.world, template.title())); + } + + if (template.description() != null) { + builder.description(this.user.getTranslation(this.world, template.description(), TextVariables.NUMBER, + String.valueOf(nextPageIndex))); + } + + // Add ClickHandler + builder.clickHandler((panel, user, clickType, i) -> { + for (ItemTemplateRecord.ActionRecords action : template.actions()) { + if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + && "NEXT".equalsIgnoreCase(action.actionType())) { + this.pageIndex++; + this.build(); + } + } + + // Always return true. + return true; + }); + + // Collect tooltips. + List tooltips = template.actions().stream().filter(action -> action.tooltip() != null) + .map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); + } + + /** + * Create previous button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + if (this.pageIndex == 0) { + // There are no next elements + return null; + } + + int previousPageIndex = this.pageIndex; + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) { + ItemStack clone = template.icon().clone(); + + if (Boolean.TRUE.equals(template.dataMap().getOrDefault("indexing", false))) { + clone.setAmount(previousPageIndex); + } + + builder.icon(clone); + } + + if (template.title() != null) { + builder.name(this.user.getTranslation(this.world, template.title())); + } + + if (template.description() != null) { + builder.description(this.user.getTranslation(this.world, template.description(), TextVariables.NUMBER, + String.valueOf(previousPageIndex))); + } + + // Add ClickHandler + builder.clickHandler((panel, user, clickType, i) -> { + for (ItemTemplateRecord.ActionRecords action : template.actions()) { + if ((clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType())) + && "PREVIOUS".equalsIgnoreCase(action.actionType())) { + this.pageIndex--; + this.build(); + } + } + + // Always return true. + return true; + }); + + // Collect tooltips. + List tooltips = template.actions().stream().filter(action -> action.tooltip() != null) + .map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); + } + + // --------------------------------------------------------------------- + // Section: Create Material Button + // --------------------------------------------------------------------- + + /** + * Create material button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + if (this.materialCountList.isEmpty()) { + // Does not contain any generators. + return null; + } + + int index = this.pageIndex * slot.amountMap().getOrDefault("BLOCK", 1) + slot.slot(); + + if (index >= this.materialCountList.size()) { + // Out of index. + return null; + } + + return this.createMaterialButton(template, this.materialCountList.get(index)); + } + + /** + * This method creates button for material. + * + * @param template the template of the button + * @param materialCount materialCount which button must be created. + * @return PanelItem for generator tier. + */ + private PanelItem createMaterialButton(ItemTemplateRecord template, Pair materialCount) { + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) { + builder.icon(template.icon().clone()); + } else { + builder.icon(PanelUtils.getMaterialItem(materialCount.getKey())); + } + + if (materialCount.getValue() < 64) { + builder.amount(materialCount.getValue()); + } + + if (template.title() != null) { + builder.name(this.user.getTranslation(this.world, template.title(), TextVariables.NUMBER, + String.valueOf(materialCount.getValue()), "[material]", + Utils.prettifyObject(materialCount.getKey(), this.user))); + } + + String description = Utils.prettifyDescription(materialCount.getKey(), this.user); + + final String reference = "level.gui.buttons.material."; + String blockId = this.user.getTranslationOrNothing(reference + "id", "[id]", materialCount.getKey().name()); + + int blockValue = this.addon.getBlockConfig().getBlockValues().getOrDefault(materialCount.getKey(), 0); + String value = blockValue > 0 + ? this.user.getTranslationOrNothing(reference + "value", TextVariables.NUMBER, + String.valueOf(blockValue)) + : ""; + + int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(materialCount.getKey(), 0); + String limit = blockLimit > 0 + ? this.user.getTranslationOrNothing(reference + "limit", TextVariables.NUMBER, + String.valueOf(blockLimit)) + : ""; + + String count = this.user.getTranslationOrNothing(reference + "count", TextVariables.NUMBER, + String.valueOf(materialCount.getValue())); + + long calculatedValue = (long) Math.min(blockLimit > 0 ? blockLimit : Integer.MAX_VALUE, + materialCount.getValue()) * blockValue; + String valueText = calculatedValue > 0 ? this.user.getTranslationOrNothing(reference + "calculated", + TextVariables.NUMBER, String.valueOf(calculatedValue)) : ""; + + if (template.description() != null) { + builder.description(this.user + .getTranslation(this.world, template.description(), "[description]", description, "[id]", blockId, + "[value]", value, "[calculated]", valueText, "[limit]", limit, "[count]", count) + .replaceAll("(?m)^[ \\t]*\\r?\\n", "").replaceAll("(?> materialCountList; + + /** + * This variable holds current pageIndex for multi-page generator choosing. + */ + private int pageIndex; + + /** + * This variable stores which tab currently is active. + */ + private Tab activeTab; + + /** + * This variable stores active filter for items. + */ + private Filter activeFilter; } diff --git a/src/test/java/world/bentobox/level/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java index 5b18aa5..e077de0 100644 --- a/src/test/java/world/bentobox/level/LevelsManagerTest.java +++ b/src/test/java/world/bentobox/level/LevelsManagerTest.java @@ -70,65 +70,63 @@ * */ @RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, BentoBox.class, DatabaseSetup.class, PanelBuilder.class}) +@PrepareForTest({ Bukkit.class, BentoBox.class, DatabaseSetup.class, PanelBuilder.class }) public class LevelsManagerTest { - @Mock - private static AbstractDatabaseHandler handler; - @Mock - Level addon; - @Mock - private BentoBox plugin; - @Mock - private Settings pluginSettings; - - - // Class under test - private LevelsManager lm; - @Mock - private Island island; - @Mock - private Pipeliner pipeliner; - private CompletableFuture cf; - private UUID uuid; - @Mock - private World world; - @Mock - private Player player; - @Mock - private ConfigSettings settings; - @Mock - private User user; - @Mock - private PlayersManager pm; - @Mock - private Inventory inv; - @Mock - private IslandWorldManager iwm; - @Mock - private PluginManager pim; - @Mock - private IslandLevels levelsData; - @Mock - private IslandsManager im; - @Mock - private BukkitScheduler scheduler; - - - - @SuppressWarnings("unchecked") - @BeforeClass - public static void beforeClass() { - // This has to be done beforeClass otherwise the tests will interfere with each other - handler = mock(AbstractDatabaseHandler.class); - // Database - PowerMockito.mockStatic(DatabaseSetup.class); - DatabaseSetup dbSetup = mock(DatabaseSetup.class); - when(DatabaseSetup.getDatabase()).thenReturn(dbSetup); - when(dbSetup.getHandler(any())).thenReturn(handler); - } - - /** + @Mock + private static AbstractDatabaseHandler handler; + @Mock + Level addon; + @Mock + private BentoBox plugin; + @Mock + private Settings pluginSettings; + + // Class under test + private LevelsManager lm; + @Mock + private Island island; + @Mock + private Pipeliner pipeliner; + private CompletableFuture cf; + private UUID uuid; + @Mock + private World world; + @Mock + private Player player; + @Mock + private ConfigSettings settings; + @Mock + private User user; + @Mock + private PlayersManager pm; + @Mock + private Inventory inv; + @Mock + private IslandWorldManager iwm; + @Mock + private PluginManager pim; + @Mock + private IslandLevels levelsData; + @Mock + private IslandsManager im; + @Mock + private BukkitScheduler scheduler; + + @SuppressWarnings("unchecked") + @BeforeClass + public static void beforeClass() { + // This has to be done beforeClass otherwise the tests will interfere with each + // other + handler = mock(AbstractDatabaseHandler.class); + // Database + PowerMockito.mockStatic(DatabaseSetup.class); + DatabaseSetup dbSetup = mock(DatabaseSetup.class); + when(DatabaseSetup.getDatabase()).thenReturn(dbSetup); + when(dbSetup.getHandler(any())).thenReturn(handler); + } + + /** * @throws java.lang.Exception */ @SuppressWarnings("unchecked") @@ -164,8 +162,7 @@ public void setUp() throws Exception { when(island.getWorld()).thenReturn(world); when(island.getUniqueId()).thenReturn(UUID.randomUUID().toString()); // Default to uuid's being island owners - when(im.isOwner(eq(world), any())).thenReturn(true); - when(im.getOwner(any(), any(UUID.class))).thenAnswer(in -> in.getArgument(1, UUID.class)); + when(im.hasIsland(eq(world), any(UUID.class))).thenReturn(true); when(im.getIsland(world, uuid)).thenReturn(island); when(im.getIslandById(anyString())).thenReturn(Optional.of(island)); @@ -235,220 +232,229 @@ public void setUp() throws Exception { lm.migrate(); } - /** - * @throws java.lang.Exception - */ - @After - public void tearDown() throws Exception { - deleteAll(new File("database")); - User.clearUsers(); - Mockito.framework().clearInlineMocks(); - } - - private static void deleteAll(File file) throws IOException { - if (file.exists()) { - Files.walk(file.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}. - */ - @Test - public void testCalculateLevel() { - Results results = new Results(); - results.setLevel(10000); - results.setInitialLevel(3); - lm.calculateLevel(uuid, island); - // Complete the pipelined completable future - cf.complete(results); - - assertEquals(10000L, lm.getLevelsData(island).getLevel()); - //Map tt = lm.getTopTen(world, 10); - //assertEquals(1, tt.size()); - //assertTrue(tt.get(uuid) == 10000); - assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); - - results.setLevel(5000); - lm.calculateLevel(uuid, island); - // Complete the pipelined completable future - cf.complete(results); - assertEquals(5000L, lm.getLevelsData(island).getLevel()); - // Still should be 10000 - assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); - - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}. - */ - @Test - public void testGetInitialLevel() { - assertEquals(0,lm.getInitialLevel(island)); - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testGetIslandLevel() { - assertEquals(-5, lm.getIslandLevel(world, uuid)); - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testGetPointsToNextString() { - // No island player - assertEquals("", lm.getPointsToNextString(world, UUID.randomUUID())); - // Player has island - assertEquals("0", lm.getPointsToNextString(world, uuid)); - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testGetIslandLevelString() { - assertEquals("-5", lm.getIslandLevelString(world, uuid)); - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}. - */ - @Test - public void testGetLevelsData() { - assertEquals(levelsData, lm.getLevelsData(island)); - - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#formatLevel(long)}. - */ - @Test - public void testFormatLevel() { - assertEquals("123456789", lm.formatLevel(123456789L)); - when(settings.isShorthand()).thenReturn(true); - assertEquals("123.5M", lm.formatLevel(123456789L)); - assertEquals("1.2k", lm.formatLevel(1234L)); - assertEquals("123.5G", lm.formatLevel(123456789352L)); - assertEquals("1.2T", lm.formatLevel(1234567893524L)); - assertEquals("12345.7T", lm.formatLevel(12345678345345349L)); - - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. - */ - @Test - public void testGetTopTenEmpty() { - Map tt = lm.getTopTen(world, Level.TEN); - assertTrue(tt.isEmpty()); - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. - */ - @Test - public void testGetTopTen() { - testLoadTopTens(); - Map tt = lm.getTopTen(world, Level.TEN); - assertFalse(tt.isEmpty()); - assertEquals(1, tt.size()); - assertEquals(1, lm.getTopTen(world, 1).size()); - } - - /** + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + deleteAll(new File("database")); + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + } + + private static void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}. + */ + @Test + public void testCalculateLevel() { + Results results = new Results(); + results.setLevel(10000); + results.setInitialLevel(3); + lm.calculateLevel(uuid, island); + // Complete the pipelined completable future + cf.complete(results); + + assertEquals(10000L, lm.getLevelsData(island).getLevel()); + // Map tt = lm.getTopTen(world, 10); + // assertEquals(1, tt.size()); + // assertTrue(tt.get(uuid) == 10000); + assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); + + results.setLevel(5000); + lm.calculateLevel(uuid, island); + // Complete the pipelined completable future + cf.complete(results); + assertEquals(5000L, lm.getLevelsData(island).getLevel()); + // Still should be 10000 + assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); + + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}. + */ + @Test + public void testGetInitialLevel() { + assertEquals(0, lm.getInitialLevel(island)); + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testGetIslandLevel() { + assertEquals(-5, lm.getIslandLevel(world, uuid)); + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testGetPointsToNextString() { + // No island player + assertEquals("", lm.getPointsToNextString(world, UUID.randomUUID())); + // Player has island + assertEquals("0", lm.getPointsToNextString(world, uuid)); + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testGetIslandLevelString() { + assertEquals("-5", lm.getIslandLevelString(world, uuid)); + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}. + */ + @Test + public void testGetLevelsData() { + assertEquals(levelsData, lm.getLevelsData(island)); + + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#formatLevel(long)}. + */ + @Test + public void testFormatLevel() { + assertEquals("123456789", lm.formatLevel(123456789L)); + when(settings.isShorthand()).thenReturn(true); + assertEquals("123.5M", lm.formatLevel(123456789L)); + assertEquals("1.2k", lm.formatLevel(1234L)); + assertEquals("123.5G", lm.formatLevel(123456789352L)); + assertEquals("1.2T", lm.formatLevel(1234567893524L)); + assertEquals("12345.7T", lm.formatLevel(12345678345345349L)); + + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. + */ + @Test + public void testGetTopTenEmpty() { + Map tt = lm.getTopTen(world, Level.TEN); + assertTrue(tt.isEmpty()); + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. + */ + @Test + public void testGetTopTen() { + testLoadTopTens(); + Map tt = lm.getTopTen(world, Level.TEN); + assertFalse(tt.isEmpty()); + assertEquals(1, tt.size()); + assertEquals(1, lm.getTopTen(world, 1).size()); + } + + /** * Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. */ @Test public void testGetTopTenNoOwners() { - when(im.isOwner(eq(world), any())).thenReturn(false); + when(im.hasIsland(eq(world), any(UUID.class))).thenReturn(false); testLoadTopTens(); Map tt = lm.getTopTen(world, Level.TEN); assertTrue(tt.isEmpty()); } - /** - * Test method for {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testHasTopTenPerm() { - assertTrue(lm.hasTopTenPerm(world, uuid)); - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#loadTopTens()}. - */ - @Test - public void testLoadTopTens() { - ArgumentCaptor task = ArgumentCaptor.forClass(Runnable.class); - lm.loadTopTens(); - PowerMockito.verifyStatic(Bukkit.class); // 1 - Bukkit.getScheduler(); - verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture()); - task.getValue().run(); - verify(addon).log("Generating rankings"); - verify(addon).log("Generated rankings for bskyblock-world"); - - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testRemoveEntry() { - testLoadTopTens(); - Map tt = lm.getTopTen(world, Level.TEN); - assertTrue(tt.containsKey(uuid)); - lm.removeEntry(world, uuid); - tt = lm.getTopTen(world, Level.TEN); - assertFalse(tt.containsKey(uuid)); - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}. - */ - @Test - public void testSetInitialIslandLevel() { - lm.setInitialIslandLevel(island, Level.TEN); - assertEquals(Level.TEN, lm.getInitialLevel(island)); - } - - /** - * Test method for {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}. - */ - @Test - public void testSetIslandLevel() { - lm.setIslandLevel(world, uuid, 1234); - assertEquals(1234, lm.getIslandLevel(world, uuid)); - - } - - - /** - * Test method for {@link world.bentobox.level.LevelsManager#getRank(World, UUID)} - */ - @Test - public void testGetRank() { - lm.createAndCleanRankings(world); - Map ttl = lm.getTopTenLists(); - Map tt = ttl.get(world).getTopTen(); - for (long i = 100; i < 150; i++) { - tt.put(UUID.randomUUID(), i); - } - // Put player as lowest rank - tt.put(uuid, 10L); - assertEquals(51, lm.getRank(world, uuid)); - // Put player as highest rank - tt.put(uuid, 1000L); - assertEquals(1, lm.getRank(world, uuid)); - // Unknown UUID - lowest rank + 1 - assertEquals(52, lm.getRank(world, UUID.randomUUID())); - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testHasTopTenPerm() { + assertTrue(lm.hasTopTenPerm(world, uuid)); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#loadTopTens()}. + */ + @Test + public void testLoadTopTens() { + ArgumentCaptor task = ArgumentCaptor.forClass(Runnable.class); + lm.loadTopTens(); + PowerMockito.verifyStatic(Bukkit.class); // 1 + Bukkit.getScheduler(); + verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture()); + task.getValue().run(); + verify(addon).log("Generating rankings"); + verify(addon).log("Generated rankings for bskyblock-world"); + + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testRemoveEntry() { + testLoadTopTens(); + Map tt = lm.getTopTen(world, Level.TEN); + assertTrue(tt.containsKey(uuid)); + lm.removeEntry(world, uuid); + tt = lm.getTopTen(world, Level.TEN); + assertFalse(tt.containsKey(uuid)); + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}. + */ + @Test + public void testSetInitialIslandLevel() { + lm.setInitialIslandLevel(island, Level.TEN); + assertEquals(Level.TEN, lm.getInitialLevel(island)); + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}. + */ + @Test + public void testSetIslandLevel() { + lm.setIslandLevel(world, uuid, 1234); + assertEquals(1234, lm.getIslandLevel(world, uuid)); + + } + + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getRank(World, UUID)} + */ + @Test + public void testGetRank() { + lm.createAndCleanRankings(world); + Map ttl = lm.getTopTenLists(); + Map tt = ttl.get(world).getTopTen(); + for (long i = 100; i < 150; i++) { + tt.put(UUID.randomUUID(), i); + } + // Put player as lowest rank + tt.put(uuid, 10L); + assertEquals(51, lm.getRank(world, uuid)); + // Put player as highest rank + tt.put(uuid, 1000L); + assertEquals(1, lm.getRank(world, uuid)); + // Unknown UUID - lowest rank + 1 + assertEquals(52, lm.getRank(world, UUID.randomUUID())); + } } From 43c898ecf704b2c560e43f5ac9a49a55f6dd1832 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 18 Nov 2023 15:22:05 -0800 Subject: [PATCH 079/106] Added test for Stats command --- .../level/commands/AdminStatsCommand.java | 7 +- .../level/commands/AdminStatsCommandTest.java | 183 ++++++++++++++++ .../commands/AdminTopRemoveCommandTest.java | 200 ++++++++++++++++++ .../admin/AdminTopRemoveCommandTest.java | 199 ----------------- 4 files changed, 389 insertions(+), 200 deletions(-) create mode 100644 src/test/java/world/bentobox/level/commands/AdminStatsCommandTest.java create mode 100644 src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java delete mode 100644 src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java diff --git a/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java b/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java index 3399a1f..78d5a05 100644 --- a/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java +++ b/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java @@ -37,7 +37,12 @@ public void setup() { @Override public boolean execute(User user, String label, List args) { user.sendMessage("admin.stats.title"); - for (Entry en : level.getManager().getTopTenLists().entrySet()) { + Map topTenLists = level.getManager().getTopTenLists(); + if (topTenLists.isEmpty()) { + user.sendMessage("admin.stats.no-data"); + return false; + } + for (Entry en : topTenLists.entrySet()) { user.sendMessage("admin.stats.world", TextVariables.NAME, level.getPlugin().getIWM().getWorldName(en.getKey())); Map topTen = en.getValue().getTopTen(); diff --git a/src/test/java/world/bentobox/level/commands/AdminStatsCommandTest.java b/src/test/java/world/bentobox/level/commands/AdminStatsCommandTest.java new file mode 100644 index 0000000..f00cd24 --- /dev/null +++ b/src/test/java/world/bentobox/level/commands/AdminStatsCommandTest.java @@ -0,0 +1,183 @@ +package world.bentobox.level.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.LocalesManager; +import world.bentobox.bentobox.managers.PlayersManager; +import world.bentobox.level.Level; +import world.bentobox.level.LevelsManager; +import world.bentobox.level.objects.TopTenData; + +/** + * @author tastybento + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Bukkit.class, BentoBox.class }) +public class AdminStatsCommandTest { + + @Mock + private CompositeCommand ic; + private UUID uuid; + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private Level addon; + @Mock + private World world; + @Mock + private IslandWorldManager iwm; + @Mock + private GameModeAddon gameModeAddon; + @Mock + private Player p; + @Mock + private LocalesManager lm; + @Mock + private PlayersManager pm; + + private AdminStatsCommand asc; + private TopTenData ttd; + @Mock + private LevelsManager manager; + @Mock + private Server server; + + @Before + public void setUp() { + // Set up plugin + BentoBox plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + User.setPlugin(plugin); + when(addon.getPlugin()).thenReturn(plugin); + + // Addon + when(ic.getAddon()).thenReturn(addon); + when(ic.getPermissionPrefix()).thenReturn("bskyblock."); + when(ic.getLabel()).thenReturn("island"); + when(ic.getTopLabel()).thenReturn("island"); + when(ic.getWorld()).thenReturn(world); + when(ic.getTopLabel()).thenReturn("bsb"); + + // IWM friendly name + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); + + // World + when(world.toString()).thenReturn("world"); + when(world.getName()).thenReturn("BSkyBlock_world"); + + // Player manager + when(plugin.getPlayers()).thenReturn(pm); + when(pm.getUser(anyString())).thenReturn(user); + // topTen + when(addon.getManager()).thenReturn(manager); + // User + uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); + + // Bukkit + PowerMockito.mockStatic(Bukkit.class); + when(Bukkit.getServer()).thenReturn(server); + // Mock item factory (for itemstacks) + ItemFactory itemFactory = mock(ItemFactory.class); + ItemMeta itemMeta = mock(ItemMeta.class); + when(itemFactory.getItemMeta(any())).thenReturn(itemMeta); + when(server.getItemFactory()).thenReturn(itemFactory); + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + + // Top ten + ttd = new TopTenData(world); + Map topten = new HashMap<>(); + Random r = new Random(); + for (int i = 0; i < 1000; i++) { + topten.put(UUID.randomUUID(), r.nextLong(20000)); + } + ttd.setTopTen(topten); + asc = new AdminStatsCommand(addon, ic); + } + + @After + public void tearDown() { + User.clearUsers(); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.AdminStatsCommand#setup()}. + */ + @Test + public void testSetup() { + assertEquals("bskyblock.admin.stats", asc.getPermission()); + assertFalse(asc.isOnlyPlayer()); + assertEquals("admin.stats.description", asc.getDescription()); + + } + + /** + * Test method for + * {@link world.bentobox.level.commands.AdminStatsCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfString() { + assertFalse(asc.execute(user, "", List.of())); + verify(user).sendMessage("admin.stats.title"); + verify(user).sendMessage("admin.stats.no-data"); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.AdminStatsCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringLevels() { + Map map = new HashMap<>(); + map.put(world, ttd); + when(manager.getTopTenLists()).thenReturn(map); + assertTrue(asc.execute(user, "", List.of())); + verify(user).sendMessage("admin.stats.title"); + verify(user, never()).sendMessage("admin.stats.no-data"); + } + +} diff --git a/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java b/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java new file mode 100644 index 0000000..af38901 --- /dev/null +++ b/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java @@ -0,0 +1,200 @@ +package world.bentobox.level.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.LocalesManager; +import world.bentobox.bentobox.managers.PlayersManager; +import world.bentobox.level.Level; +import world.bentobox.level.LevelsManager; +import world.bentobox.level.objects.TopTenData; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Bukkit.class, BentoBox.class }) +public class AdminTopRemoveCommandTest { + + @Mock + private CompositeCommand ic; + private UUID uuid; + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private Level addon; + @Mock + private World world; + @Mock + private IslandWorldManager iwm; + @Mock + private GameModeAddon gameModeAddon; + @Mock + private Player p; + @Mock + private LocalesManager lm; + @Mock + private PlayersManager pm; + + private AdminTopRemoveCommand atrc; + @Mock + private TopTenData ttd; + @Mock + private LevelsManager manager; + @Mock + private Server server; + + @Before + public void setUp() { + // Set up plugin + BentoBox plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + User.setPlugin(plugin); + // Addon + when(ic.getAddon()).thenReturn(addon); + when(ic.getPermissionPrefix()).thenReturn("bskyblock."); + when(ic.getLabel()).thenReturn("island"); + when(ic.getTopLabel()).thenReturn("island"); + when(ic.getWorld()).thenReturn(world); + when(ic.getTopLabel()).thenReturn("bsb"); + + // IWM friendly name + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); + + // World + when(world.toString()).thenReturn("world"); + when(world.getName()).thenReturn("BSkyBlock_world"); + + // Player manager + when(plugin.getPlayers()).thenReturn(pm); + when(pm.getUser(anyString())).thenReturn(user); + // topTen + when(addon.getManager()).thenReturn(manager); + // User + uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); + + // Bukkit + PowerMockito.mockStatic(Bukkit.class); + when(Bukkit.getServer()).thenReturn(server); + // Mock item factory (for itemstacks) + ItemFactory itemFactory = mock(ItemFactory.class); + ItemMeta itemMeta = mock(ItemMeta.class); + when(itemFactory.getItemMeta(any())).thenReturn(itemMeta); + when(server.getItemFactory()).thenReturn(itemFactory); + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + + atrc = new AdminTopRemoveCommand(addon, ic); + } + + @After + public void tearDown() { + User.clearUsers(); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#AdminTopRemoveCommand(world.bentobox.level.Level, world.bentobox.bentobox.api.commands.CompositeCommand)}. + */ + @Test + public void testAdminTopRemoveCommand() { + assertEquals("remove", atrc.getLabel()); + assertEquals("delete", atrc.getAliases().get(0)); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#setup()}. + */ + @Test + public void testSetup() { + assertEquals("bskyblock.admin.top.remove", atrc.getPermission()); + assertEquals("admin.top.remove.parameters", atrc.getParameters()); + assertEquals("admin.top.remove.description", atrc.getDescription()); + assertFalse(atrc.isOnlyPlayer()); + + } + + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteWrongArgs() { + assertFalse(atrc.canExecute(user, "delete", Collections.emptyList())); + verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "BSkyBlock"); + } + + /** + * Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteUnknown() { + when(pm.getUser(anyString())).thenReturn(null); + assertFalse(atrc.canExecute(user, "delete", Collections.singletonList("tastybento"))); + verify(user).sendMessage("general.errors.unknown-player", TextVariables.NAME, "tastybento"); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteKnown() { + assertTrue(atrc.canExecute(user, "delete", Collections.singletonList("tastybento"))); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfString() { + testCanExecuteKnown(); + assertTrue(atrc.execute(user, "delete", Collections.singletonList("tastybento"))); + verify(manager).removeEntry(any(World.class), eq(uuid)); + verify(user).sendMessage("general.success"); + } + +} diff --git a/src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java b/src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java deleted file mode 100644 index 71070a4..0000000 --- a/src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java +++ /dev/null @@ -1,199 +0,0 @@ -package world.bentobox.level.commands.admin; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Collections; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.inventory.meta.ItemMeta; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.reflect.Whitebox; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.addons.GameModeAddon; -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.localization.TextVariables; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.managers.IslandWorldManager; -import world.bentobox.bentobox.managers.IslandsManager; -import world.bentobox.bentobox.managers.LocalesManager; -import world.bentobox.bentobox.managers.PlayersManager; -import world.bentobox.level.Level; -import world.bentobox.level.LevelsManager; -import world.bentobox.level.commands.AdminTopRemoveCommand; -import world.bentobox.level.objects.TopTenData; - -/** - * @author tastybento - * - */ -@RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, BentoBox.class}) -public class AdminTopRemoveCommandTest { - - @Mock - private CompositeCommand ic; - private UUID uuid; - @Mock - private User user; - @Mock - private IslandsManager im; - @Mock - private Island island; - @Mock - private Level addon; - @Mock - private World world; - @Mock - private IslandWorldManager iwm; - @Mock - private GameModeAddon gameModeAddon; - @Mock - private Player p; - @Mock - private LocalesManager lm; - @Mock - private PlayersManager pm; - - private AdminTopRemoveCommand atrc; - @Mock - private TopTenData ttd; - @Mock - private LevelsManager manager; - @Mock - private Server server; - - @Before - public void setUp() { - // Set up plugin - BentoBox plugin = mock(BentoBox.class); - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - User.setPlugin(plugin); - // Addon - when(ic.getAddon()).thenReturn(addon); - when(ic.getPermissionPrefix()).thenReturn("bskyblock."); - when(ic.getLabel()).thenReturn("island"); - when(ic.getTopLabel()).thenReturn("island"); - when(ic.getWorld()).thenReturn(world); - when(ic.getTopLabel()).thenReturn("bsb"); - - - // IWM friendly name - when(plugin.getIWM()).thenReturn(iwm); - when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); - - // World - when(world.toString()).thenReturn("world"); - when(world.getName()).thenReturn("BSkyBlock_world"); - - - // Player manager - when(plugin.getPlayers()).thenReturn(pm); - when(pm.getUser(anyString())).thenReturn(user); - // topTen - when(addon.getManager()).thenReturn(manager); - // User - uuid = UUID.randomUUID(); - when(user.getUniqueId()).thenReturn(uuid); - when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); - - // Bukkit - PowerMockito.mockStatic(Bukkit.class); - when(Bukkit.getServer()).thenReturn(server); - // Mock item factory (for itemstacks) - ItemFactory itemFactory = mock(ItemFactory.class); - ItemMeta itemMeta = mock(ItemMeta.class); - when(itemFactory.getItemMeta(any())).thenReturn(itemMeta); - when(server.getItemFactory()).thenReturn(itemFactory); - when(Bukkit.getItemFactory()).thenReturn(itemFactory); - - - atrc = new AdminTopRemoveCommand(addon, ic); - } - - @After - public void tearDown() { - User.clearUsers(); - } - - /** - * Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#AdminTopRemoveCommand(world.bentobox.level.Level, world.bentobox.bentobox.api.commands.CompositeCommand)}. - */ - @Test - public void testAdminTopRemoveCommand() { - assertEquals("remove", atrc.getLabel()); - assertEquals("delete", atrc.getAliases().get(0)); - } - - /** - * Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#setup()}. - */ - @Test - public void testSetup() { - assertEquals("bskyblock.admin.top.remove", atrc.getPermission()); - assertEquals("admin.top.remove.parameters", atrc.getParameters()); - assertEquals("admin.top.remove.description", atrc.getDescription()); - assertFalse(atrc.isOnlyPlayer()); - - } - - /** - * Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testCanExecuteWrongArgs() { - assertFalse(atrc.canExecute(user, "delete", Collections.emptyList())); - verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "BSkyBlock"); - } - - /** - * Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testCanExecuteUnknown() { - when(pm.getUser(anyString())).thenReturn(null); - assertFalse(atrc.canExecute(user, "delete", Collections.singletonList("tastybento"))); - verify(user).sendMessage("general.errors.unknown-player", TextVariables.NAME, "tastybento"); - } - - /** - * Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testCanExecuteKnown() { - assertTrue(atrc.canExecute(user, "delete", Collections.singletonList("tastybento"))); - } - - /** - * Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testExecuteUserStringListOfString() { - testCanExecuteKnown(); - assertTrue(atrc.execute(user, "delete", Collections.singletonList("tastybento"))); - verify(manager).removeEntry(any(World.class), eq(uuid)); - verify(user).sendMessage("general.success"); - } - -} From 26d4839f6a82b21bcd7ff889dfb6f1b02da38f14 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 18 Nov 2023 15:26:33 -0800 Subject: [PATCH 080/106] Try lower version of jacoco --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0bab5c3..46b6078 100644 --- a/pom.xml +++ b/pom.xml @@ -403,7 +403,7 @@ org.jacoco jacoco-maven-plugin - 0.8.11 + 0.8.10 true From 9d1a5c7476a5bfb0110270815e255d8a2fd7d7e1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 19 Nov 2023 18:08:47 -0800 Subject: [PATCH 081/106] Changed top ten internally to use islands instead of players as keys (#295) Added %[gamemode]_top_weighted_value_x% placeholder https://github.com/BentoBoxWorld/Level/issues/294 --- .../world/bentobox/level/LevelsManager.java | 853 +++++++++--------- .../bentobox/level/PlaceholderManager.java | 246 ++--- .../level/commands/AdminStatsCommand.java | 133 ++- .../level/commands/AdminTopCommand.java | 44 +- .../level/commands/AdminTopRemoveCommand.java | 55 +- .../listeners/IslandActivitiesListeners.java | 80 +- .../bentobox/level/objects/TopTenData.java | 33 +- .../bentobox/level/panels/TopLevelPanel.java | 694 +++++++------- .../bentobox/level/LevelsManagerTest.java | 493 +++++----- .../level/PlaceholderManagerTest.java | 222 +++-- .../level/commands/AdminStatsCommandTest.java | 252 +++--- .../commands/AdminTopRemoveCommandTest.java | 282 +++--- 12 files changed, 1662 insertions(+), 1725 deletions(-) diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index fa8083d..e9193d7 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -32,452 +32,429 @@ import world.bentobox.level.objects.TopTenData; public class LevelsManager { - private static final String INTOPTEN = "intopten"; - private static final TreeMap LEVELS; - private static final BigInteger THOUSAND = BigInteger.valueOf(1000); - static { - LEVELS = new TreeMap<>(); - - LEVELS.put(THOUSAND, "k"); - LEVELS.put(THOUSAND.pow(2), "M"); - LEVELS.put(THOUSAND.pow(3), "G"); - LEVELS.put(THOUSAND.pow(4), "T"); - } - private final Level addon; - - // Database handler for level data - private final Database handler; - // A cache of island levels. - private final Map levelsCache; - // Top ten lists - private final Map topTenLists; - - public LevelsManager(Level addon) { - this.addon = addon; - // Get the BentoBox database - // Set up the database handler to store and retrieve data - // Note that these are saved by the BentoBox database - handler = new Database<>(addon, IslandLevels.class); - // Initialize the cache - levelsCache = new HashMap<>(); - // Initialize top ten lists - topTenLists = new ConcurrentHashMap<>(); - } - - 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; - } - }); - } - - /** - * Add a score to the top players list - * - * @param world - world - * @param targetPlayer - target player - * @param lv - island level - */ - private void addToTopTen(@NonNull World world, @NonNull UUID targetPlayer, long lv) { - // Get top ten - Map topTen = topTenLists.computeIfAbsent(world, k -> new TopTenData(world)).getTopTen(); - // Remove this player from the top list no matter what (we'll put them back - // later if required) - topTen.remove(targetPlayer); - - // Get the island - Island island = addon.getIslands().getIsland(world, targetPlayer); - if (island != null && island.getOwner() != null && hasTopTenPerm(world, island.getOwner())) { - // Insert the owner into the top ten - topTen.put(island.getOwner(), lv); - } - } - - /** - * Add an island to a top ten - * - * @param island - island to add - * @param lv - level - * @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.getOwner(), lv); - return true; - } - return false; - } - - /** - * Calculate the island level, set all island member's levels to the result and - * try to add the owner to the top ten - * - * @param targetPlayer - uuid of targeted player - owner or team member - * @param island - island to calculate - * @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.getWorld(), island.getOwner(), r); - // Save the island scan details - result.complete(r); - }); - return result; - } - - /** - * Fires the IslandLevelCalculatedEvent and returns true if it is canceled - * - * @param targetPlayer - target player - * @param island - island - * @param results - results set - * @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)); - } - - /** - * Get the string representation of the level. May be converted to shorthand - * notation, e.g., 104556 = 10.5k - * - * @param lvl - long value to represent - * @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); - - 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; - } - - /** - * Get the initial level of the island. Used to zero island levels - * - * @param island - island - * @return initial level of island - */ - public long getInitialLevel(Island island) { - return getLevelsData(island).getInitialLevel(); - } - - /** - * Get level of island from cache for a player. - * - * @param world - world where the island is - * @param targetPlayer - target player UUID - * @return Level of the player's island or zero if player is unknown or UUID is - * 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(); - } - - /** - * Get the maximum level ever given to this island - * - * @param world - world where the island is - * @param targetPlayer - target player UUID - * @return Max level of the player's island or zero if player is unknown or UUID - * 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(); - } - - /** - * Returns a formatted string of the target player's island level - * - * @param world - world where the island is - * @param targetPlayer - target player's UUID - * @return Formatted level of player or zero if player is unknown or UUID is - * null - */ - public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { - return formatLevel(getIslandLevel(world, targetPlayer)); - } - - /** - * Load a level data for the island from the cache or database. - * - * @param island - UUID of island - * @return IslandLevels object - */ - @NonNull - public IslandLevels getLevelsData(@NonNull Island island) { - String id = island.getUniqueId(); - if (levelsCache.containsKey(id)) { - return levelsCache.get(id); - } - // Get from database if not in cache - if (handler.objectExists(id)) { - IslandLevels ld = handler.loadObject(id); - if (ld != null) { - levelsCache.put(id, ld); - } else { - handler.deleteID(id); - levelsCache.put(id, new IslandLevels(id)); - } - } else { - levelsCache.put(id, new IslandLevels(id)); - } - // Return cached value - return levelsCache.get(id); - } - - /** - * Get the number of points required until the next level since the last level - * calc - * - * @param world - world where the island is - * @param targetPlayer - target player UUID - * @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()); - } - - /** - * Get the top ten for this world. Returns offline players or players with the - * intopten permission. - * - * @param world - world requested - * @param size - size of the top ten - * @return sorted top ten map - */ - @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(e -> addon.getIslands().hasIsland(world, e.getKey())).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 -> !hasTopTenPerm(world, u)); - } - - /** - * @return the topTenLists - */ - public Map getTopTenLists() { - return topTenLists; - } - - /** - * Get the rank of the player in the rankings - * - * @param world - world - * @param uuid - player UUID - * @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(e -> addon.getIslands().hasIsland(world, e.getKey())).filter(l -> l.getValue() > 0) - .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); - return (int) (stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).count() + 1); + private static final String INTOPTEN = "intopten"; + private static final TreeMap LEVELS; + private static final BigInteger THOUSAND = BigInteger.valueOf(1000); + static { + LEVELS = new TreeMap<>(); + + LEVELS.put(THOUSAND, "k"); + LEVELS.put(THOUSAND.pow(2), "M"); + LEVELS.put(THOUSAND.pow(3), "G"); + LEVELS.put(THOUSAND.pow(4), "T"); + } + private final Level addon; + + // Database handler for level data + private final Database handler; + // A cache of island levels. + private final Map levelsCache; + // Top ten lists + private final Map topTenLists; + + public LevelsManager(Level addon) { + this.addon = addon; + // Get the BentoBox database + // Set up the database handler to store and retrieve data + // Note that these are saved by the BentoBox database + handler = new Database<>(addon, IslandLevels.class); + // Initialize the cache + levelsCache = new HashMap<>(); + // Initialize top ten lists + topTenLists = new ConcurrentHashMap<>(); + } + + 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; + } + }); + } + + /** + * Add an island to a top ten + * + * @param island - island to add + * @param lv - level + * @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; } - - /** - * Checks if player has the correct top ten perm to have their level saved - * - * @param world - * @param targetPlayer - * @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); + return false; + } + + /** + * Calculate the island level, set all island member's levels to the result and + * try to add the owner to the top ten + * + * @param targetPlayer - uuid of targeted player - owner or team member + * @param island - island to calculate + * @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); } - - /** - * 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())); - }); + // 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; + } + + /** + * Fires the IslandLevelCalculatedEvent and returns true if it is canceled + * + * @param targetPlayer - target player + * @param island - island + * @param results - results set + * @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)); + } + + /** + * Get the string representation of the level. May be converted to shorthand + * notation, e.g., 104556 = 10.5k + * + * @param lvl - long value to represent + * @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); + + 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(); + } } - - /** - * Removes a player from a world's top ten and removes world from player's level - * data - * - * @param world - world - * @param uuid - the player's uuid - */ - public void removeEntry(World world, UUID uuid) { - if (topTenLists.containsKey(world)) { - topTenLists.get(world).getTopTen().remove(uuid); - } - + return level; + } + + /** + * Get the initial level of the island. Used to zero island levels + * + * @param island - island + * @return initial level of island + */ + public long getInitialLevel(Island island) { + return getLevelsData(island).getInitialLevel(); + } + + /** + * Get level of island from cache for a player. + * + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return Level of the player's island or zero if player is unknown or UUID is + * 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(); + } + + /** + * Get the maximum level ever given to this island + * + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return Max level of the player's island or zero if player is unknown or UUID + * 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(); + } + + /** + * Returns a formatted string of the target player's island level + * + * @param world - world where the island is + * @param targetPlayer - target player's UUID + * @return Formatted level of player or zero if player is unknown or UUID is + * null + */ + public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { + return formatLevel(getIslandLevel(world, targetPlayer)); + } + + /** + * Load a level data for the island from the cache or database. + * + * @param island - UUID of island + * @return IslandLevels object + */ + @NonNull + public IslandLevels getLevelsData(@NonNull Island island) { + String id = island.getUniqueId(); + if (levelsCache.containsKey(id)) { + return levelsCache.get(id); } - - /** - * Set an initial island level - * - * @param island - the island to set. Must have a non-null world - * @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())); + // Get from database if not in cache + if (handler.objectExists(id)) { + IslandLevels ld = handler.loadObject(id); + if (ld != null) { + levelsCache.put(id, ld); + } else { + handler.deleteID(id); + levelsCache.put(id, new IslandLevels(id)); + } + } else { + levelsCache.put(id, new IslandLevels(id)); } - - /** - * Set the island level for the owner of the island that targetPlayer is a - * member - * - * @param world - world - * @param targetPlayer - player, may be a team member - * @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(world, targetPlayer, levelsCache.get(id).getLevel()); + // Return cached value + return levelsCache.get(id); + } + + /** + * Get the number of points required until the next level since the last level + * calc + * + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @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()); + } + + /** + * Get the top ten for this world. Returns offline players or players with the + * intopten permission. + * + * @param world - world requested + * @param size - size of the top ten + * @return sorted top ten map. The key is the island unique ID + */ + @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))); + } + + 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()); + } + + /** + * @return the topTenLists + */ + public Map getTopTenLists() { + return topTenLists; + } + + /** + * Get the rank of the player in the rankings + * + * @param world - world + * @param uuid - player UUID + * @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); + } + + /** + * Checks if player has the correct top ten perm to have their level saved + * + * @param world + * @param targetPlayer + * @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); + } + + /** + * 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())); } - - } - - /** - * Set the island level for the owner of the island that targetPlayer is a - * member - * - * @param world - world - * @param owner - owner of the island - * @param r - results of the calculation - */ - private void setIslandResults(World world, @NonNull UUID owner, Results r) { - // Get the island - Island island = addon.getIslands().getIsland(world, owner); - 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(world, owner, ld.getLevel()); + }); + topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName())); + }); + } + + /** + * Removes an island from a world's top ten + * + * @param world - world + * @param uuid - the island's uuid + */ + public void removeEntry(World world, String uuid) { + if (topTenLists.containsKey(world)) { + topTenLists.get(world).getTopTen().remove(uuid); } - /** - * Removes island from cache when it is deleted - * - * @param uniqueId - id of island - */ - public void deleteIsland(String uniqueId) { - levelsCache.remove(uniqueId); - handler.deleteID(uniqueId); + } + + /** + * Set an initial island level + * + * @param island - the island to set. Must have a non-null world + * @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())); + } + + /** + * Set the island level for the owner of the island that targetPlayer is a + * member + * + * @param world - world + * @param island - island + * @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()); } + } + + /** + * Set the island level for the owner of the island that targetPlayer is a + * member + * + * @param world - world + * @param owner - owner of the island + * @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()); + } + + /** + * Removes island from cache when it is deleted + * + * @param uniqueId - id of island + */ + public void deleteIsland(String uniqueId) { + levelsCache.remove(uniqueId); + handler.deleteID(uniqueId); + } } diff --git a/src/main/java/world/bentobox/level/PlaceholderManager.java b/src/main/java/world/bentobox/level/PlaceholderManager.java index 5e02a2c..8277a64 100644 --- a/src/main/java/world/bentobox/level/PlaceholderManager.java +++ b/src/main/java/world/bentobox/level/PlaceholderManager.java @@ -2,10 +2,13 @@ import java.util.Collections; import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; import org.bukkit.World; +import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.GameModeAddon; @@ -18,6 +21,7 @@ /** * Handles Level placeholders + * * @author tastybento * */ @@ -27,150 +31,180 @@ public class PlaceholderManager { private final BentoBox plugin; public PlaceholderManager(Level addon) { - this.addon = addon; - this.plugin = addon.getPlugin(); + this.addon = addon; + this.plugin = addon.getPlugin(); } - + protected void registerPlaceholders(GameModeAddon gm) { - if (plugin.getPlaceholdersManager() == null) return; - PlaceholdersManager bpm = plugin.getPlaceholdersManager(); - // Island Level - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_level", - user -> addon.getManager().getIslandLevelString(gm.getOverWorld(), user.getUniqueId())); - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_level_raw", - user -> String.valueOf(addon.getManager().getIslandLevel(gm.getOverWorld(), user.getUniqueId()))); - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_total_points", - user -> { - IslandLevels data = addon.getManager().getLevelsData(addon.getIslands().getIsland(gm.getOverWorld(), user)); - return data.getTotalPoints()+""; - }); - - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_points_to_next_level", - user -> addon.getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId())); - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_island_level_max", - user -> String.valueOf(addon.getManager().getIslandMaxLevel(gm.getOverWorld(), user.getUniqueId()))); - - // Visited Island Level - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_visited_island_level", user -> getVisitedIslandLevel(gm, user)); - - // Register Top Ten Placeholders - for (int i = 1; i < 11; i++) { - final int rank = i; - // Name - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_top_name_" + i, u -> getRankName(gm.getOverWorld(), rank)); - // Island Name - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_top_island_name_" + i, u -> getRankIslandName(gm.getOverWorld(), rank)); - // Members - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_top_members_" + i, u -> getRankMembers(gm.getOverWorld(), rank)); - // Level - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank)); - } - - // Personal rank - bpm.registerPlaceholder(addon, - gm.getDescription().getName().toLowerCase() + "_rank_value", u -> getRankValue(gm.getOverWorld(), u)); + if (plugin.getPlaceholdersManager() == null) + return; + PlaceholdersManager bpm = plugin.getPlaceholdersManager(); + // Island Level + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_level", + user -> addon.getManager().getIslandLevelString(gm.getOverWorld(), user.getUniqueId())); + // Unformatted island level + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_level_raw", + user -> String.valueOf(addon.getManager().getIslandLevel(gm.getOverWorld(), user.getUniqueId()))); + // Total number of points counted before applying level formula + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_total_points", user -> { + IslandLevels data = addon.getManager().getLevelsData(addon.getIslands().getIsland(gm.getOverWorld(), user)); + return data.getTotalPoints() + ""; + }); + // Points to the next level for player + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_points_to_next_level", + user -> addon.getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId())); + // Maximum level this island has ever been. Current level maybe lower. + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_island_level_max", + user -> String.valueOf(addon.getManager().getIslandMaxLevel(gm.getOverWorld(), user.getUniqueId()))); + + // Visited Island Level + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_visited_island_level", + user -> getVisitedIslandLevel(gm, user)); + + // Register Top Ten Placeholders + for (int i = 1; i < 11; i++) { + final int rank = i; + // Name + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_name_" + i, + u -> getRankName(gm.getOverWorld(), rank)); + // Island Name + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_island_name_" + i, + u -> getRankIslandName(gm.getOverWorld(), rank)); + // Members + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_members_" + i, + u -> getRankMembers(gm.getOverWorld(), rank)); + // Level + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_value_" + i, + u -> getRankLevel(gm.getOverWorld(), rank)); + // Weighted Level (Level / number of members) + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_weighted_value_" + i, + u -> getWeightedRankLevel(gm.getOverWorld(), rank)); + } + + // Personal rank + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_rank_value", + u -> getRankValue(gm.getOverWorld(), u)); } /** - * Get the name of the player who holds the rank in this world + * Get the name of the owner of the island who holds the rank in this world. + * * @param world world - * @param rank rank 1 to 10 + * @param rank rank 1 to 10 * @return rank name */ String getRankName(World world, int rank) { - if (rank < 1) rank = 1; - if (rank > Level.TEN) rank = Level.TEN; - return addon.getPlayers().getName(addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null)); + // Ensure rank is within bounds + rank = Math.max(1, Math.min(rank, Level.TEN)); + @Nullable + UUID owner = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L) + .findFirst().flatMap(addon.getIslands()::getIslandById).map(Island::getOwner).orElse(null); + + return addon.getPlayers().getName(owner); } /** * Get the island name for this rank + * * @param world world - * @param rank rank 1 to 10 + * @param rank rank 1 to 10 * @return name of island or nothing if there isn't one */ String getRankIslandName(World world, int rank) { - if (rank < 1) rank = 1; - if (rank > Level.TEN) rank = Level.TEN; - UUID owner = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null); - if (owner != null) { - Island island = addon.getIslands().getIsland(world, owner); - if (island != null) { - return island.getName() == null ? "" : island.getName(); - } - } - return ""; + // Ensure rank is within bounds + rank = Math.max(1, Math.min(rank, Level.TEN)); + return addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst() + .flatMap(addon.getIslands()::getIslandById).map(Island::getName).orElse(""); } /** * Gets a comma separated string of island member names + * * @param world world - * @param rank rank to request + * @param rank rank to request * @return comma separated string of island member names */ String getRankMembers(World world, int rank) { - if (rank < 1) rank = 1; - if (rank > Level.TEN) rank = Level.TEN; - UUID owner = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst().orElse(null); - if (owner != null) { - Island island = addon.getIslands().getIsland(world, owner); - if (island != null) { - // Sort members by rank - return island.getMembers().entrySet().stream() - .filter(e -> e.getValue() >= RanksManager.MEMBER_RANK) - .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) - .map(Map.Entry::getKey) - .map(addon.getPlayers()::getName) - .collect(Collectors.joining(",")); - } - } - return ""; + // Ensure rank is within bounds + rank = Math.max(1, Math.min(rank, Level.TEN)); + Optional island = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L) + .limit(1L).findFirst().flatMap(addon.getIslands()::getIslandById); + + if (island.isPresent()) { + // Sort members by rank + return island.get().getMembers().entrySet().stream().filter(e -> e.getValue() >= RanksManager.MEMBER_RANK) + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).map(Map.Entry::getKey) + .map(addon.getPlayers()::getName).collect(Collectors.joining(",")); + } + return ""; + } + + /** + * Gets the weighted level, which is the level / number of players + * + * @param world world + * @param rank level + * @return weighted level + */ + String getWeightedRankLevel(World world, int rank) { + // Ensure rank is within bounds + rank = Math.max(1, Math.min(rank, Level.TEN)); + + // Retrieve the top ten entries + Map topTen = addon.getManager().getTopTen(world, Level.TEN); + if (topTen.isEmpty()) { + return ""; + } + + // Find the entry corresponding to the rank + Entry entry = topTen.entrySet().stream().skip(rank - 1).findFirst().orElse(null); + if (entry == null) { + return ""; + } + + // Calculate the score + Island island = addon.getIslands().getIslandById(entry.getKey()).orElse(null); + if (island == null || island.getMemberSet().isEmpty()) { + return ""; + } + + double score = (double) entry.getValue() / island.getMemberSet().size(); + + // Format and return the level + return addon.getManager().formatLevel((long) score); + } String getRankLevel(World world, int rank) { - if (rank < 1) rank = 1; - if (rank > Level.TEN) rank = Level.TEN; - return addon.getManager() - .formatLevel(addon.getManager() - .getTopTen(world, Level.TEN) - .values() - .stream() - .skip(rank - 1L) - .limit(1L) - .findFirst() - .orElse(null)); + // Ensure rank is within bounds + rank = Math.max(1, Math.min(rank, Level.TEN)); + return addon.getManager().formatLevel(addon.getManager().getTopTen(world, Level.TEN).values().stream() + .skip(rank - 1L).limit(1L).findFirst().orElse(null)); } - + /** * Return the rank of the player in a world + * * @param world world - * @param user player + * @param user player * @return rank where 1 is the top rank. */ private String getRankValue(World world, User user) { - if (user == null) { - return ""; - } - // Get the island level for this user - long level = addon.getManager().getIslandLevel(world, user.getUniqueId()); - return String.valueOf(addon.getManager().getTopTenLists().getOrDefault(world, new TopTenData(world)).getTopTen().values().stream().filter(l -> l > level).count() + 1); + if (user == null) { + return ""; + } + // Get the island level for this user + long level = addon.getManager().getIslandLevel(world, user.getUniqueId()); + return String.valueOf(addon.getManager().getTopTenLists().getOrDefault(world, new TopTenData(world)).getTopTen() + .values().stream().filter(l -> l > level).count() + 1); } String getVisitedIslandLevel(GameModeAddon gm, User user) { - if (user == null || !gm.inWorld(user.getWorld())) return ""; - return addon.getIslands().getIslandAt(user.getLocation()) - .map(island -> addon.getManager().getIslandLevelString(gm.getOverWorld(), island.getOwner())) - .orElse("0"); + if (user == null || !gm.inWorld(user.getWorld())) + return ""; + return addon.getIslands().getIslandAt(user.getLocation()) + .map(island -> addon.getManager().getIslandLevelString(gm.getOverWorld(), island.getOwner())) + .orElse("0"); } } diff --git a/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java b/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java index 78d5a05..180c41e 100644 --- a/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java +++ b/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java @@ -6,7 +6,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; -import java.util.UUID; import java.util.stream.Collectors; import org.bukkit.World; @@ -19,81 +18,81 @@ public class AdminStatsCommand extends CompositeCommand { - private final Level level; + private final Level level; - public AdminStatsCommand(Level addon, CompositeCommand parent) { - super(parent, "stats"); - this.level = addon; - new AdminTopRemoveCommand(addon, this); - } + public AdminStatsCommand(Level addon, CompositeCommand parent) { + super(parent, "stats"); + this.level = addon; + new AdminTopRemoveCommand(addon, this); + } - @Override - public void setup() { - this.setPermission("admin.stats"); - this.setOnlyPlayer(false); - this.setDescription("admin.stats.description"); - } + @Override + public void setup() { + this.setPermission("admin.stats"); + this.setOnlyPlayer(false); + this.setDescription("admin.stats.description"); + } - @Override - public boolean execute(User user, String label, List args) { - user.sendMessage("admin.stats.title"); - Map topTenLists = level.getManager().getTopTenLists(); - if (topTenLists.isEmpty()) { - user.sendMessage("admin.stats.no-data"); - return false; - } - for (Entry en : topTenLists.entrySet()) { - user.sendMessage("admin.stats.world", TextVariables.NAME, - level.getPlugin().getIWM().getWorldName(en.getKey())); - Map topTen = en.getValue().getTopTen(); - if (topTen.isEmpty()) { - user.sendMessage("admin.stats.no-data"); - return false; - } + @Override + public boolean execute(User user, String label, List args) { + user.sendMessage("admin.stats.title"); + Map topTenLists = level.getManager().getTopTenLists(); + if (topTenLists.isEmpty()) { + user.sendMessage("admin.stats.no-data"); + return false; + } + for (Entry en : topTenLists.entrySet()) { + user.sendMessage("admin.stats.world", TextVariables.NAME, + level.getPlugin().getIWM().getWorldName(en.getKey())); + Map topTen = en.getValue().getTopTen(); + if (topTen.isEmpty()) { + user.sendMessage("admin.stats.no-data"); + return false; + } - // Calculating basic statistics - long sum = 0, max = Long.MIN_VALUE, min = Long.MAX_VALUE; - Map levelFrequency = new HashMap<>(); + // Calculating basic statistics + long sum = 0, max = Long.MIN_VALUE, min = Long.MAX_VALUE; + Map levelFrequency = new HashMap<>(); - for (Long level : topTen.values()) { - sum += level; - max = Math.max(max, level); - min = Math.min(min, level); - levelFrequency.merge(level, 1, Integer::sum); - } + for (Long level : topTen.values()) { + sum += level; + max = Math.max(max, level); + min = Math.min(min, level); + levelFrequency.merge(level, 1, Integer::sum); + } - double average = sum / (double) topTen.size(); - List sortedLevels = topTen.values().stream().sorted().collect(Collectors.toList()); - long median = sortedLevels.get(sortedLevels.size() / 2); - Long mode = Collections.max(levelFrequency.entrySet(), Map.Entry.comparingByValue()).getKey(); + double average = sum / (double) topTen.size(); + List sortedLevels = topTen.values().stream().sorted().collect(Collectors.toList()); + long median = sortedLevels.get(sortedLevels.size() / 2); + Long mode = Collections.max(levelFrequency.entrySet(), Map.Entry.comparingByValue()).getKey(); - // Logging basic statistics - user.sendMessage("admin.stats.average-level", TextVariables.NUMBER, String.valueOf(average)); - user.sendMessage("admin.stats.median-level", TextVariables.NUMBER, String.valueOf(median)); - user.sendMessage("admin.stats.mode-level", TextVariables.NUMBER, String.valueOf(mode)); - user.sendMessage("admin.stats.highest-level", TextVariables.NUMBER, String.valueOf(max)); - user.sendMessage("admin.stats.lowest-level", TextVariables.NUMBER, String.valueOf(min)); + // Logging basic statistics + user.sendMessage("admin.stats.average-level", TextVariables.NUMBER, String.valueOf(average)); + user.sendMessage("admin.stats.median-level", TextVariables.NUMBER, String.valueOf(median)); + user.sendMessage("admin.stats.mode-level", TextVariables.NUMBER, String.valueOf(mode)); + user.sendMessage("admin.stats.highest-level", TextVariables.NUMBER, String.valueOf(max)); + user.sendMessage("admin.stats.lowest-level", TextVariables.NUMBER, String.valueOf(min)); - // Grouping data for distribution analysis - Map rangeMap = new TreeMap<>(); - for (Long level : topTen.values()) { - String range = getRange(level); - rangeMap.merge(range, 1, Integer::sum); - } + // Grouping data for distribution analysis + Map rangeMap = new TreeMap<>(); + for (Long level : topTen.values()) { + String range = getRange(level); + rangeMap.merge(range, 1, Integer::sum); + } - // Logging distribution - user.sendMessage("admin.stats.distribution"); - for (Map.Entry entry : rangeMap.entrySet()) { - user.sendMessage( - entry.getKey() + ": " + entry.getValue() + " " + user.getTranslation("admin.stats.islands")); - } - } - return true; + // Logging distribution + user.sendMessage("admin.stats.distribution"); + for (Map.Entry entry : rangeMap.entrySet()) { + user.sendMessage( + entry.getKey() + ": " + entry.getValue() + " " + user.getTranslation("admin.stats.islands")); + } } + return true; + } - private static String getRange(long level) { - long rangeStart = level / 100 * 100; - long rangeEnd = rangeStart + 99; - return rangeStart + "-" + rangeEnd; - } + private static String getRange(long level) { + long rangeStart = level / 100 * 100; + long rangeEnd = rangeStart + 99; + return rangeStart + "-" + rangeEnd; + } } diff --git a/src/main/java/world/bentobox/level/commands/AdminTopCommand.java b/src/main/java/world/bentobox/level/commands/AdminTopCommand.java index 9144107..9287e14 100644 --- a/src/main/java/world/bentobox/level/commands/AdminTopCommand.java +++ b/src/main/java/world/bentobox/level/commands/AdminTopCommand.java @@ -2,7 +2,7 @@ import java.util.List; import java.util.Map; -import java.util.UUID; +import java.util.Optional; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.user.User; @@ -14,36 +14,32 @@ public class AdminTopCommand extends CompositeCommand { private final Level levelPlugin; public AdminTopCommand(Level addon, CompositeCommand parent) { - super(parent, "top", "topten"); - this.levelPlugin = addon; - new AdminTopRemoveCommand(addon, this); + super(parent, "top", "topten"); + this.levelPlugin = addon; + new AdminTopRemoveCommand(addon, this); } @Override public void setup() { - this.setPermission("admin.top"); - this.setOnlyPlayer(false); - this.setDescription("admin.top.description"); + this.setPermission("admin.top"); + this.setOnlyPlayer(false); + this.setDescription("admin.top.description"); } @Override public boolean execute(User user, String label, List args) { - user.sendMessage("island.top.gui-title"); - int rank = 0; - for (Map.Entry topTen : levelPlugin.getManager().getTopTen(getWorld(), Level.TEN).entrySet()) { - Island island = getPlugin().getIslands().getIsland(getWorld(), topTen.getKey()); - if (island != null) { - rank++; - user.sendMessage("admin.top.display", - "[rank]", - String.valueOf(rank), - "[name]", - this.getPlugin().getPlayers().getUser(island.getOwner()).getName(), - "[level]", - String.valueOf(topTen.getValue())); - } - } - - return true; + user.sendMessage("island.top.gui-title"); + int rank = 0; + for (Map.Entry topTen : levelPlugin.getManager().getTopTen(getWorld(), Level.TEN).entrySet()) { + Optional is = getPlugin().getIslands().getIslandById(topTen.getKey()); + if (is.isPresent()) { + Island island = is.get(); + rank++; + user.sendMessage("admin.top.display", "[rank]", String.valueOf(rank), "[name]", + this.getPlugin().getPlayers().getUser(island.getOwner()).getName(), "[level]", + String.valueOf(topTen.getValue())); + } + } + return true; } } diff --git a/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java b/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java index b54ca3e..1c51fb9 100644 --- a/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java +++ b/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java @@ -6,10 +6,12 @@ import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; import world.bentobox.level.Level; /** * Removes a player from the top ten + * * @author tastybento * */ @@ -19,46 +21,53 @@ public class AdminTopRemoveCommand extends CompositeCommand { private User target; public AdminTopRemoveCommand(Level addon, CompositeCommand parent) { - super(parent, "remove", "delete"); - this.addon = addon; + super(parent, "remove", "delete"); + this.addon = addon; } @Override public void setup() { - this.setPermission("admin.top.remove"); - this.setOnlyPlayer(false); - this.setParametersHelp("admin.top.remove.parameters"); - this.setDescription("admin.top.remove.description"); + this.setPermission("admin.top.remove"); + this.setOnlyPlayer(false); + this.setParametersHelp("admin.top.remove.parameters"); + this.setDescription("admin.top.remove.description"); } - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List) + /* + * (non-Javadoc) + * + * @see world.bentobox.bentobox.api.commands.BentoBoxCommand#canExecute(world. + * bentobox.bentobox.api.user.User, java.lang.String, java.util.List) */ @Override public boolean canExecute(User user, String label, List args) { - if (args.size() != 1) { - this.showHelp(this, user); - return false; - } - target = getPlayers().getUser(args.get(0)); - if (target == null) { - user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); - return false; - } + if (args.size() != 1) { + this.showHelp(this, user); + return false; + } + target = getPlayers().getUser(args.get(0)); + if (target == null) { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return false; + } - return true; + return true; } @Override public boolean execute(User user, String label, List args) { - addon.getManager().removeEntry(getWorld(), target.getUniqueId()); - user.sendMessage("general.success"); - return true; + // Removes islands that this target is an owner of + getIslands().getIslands(getWorld(), target.getUniqueId()).stream() + .filter(is -> target.getUniqueId().equals(is.getOwner())) + .forEach(island -> addon.getManager().removeEntry(getWorld(), island.getUniqueId())); + user.sendMessage("general.success"); + return true; } @Override public Optional> tabComplete(User user, String alias, List args) { - return Optional.of(addon.getManager().getTopTen(getWorld(), Level.TEN).keySet().stream().map(addon.getPlayers()::getName) - .filter(n -> !n.isEmpty()).toList()); + return Optional.of(addon.getManager().getTopTen(getWorld(), Level.TEN).keySet().stream() + .map(getIslands()::getIslandById).flatMap(Optional::stream).map(Island::getOwner) + .map(addon.getPlayers()::getName).filter(n -> !n.isEmpty()).toList()); } } diff --git a/src/main/java/world/bentobox/level/listeners/IslandActivitiesListeners.java b/src/main/java/world/bentobox/level/listeners/IslandActivitiesListeners.java index 05bdfc2..4a75f5c 100644 --- a/src/main/java/world/bentobox/level/listeners/IslandActivitiesListeners.java +++ b/src/main/java/world/bentobox/level/listeners/IslandActivitiesListeners.java @@ -1,7 +1,5 @@ package world.bentobox.level.listeners; -import java.util.UUID; - import org.bukkit.World; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -21,7 +19,9 @@ import world.bentobox.level.Level; /** - * Listens for new islands or ownership changes and sets the level to zero automatically + * Listens for new islands or ownership changes and sets the level to zero + * automatically + * * @author tastybento * */ @@ -33,93 +33,89 @@ public class IslandActivitiesListeners implements Listener { * @param addon - addon */ public IslandActivitiesListeners(Level addon) { - this.addon = addon; + this.addon = addon; } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIsland(IslandCreatedEvent e) { - if (addon.getSettings().isZeroNewIslandLevels()) { - zeroIsland(e.getIsland()); - } + if (addon.getSettings().isZeroNewIslandLevels()) { + zeroIsland(e.getIsland()); + } } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIsland(IslandResettedEvent e) { - if (addon.getSettings().isZeroNewIslandLevels()) { - zeroIsland(e.getIsland()); - } + if (addon.getSettings().isZeroNewIslandLevels()) { + zeroIsland(e.getIsland()); + } } private void zeroIsland(final Island island) { - // Clear the island setting - if (island.getOwner() != null && island.getWorld() != null) { - addon.getPipeliner().zeroIsland(island).thenAccept(results -> - addon.getManager().setInitialIslandLevel(island, results.getLevel())); - } + // Clear the island setting + if (island.getOwner() != null && island.getWorld() != null) { + addon.getPipeliner().zeroIsland(island) + .thenAccept(results -> addon.getManager().setInitialIslandLevel(island, results.getLevel())); + } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onIslandDelete(IslandPreclearEvent e) { - - // Remove player from the top ten and level - UUID uuid = e.getIsland().getOwner(); - World world = e.getIsland().getWorld(); - remove(world, uuid); + remove(e.getIsland().getWorld(), e.getIsland().getUniqueId()); } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onIslandDeleted(IslandDeleteEvent e) { - // Remove island - addon.getManager().deleteIsland(e.getIsland().getUniqueId()); + // Remove island + addon.getManager().deleteIsland(e.getIsland().getUniqueId()); } - private void remove(World world, UUID uuid) { - if (uuid != null && world != null) { - addon.getManager().removeEntry(world, uuid); - } + private void remove(World world, String uuid) { + if (uuid != null && world != null) { + addon.getManager().removeEntry(world, uuid); + } } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIslandOwner(TeamSetownerEvent e) { - // Remove player from the top ten and level - remove(e.getIsland().getWorld(), e.getIsland().getOwner()); + // Remove island from the top ten and level + remove(e.getIsland().getWorld(), e.getIsland().getUniqueId()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(TeamJoinedEvent e) { - - // Remove player from the top ten and level - remove(e.getIsland().getWorld(), e.getPlayerUUID()); + // TODO: anything to do here? + // Remove player from the top ten and level + // remove(e.getIsland().getWorld(), e.getPlayerUUID()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(IslandUnregisteredEvent e) { - // Remove player from the top ten - remove(e.getIsland().getWorld(), e.getPlayerUUID()); + // Remove island from the top ten + remove(e.getIsland().getWorld(), e.getIsland().getUniqueId()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(IslandRegisteredEvent e) { - - // Remove player from the top ten - remove(e.getIsland().getWorld(), e.getPlayerUUID()); + // TODO: anything to do here? + // Remove player from the top ten + // remove(e.getIsland().getWorld(), e.getPlayerUUID()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(TeamLeaveEvent e) { - - // Remove player from the top ten and level - remove(e.getIsland().getWorld(), e.getPlayerUUID()); + // TODO: anything to do here? + // Remove player from the top ten and level + // remove(e.getIsland().getWorld(), e.getPlayerUUID()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(TeamKickEvent e) { - - // Remove player from the top ten and level - remove(e.getIsland().getWorld(), e.getPlayerUUID()); + //// TODO: anything to do here? + // Remove player from the top ten and level + // remove(e.getIsland().getWorld(), e.getPlayerUUID()); } } diff --git a/src/main/java/world/bentobox/level/objects/TopTenData.java b/src/main/java/world/bentobox/level/objects/TopTenData.java index e18e8bd..3d50b1f 100644 --- a/src/main/java/world/bentobox/level/objects/TopTenData.java +++ b/src/main/java/world/bentobox/level/objects/TopTenData.java @@ -3,56 +3,41 @@ import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; -import java.util.UUID; import org.bukkit.World; import com.google.gson.annotations.Expose; -import world.bentobox.bentobox.database.objects.DataObject; -import world.bentobox.bentobox.database.objects.Table; - /** * This class stores the top ten. + * * @author tastybento * */ -@Table(name = "TopTenData") -public class TopTenData implements DataObject { +public class TopTenData { // UniqueId is the world name @Expose private String uniqueId = ""; @Expose - private Map topTen = new LinkedHashMap<>(); + private Map topTen = new LinkedHashMap<>(); public TopTenData(World k) { - uniqueId = k.getName().toLowerCase(Locale.ENGLISH); - } - - @Override - public String getUniqueId() { - // This is the world name - return uniqueId; + uniqueId = k.getName().toLowerCase(Locale.ENGLISH); } - @Override - public void setUniqueId(String uniqueId) { - // This is the world name - make it always lowercase - this.uniqueId = uniqueId.toLowerCase(Locale.ENGLISH); - } /** * @return the topTen */ - public Map getTopTen() { - return topTen; + public Map getTopTen() { + return topTen; } + /** * @param topTen the topTen to set */ - public void setTopTen(Map topTen) { - this.topTen = topTen; + public void setTopTen(Map topTen) { + this.topTen = topTen; } - } diff --git a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java index e60d377..0254204 100644 --- a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java +++ b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java @@ -5,10 +5,11 @@ package world.bentobox.level.panels; - import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -29,474 +30,390 @@ import world.bentobox.level.Level; import world.bentobox.level.util.Utils; - /** * This panel opens top likes panel */ -public class TopLevelPanel -{ - +public class TopLevelPanel { // --------------------------------------------------------------------- // Section: Internal Constructor // --------------------------------------------------------------------- - /** - * This is internal constructor. It is used internally in current class to avoid creating objects everywhere. + * This is internal constructor. It is used internally in current class to avoid + * creating objects everywhere. * - * @param addon Level object. - * @param user User who opens Panel. - * @param world World where gui is opened + * @param addon Level object. + * @param user User who opens Panel. + * @param world World where gui is opened * @param permissionPrefix Permission Prefix */ - private TopLevelPanel(Level addon, User user, World world, String permissionPrefix) - { - this.addon = addon; - this.user = user; - this.world = world; - - this.iconPermission = permissionPrefix + "level.icon"; - - this.topIslands = this.addon.getManager().getTopTen(this.world, 10).entrySet().stream(). - map(entry -> { - Island island = this.addon.getIslandsManager().getIsland(this.world, entry.getKey()); - return new IslandTopRecord(island, entry.getValue()); - }). - collect(Collectors.toList()); + private TopLevelPanel(Level addon, User user, World world, String permissionPrefix) { + this.addon = addon; + this.user = user; + this.world = world; + + this.iconPermission = permissionPrefix + "level.icon"; + topIslands = new ArrayList<>(); + for (Map.Entry en : addon.getManager().getTopTen(this.world, Level.TEN).entrySet()) { + Optional is = addon.getIslands().getIslandById(en.getKey()); + if (is.isPresent()) { + topIslands.add(new IslandTopRecord(is.get(), en.getValue())); + } + } } - /** - * Build method manages current panel opening. It uses BentoBox PanelAPI that is easy to use and users can get nice - * panels. + * Build method manages current panel opening. It uses BentoBox PanelAPI that is + * easy to use and users can get nice panels. */ - public void build() - { - TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); + public void build() { + TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); - panelBuilder.user(this.user); - panelBuilder.world(this.world); + panelBuilder.user(this.user); + panelBuilder.world(this.world); - panelBuilder.template("top_panel", new File(this.addon.getDataFolder(), "panels")); + panelBuilder.template("top_panel", new File(this.addon.getDataFolder(), "panels")); - panelBuilder.registerTypeBuilder("VIEW", this::createViewerButton); - panelBuilder.registerTypeBuilder("TOP", this::createPlayerButton); + panelBuilder.registerTypeBuilder("VIEW", this::createViewerButton); + panelBuilder.registerTypeBuilder("TOP", this::createPlayerButton); - // Register unknown type builder. - panelBuilder.build(); + // Register unknown type builder. + panelBuilder.build(); } - // --------------------------------------------------------------------- // Section: Methods // --------------------------------------------------------------------- - /** * Creates fallback based on template. + * * @param template Template record for fallback button. - * @param index Place of the fallback. + * @param index Place of the fallback. * @return Fallback panel item. */ - private PanelItem createFallback(ItemTemplateRecord template, long index) - { - if (template == null) - { - return null; - } - - PanelItemBuilder builder = new PanelItemBuilder(); - - if (template.icon() != null) - { - builder.icon(template.icon().clone()); - } - - if (template.title() != null) - { - builder.name(this.user.getTranslation(this.world, template.title(), - TextVariables.NAME, String.valueOf(index))); - } - else - { - builder.name(this.user.getTranslation(this.world, REFERENCE, - TextVariables.NAME, String.valueOf(index))); - } - - if (template.description() != null) - { - builder.description(this.user.getTranslation(this.world, template.description(), - TextVariables.NUMBER, String.valueOf(index))); - } - - builder.amount(index != 0 ? (int) index : 1); - - return builder.build(); - } + private PanelItem createFallback(ItemTemplateRecord template, long index) { + if (template == null) { + return null; + } + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) { + builder.icon(template.icon().clone()); + } + + if (template.title() != null) { + builder.name( + this.user.getTranslation(this.world, template.title(), TextVariables.NAME, String.valueOf(index))); + } else { + builder.name(this.user.getTranslation(this.world, REFERENCE, TextVariables.NAME, String.valueOf(index))); + } + + if (template.description() != null) { + builder.description(this.user.getTranslation(this.world, template.description(), TextVariables.NUMBER, + String.valueOf(index))); + } + builder.amount(index != 0 ? (int) index : 1); + + return builder.build(); + } /** * This method creates player icon with warp functionality. * * @return PanelItem for PanelBuilder. */ - private PanelItem createPlayerButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot itemSlot) - { - int index = (int) template.dataMap().getOrDefault("index", 0); + private PanelItem createPlayerButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot itemSlot) { + int index = (int) template.dataMap().getOrDefault("index", 0); - if (index < 1) - { - return this.createFallback(template.fallback(), index); - } + if (index < 1) { + return this.createFallback(template.fallback(), index); + } - IslandTopRecord islandTopRecord = this.topIslands.size() < index ? null : this.topIslands.get(index - 1); + IslandTopRecord islandTopRecord = this.topIslands.size() < index ? null : this.topIslands.get(index - 1); - if (islandTopRecord == null) - { - return this.createFallback(template.fallback(), index); - } + if (islandTopRecord == null) { + return this.createFallback(template.fallback(), index); + } - return this.createIslandIcon(template, islandTopRecord, index); + return this.createIslandIcon(template, islandTopRecord, index); } - /** * This method creates button from template for given island top record. - * @param template Icon Template. + * + * @param template Icon Template. * @param islandTopRecord Island Top Record. - * @param index Place Index. + * @param index Place Index. * @return PanelItem for PanelBuilder. */ - private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord islandTopRecord, int index) - { - // Get player island. - Island island = islandTopRecord.island(); - - if (island == null) - { - return this.createFallback(template.fallback(), index); - } - - PanelItemBuilder builder = new PanelItemBuilder(); - - this.populateIslandIcon(builder, template, island); - this.populateIslandTitle(builder, template, island); - this.populateIslandDescription(builder, template, island, islandTopRecord, index); - - builder.amount(index); - - // Get only possible actions, by removing all inactive ones. - List activeActions = new ArrayList<>(template.actions()); - - activeActions.removeIf(action -> - { - switch (action.actionType().toUpperCase()) - { - case "WARP" -> { - return island.getOwner() == null || - this.addon.getWarpHook() == null || - !this.addon.getWarpHook().getWarpSignsManager().hasWarp(this.world, island.getOwner()); - } - case "VISIT" -> { - return island.getOwner() == null || - this.addon.getVisitHook() == null || - !this.addon.getVisitHook().getAddonManager().preprocessTeleportation(this.user, island); - } - case "VIEW" -> { - return island.getOwner() == null || - !island.getMemberSet(RanksManager.MEMBER_RANK).contains(this.user.getUniqueId()); - } - default -> { - return false; - } - } - }); - - // Add Click handler - builder.clickHandler((panel, user, clickType, i) -> - { - for (ItemTemplateRecord.ActionRecords action : activeActions) - { - if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) - { - switch (action.actionType().toUpperCase()) - { - case "WARP" -> { - this.user.closeInventory(); - this.addon.getWarpHook().getWarpSignsManager().warpPlayer(this.world, this.user, island.getOwner()); - } - case "VISIT" -> - // The command call implementation solves necessity to check for all visits options, - // like cool down, confirmation and preprocess in single go. Would it be better to write - // all logic here? - - this.addon.getPlugin().getIWM().getAddon(this.world). - flatMap(GameModeAddon::getPlayerCommand).ifPresent(command -> - { - String mainCommand = - this.addon.getVisitHook().getSettings().getPlayerMainCommand(); - - if (!mainCommand.isBlank()) - { - this.user.closeInventory(); - this.user.performCommand(command.getTopLabel() + " " + mainCommand + " " + island.getOwner()); - } - }); - - case "VIEW" -> { - this.user.closeInventory(); - // Open Detailed GUI. - DetailsPanel.openPanel(this.addon, this.world, this.user); - } - // Catch default - default -> { - this.user.closeInventory(); - addon.logError("Unknown action type " + action.actionType().toUpperCase()); - } - } - } - } - - return true; - }); - - // Collect tooltips. - List tooltips = activeActions.stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.world, action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); - - // Add tooltips. - if (!tooltips.isEmpty()) - { - // Empty line and tooltips. - builder.description(""); - builder.description(tooltips); - } - - return builder.build(); + private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord islandTopRecord, int index) { + // Get player island. + Island island = islandTopRecord.island(); + + if (island == null) { + return this.createFallback(template.fallback(), index); + } + + PanelItemBuilder builder = new PanelItemBuilder(); + + this.populateIslandIcon(builder, template, island); + this.populateIslandTitle(builder, template, island); + this.populateIslandDescription(builder, template, island, islandTopRecord, index); + + builder.amount(index); + + // Get only possible actions, by removing all inactive ones. + List activeActions = new ArrayList<>(template.actions()); + + activeActions.removeIf(action -> { + switch (action.actionType().toUpperCase()) { + case "WARP" -> { + return island.getOwner() == null || this.addon.getWarpHook() == null + || !this.addon.getWarpHook().getWarpSignsManager().hasWarp(this.world, island.getOwner()); + } + case "VISIT" -> { + return island.getOwner() == null || this.addon.getVisitHook() == null + || !this.addon.getVisitHook().getAddonManager().preprocessTeleportation(this.user, island); + } + case "VIEW" -> { + return island.getOwner() == null + || !island.getMemberSet(RanksManager.MEMBER_RANK).contains(this.user.getUniqueId()); + } + default -> { + return false; + } + } + }); + + // Add Click handler + builder.clickHandler((panel, user, clickType, i) -> { + for (ItemTemplateRecord.ActionRecords action : activeActions) { + if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) { + switch (action.actionType().toUpperCase()) { + case "WARP" -> { + this.user.closeInventory(); + this.addon.getWarpHook().getWarpSignsManager().warpPlayer(this.world, this.user, + island.getOwner()); + } + case "VISIT" -> + // The command call implementation solves necessity to check for all visits + // options, + // like cool down, confirmation and preprocess in single go. Would it be better + // to write + // all logic here? + + this.addon.getPlugin().getIWM().getAddon(this.world).flatMap(GameModeAddon::getPlayerCommand) + .ifPresent(command -> { + String mainCommand = this.addon.getVisitHook().getSettings().getPlayerMainCommand(); + + if (!mainCommand.isBlank()) { + this.user.closeInventory(); + this.user.performCommand( + command.getTopLabel() + " " + mainCommand + " " + island.getOwner()); + } + }); + + case "VIEW" -> { + this.user.closeInventory(); + // Open Detailed GUI. + DetailsPanel.openPanel(this.addon, this.world, this.user); + } + // Catch default + default -> { + this.user.closeInventory(); + addon.logError("Unknown action type " + action.actionType().toUpperCase()); + } + } + } + } + + return true; + }); + + // Collect tooltips. + List tooltips = activeActions.stream().filter(action -> action.tooltip() != null) + .map(action -> this.user.getTranslation(this.world, action.tooltip())).filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); } - /** - * Populate given panel item builder name with values from template and island objects. + * Populate given panel item builder name with values from template and island + * objects. * - * @param builder the builder + * @param builder the builder * @param template the template - * @param island the island + * @param island the island */ - private void populateIslandTitle(PanelItemBuilder builder, - ItemTemplateRecord template, - Island island) - { - - // Get Island Name - String nameText; - - if (island.getName() == null || island.getName().isEmpty()) - { - nameText = this.user.getTranslation(REFERENCE + "owners-island", - PLAYER, - island.getOwner() == null ? - this.user.getTranslation(REFERENCE + "unknown") : - this.addon.getPlayers().getName(island.getOwner())); - } - else - { - nameText = island.getName(); - } - - // Template specific title is always more important than custom one. - if (template.title() != null && !template.title().isBlank()) - { - builder.name(this.user.getTranslation(this.world, template.title(), - TextVariables.NAME, nameText)); - } - else - { - builder.name(this.user.getTranslation(REFERENCE + "name", TextVariables.NAME, nameText)); - } + private void populateIslandTitle(PanelItemBuilder builder, ItemTemplateRecord template, Island island) { + + // Get Island Name + String nameText; + + if (island.getName() == null || island.getName().isEmpty()) { + nameText = this.user.getTranslation(REFERENCE + "owners-island", PLAYER, + island.getOwner() == null ? this.user.getTranslation(REFERENCE + "unknown") + : this.addon.getPlayers().getName(island.getOwner())); + } else { + nameText = island.getName(); + } + + // Template specific title is always more important than custom one. + if (template.title() != null && !template.title().isBlank()) { + builder.name(this.user.getTranslation(this.world, template.title(), TextVariables.NAME, nameText)); + } else { + builder.name(this.user.getTranslation(REFERENCE + "name", TextVariables.NAME, nameText)); + } } - /** - * Populate given panel item builder icon with values from template and island objects. + * Populate given panel item builder icon with values from template and island + * objects. * - * @param builder the builder + * @param builder the builder * @param template the template - * @param island the island + * @param island the island */ - private void populateIslandIcon(PanelItemBuilder builder, - ItemTemplateRecord template, - Island island) - { - User owner = island.getOwner() == null ? null : User.getInstance(island.getOwner()); - - // Get permission or island icon - String permissionIcon = owner == null ? null : - Utils.getPermissionValue(owner, this.iconPermission, null); - - Material material; - - if (permissionIcon != null && !permissionIcon.equals("*")) - { - material = Material.matchMaterial(permissionIcon); - } - else - { - material = null; - } - - if (material != null) - { - if (!material.equals(Material.PLAYER_HEAD)) - { - builder.icon(material); - } - else - { - builder.icon(owner.getName()); - } - } - else if (template.icon() != null) - { - builder.icon(template.icon().clone()); - } - else if (owner != null) - { - builder.icon(owner.getName()); - } - else - { - builder.icon(Material.PLAYER_HEAD); - } + private void populateIslandIcon(PanelItemBuilder builder, ItemTemplateRecord template, Island island) { + User owner = island.getOwner() == null ? null : User.getInstance(island.getOwner()); + + // Get permission or island icon + String permissionIcon = owner == null ? null : Utils.getPermissionValue(owner, this.iconPermission, null); + + Material material; + + if (permissionIcon != null && !permissionIcon.equals("*")) { + material = Material.matchMaterial(permissionIcon); + } else { + material = null; + } + + if (material != null) { + if (!material.equals(Material.PLAYER_HEAD)) { + builder.icon(material); + } else { + builder.icon(owner.getName()); + } + } else if (template.icon() != null) { + builder.icon(template.icon().clone()); + } else if (owner != null) { + builder.icon(owner.getName()); + } else { + builder.icon(Material.PLAYER_HEAD); + } } - /** - * Populate given panel item builder description with values from template and island objects. + * Populate given panel item builder description with values from template and + * island objects. * - * @param builder the builder - * @param template the template - * @param island the island + * @param builder the builder + * @param template the template + * @param island the island * @param islandTopRecord the top record object - * @param index place index. + * @param index place index. */ - private void populateIslandDescription(PanelItemBuilder builder, - ItemTemplateRecord template, - Island island, - IslandTopRecord islandTopRecord, - int index) - { - // Get Owner Name - String ownerText = this.user.getTranslation(REFERENCE + "owner", - PLAYER, - island.getOwner() == null ? - this.user.getTranslation(REFERENCE + "unknown") : - this.addon.getPlayers().getName(island.getOwner())); - - // Get Members Text - String memberText; - - if (island.getMemberSet().size() > 1) - { - StringBuilder memberBuilder = new StringBuilder( - this.user.getTranslationOrNothing(REFERENCE + "members-title")); - - for (UUID uuid : island.getMemberSet()) - { - User member = User.getInstance(uuid); - - if (memberBuilder.length() > 0) - { - memberBuilder.append("\n"); - } - - memberBuilder.append( - this.user.getTranslationOrNothing(REFERENCE + "member", - PLAYER, member.getName())); - } - - memberText = memberBuilder.toString(); - } - else - { - memberText = ""; - } - - String placeText = this.user.getTranslation(REFERENCE + "place", - TextVariables.NUMBER, String.valueOf(index)); - - String levelText = this.user.getTranslation(REFERENCE + "level", - TextVariables.NUMBER, this.addon.getManager().formatLevel(islandTopRecord.level())); - - // Template specific description is always more important than custom one. - if (template.description() != null && !template.description().isBlank()) - { - builder.description(this.user.getTranslation(this.world, template.description(), - "[owner]", ownerText, - "[members]", memberText, - "[level]", levelText, - "[place]", placeText). - replaceAll("(?m)^[ \\t]*\\r?\\n", ""). - replaceAll("(? 1) { + StringBuilder memberBuilder = new StringBuilder( + this.user.getTranslationOrNothing(REFERENCE + "members-title")); + + for (UUID uuid : island.getMemberSet()) { + User member = User.getInstance(uuid); + + if (memberBuilder.length() > 0) { + memberBuilder.append("\n"); + } + + memberBuilder.append(this.user.getTranslationOrNothing(REFERENCE + "member", PLAYER, member.getName())); + } + + memberText = memberBuilder.toString(); + } else { + memberText = ""; + } + + String placeText = this.user.getTranslation(REFERENCE + "place", TextVariables.NUMBER, String.valueOf(index)); + + String levelText = this.user.getTranslation(REFERENCE + "level", TextVariables.NUMBER, + this.addon.getManager().formatLevel(islandTopRecord.level())); + + // Template specific description is always more important than custom one. + if (template.description() != null && !template.description().isBlank()) { + builder.description(this.user + .getTranslation(this.world, template.description(), "[owner]", ownerText, "[members]", memberText, + "[level]", levelText, "[place]", placeText) + .replaceAll("(?m)^[ \\t]*\\r?\\n", "").replaceAll("(? level to island -> level. + * + * @param island island + * @param level level */ - private record IslandTopRecord(Island island, Long level) {} - + private record IslandTopRecord(Island island, Long level) { + } // --------------------------------------------------------------------- // Section: Variables diff --git a/src/test/java/world/bentobox/level/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java index e077de0..44b1b9b 100644 --- a/src/test/java/world/bentobox/level/LevelsManagerTest.java +++ b/src/test/java/world/bentobox/level/LevelsManagerTest.java @@ -73,60 +73,60 @@ @PrepareForTest({ Bukkit.class, BentoBox.class, DatabaseSetup.class, PanelBuilder.class }) public class LevelsManagerTest { - @Mock - private static AbstractDatabaseHandler handler; - @Mock - Level addon; - @Mock - private BentoBox plugin; - @Mock - private Settings pluginSettings; - - // Class under test - private LevelsManager lm; - @Mock - private Island island; - @Mock - private Pipeliner pipeliner; - private CompletableFuture cf; - private UUID uuid; - @Mock - private World world; - @Mock - private Player player; - @Mock - private ConfigSettings settings; - @Mock - private User user; - @Mock - private PlayersManager pm; - @Mock - private Inventory inv; - @Mock - private IslandWorldManager iwm; - @Mock - private PluginManager pim; - @Mock - private IslandLevels levelsData; - @Mock - private IslandsManager im; - @Mock - private BukkitScheduler scheduler; - - @SuppressWarnings("unchecked") - @BeforeClass - public static void beforeClass() { - // This has to be done beforeClass otherwise the tests will interfere with each - // other - handler = mock(AbstractDatabaseHandler.class); - // Database - PowerMockito.mockStatic(DatabaseSetup.class); - DatabaseSetup dbSetup = mock(DatabaseSetup.class); - when(DatabaseSetup.getDatabase()).thenReturn(dbSetup); - when(dbSetup.getHandler(any())).thenReturn(handler); - } + @Mock + private static AbstractDatabaseHandler handler; + @Mock + Level addon; + @Mock + private BentoBox plugin; + @Mock + private Settings pluginSettings; + + // Class under test + private LevelsManager lm; + @Mock + private Island island; + @Mock + private Pipeliner pipeliner; + private CompletableFuture cf; + private UUID uuid; + @Mock + private World world; + @Mock + private Player player; + @Mock + private ConfigSettings settings; + @Mock + private User user; + @Mock + private PlayersManager pm; + @Mock + private Inventory inv; + @Mock + private IslandWorldManager iwm; + @Mock + private PluginManager pim; + @Mock + private IslandLevels levelsData; + @Mock + private IslandsManager im; + @Mock + private BukkitScheduler scheduler; - /** + @SuppressWarnings("unchecked") + @BeforeClass + public static void beforeClass() { + // This has to be done beforeClass otherwise the tests will interfere with each + // other + handler = mock(AbstractDatabaseHandler.class); + // Database + PowerMockito.mockStatic(DatabaseSetup.class); + DatabaseSetup dbSetup = mock(DatabaseSetup.class); + when(DatabaseSetup.getDatabase()).thenReturn(dbSetup); + when(dbSetup.getHandler(any())).thenReturn(handler); + } + + /** * @throws java.lang.Exception */ @SuppressWarnings("unchecked") @@ -160,7 +160,7 @@ public void setUp() throws Exception { when(island.getMemberSet()).thenReturn(iset); when(island.getOwner()).thenReturn(uuid); when(island.getWorld()).thenReturn(world); - when(island.getUniqueId()).thenReturn(UUID.randomUUID().toString()); + when(island.getUniqueId()).thenReturn(uuid.toString()); // Default to uuid's being island owners when(im.hasIsland(eq(world), any(UUID.class))).thenReturn(true); when(im.getIsland(world, uuid)).thenReturn(island); @@ -232,229 +232,218 @@ public void setUp() throws Exception { lm.migrate(); } - /** - * @throws java.lang.Exception - */ - @After - public void tearDown() throws Exception { - deleteAll(new File("database")); - User.clearUsers(); - Mockito.framework().clearInlineMocks(); - } + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + deleteAll(new File("database")); + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + } - private static void deleteAll(File file) throws IOException { - if (file.exists()) { - Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); - } + private static void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}. - */ - @Test - public void testCalculateLevel() { - Results results = new Results(); - results.setLevel(10000); - results.setInitialLevel(3); - lm.calculateLevel(uuid, island); - // Complete the pipelined completable future - cf.complete(results); - - assertEquals(10000L, lm.getLevelsData(island).getLevel()); - // Map tt = lm.getTopTen(world, 10); - // assertEquals(1, tt.size()); - // assertTrue(tt.get(uuid) == 10000); - assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); - - results.setLevel(5000); - lm.calculateLevel(uuid, island); - // Complete the pipelined completable future - cf.complete(results); - assertEquals(5000L, lm.getLevelsData(island).getLevel()); - // Still should be 10000 - assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); - - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}. + */ + @Test + public void testCalculateLevel() { + Results results = new Results(); + results.setLevel(10000); + results.setInitialLevel(3); + lm.calculateLevel(uuid, island); + // Complete the pipelined completable future + cf.complete(results); + + assertEquals(10000L, lm.getLevelsData(island).getLevel()); + // Map tt = lm.getTopTen(world, 10); + // assertEquals(1, tt.size()); + // assertTrue(tt.get(uuid) == 10000); + assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); + + results.setLevel(5000); + lm.calculateLevel(uuid, island); + // Complete the pipelined completable future + cf.complete(results); + assertEquals(5000L, lm.getLevelsData(island).getLevel()); + // Still should be 10000 + assertEquals(10000L, lm.getIslandMaxLevel(world, uuid)); - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}. - */ - @Test - public void testGetInitialLevel() { - assertEquals(0, lm.getInitialLevel(island)); - } + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testGetIslandLevel() { - assertEquals(-5, lm.getIslandLevel(world, uuid)); - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}. + */ + @Test + public void testGetInitialLevel() { + assertEquals(0, lm.getInitialLevel(island)); + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testGetPointsToNextString() { - // No island player - assertEquals("", lm.getPointsToNextString(world, UUID.randomUUID())); - // Player has island - assertEquals("0", lm.getPointsToNextString(world, uuid)); - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testGetIslandLevel() { + assertEquals(-5, lm.getIslandLevel(world, uuid)); + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testGetIslandLevelString() { - assertEquals("-5", lm.getIslandLevelString(world, uuid)); - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testGetPointsToNextString() { + // No island player + assertEquals("", lm.getPointsToNextString(world, UUID.randomUUID())); + // Player has island + assertEquals("0", lm.getPointsToNextString(world, uuid)); + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}. - */ - @Test - public void testGetLevelsData() { - assertEquals(levelsData, lm.getLevelsData(island)); + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testGetIslandLevelString() { + assertEquals("-5", lm.getIslandLevelString(world, uuid)); + } - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}. + */ + @Test + public void testGetLevelsData() { + assertEquals(levelsData, lm.getLevelsData(island)); - /** - * Test method for {@link world.bentobox.level.LevelsManager#formatLevel(long)}. - */ - @Test - public void testFormatLevel() { - assertEquals("123456789", lm.formatLevel(123456789L)); - when(settings.isShorthand()).thenReturn(true); - assertEquals("123.5M", lm.formatLevel(123456789L)); - assertEquals("1.2k", lm.formatLevel(1234L)); - assertEquals("123.5G", lm.formatLevel(123456789352L)); - assertEquals("1.2T", lm.formatLevel(1234567893524L)); - assertEquals("12345.7T", lm.formatLevel(12345678345345349L)); + } - } + /** + * Test method for {@link world.bentobox.level.LevelsManager#formatLevel(long)}. + */ + @Test + public void testFormatLevel() { + assertEquals("123456789", lm.formatLevel(123456789L)); + when(settings.isShorthand()).thenReturn(true); + assertEquals("123.5M", lm.formatLevel(123456789L)); + assertEquals("1.2k", lm.formatLevel(1234L)); + assertEquals("123.5G", lm.formatLevel(123456789352L)); + assertEquals("1.2T", lm.formatLevel(1234567893524L)); + assertEquals("12345.7T", lm.formatLevel(12345678345345349L)); - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. - */ - @Test - public void testGetTopTenEmpty() { - Map tt = lm.getTopTen(world, Level.TEN); - assertTrue(tt.isEmpty()); - } + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. - */ - @Test - public void testGetTopTen() { - testLoadTopTens(); - Map tt = lm.getTopTen(world, Level.TEN); - assertFalse(tt.isEmpty()); - assertEquals(1, tt.size()); - assertEquals(1, lm.getTopTen(world, 1).size()); - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. + */ + @Test + public void testGetTopTenEmpty() { + Map tt = lm.getTopTen(world, Level.TEN); + assertTrue(tt.isEmpty()); + } - /** - * Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. */ @Test - public void testGetTopTenNoOwners() { - when(im.hasIsland(eq(world), any(UUID.class))).thenReturn(false); - testLoadTopTens(); - Map tt = lm.getTopTen(world, Level.TEN); - assertTrue(tt.isEmpty()); + public void testGetTopTen() { + testLoadTopTens(); + Map tt = lm.getTopTen(world, Level.TEN); + assertFalse(tt.isEmpty()); + assertEquals(1, tt.size()); + assertEquals(1, lm.getTopTen(world, 1).size()); } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testHasTopTenPerm() { - assertTrue(lm.hasTopTenPerm(world, uuid)); - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testHasTopTenPerm() { + assertTrue(lm.hasTopTenPerm(world, uuid)); + } - /** - * Test method for {@link world.bentobox.level.LevelsManager#loadTopTens()}. - */ - @Test - public void testLoadTopTens() { - ArgumentCaptor task = ArgumentCaptor.forClass(Runnable.class); - lm.loadTopTens(); - PowerMockito.verifyStatic(Bukkit.class); // 1 - Bukkit.getScheduler(); - verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture()); - task.getValue().run(); - verify(addon).log("Generating rankings"); - verify(addon).log("Generated rankings for bskyblock-world"); + /** + * Test method for {@link world.bentobox.level.LevelsManager#loadTopTens()}. + */ + @Test + public void testLoadTopTens() { + ArgumentCaptor task = ArgumentCaptor.forClass(Runnable.class); + lm.loadTopTens(); + PowerMockito.verifyStatic(Bukkit.class); // 1 + Bukkit.getScheduler(); + verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture()); + task.getValue().run(); + verify(addon).log("Generating rankings"); + verify(addon).log("Generated rankings for bskyblock-world"); - } + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testRemoveEntry() { - testLoadTopTens(); - Map tt = lm.getTopTen(world, Level.TEN); - assertTrue(tt.containsKey(uuid)); - lm.removeEntry(world, uuid); - tt = lm.getTopTen(world, Level.TEN); - assertFalse(tt.containsKey(uuid)); - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testRemoveEntry() { + testLoadTopTens(); + Map tt = lm.getTopTen(world, Level.TEN); + assertTrue(tt.containsKey(uuid.toString())); + lm.removeEntry(world, uuid.toString()); + tt = lm.getTopTen(world, Level.TEN); + assertFalse(tt.containsKey(uuid.toString())); + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}. - */ - @Test - public void testSetInitialIslandLevel() { - lm.setInitialIslandLevel(island, Level.TEN); - assertEquals(Level.TEN, lm.getInitialLevel(island)); - } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}. + */ + @Test + public void testSetInitialIslandLevel() { + lm.setInitialIslandLevel(island, Level.TEN); + assertEquals(Level.TEN, lm.getInitialLevel(island)); + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}. - */ - @Test - public void testSetIslandLevel() { - lm.setIslandLevel(world, uuid, 1234); - assertEquals(1234, lm.getIslandLevel(world, uuid)); + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}. + */ + @Test + public void testSetIslandLevel() { + lm.setIslandLevel(world, uuid, 1234); + assertEquals(1234, lm.getIslandLevel(world, uuid)); - } + } - /** - * Test method for - * {@link world.bentobox.level.LevelsManager#getRank(World, UUID)} - */ - @Test - public void testGetRank() { - lm.createAndCleanRankings(world); - Map ttl = lm.getTopTenLists(); - Map tt = ttl.get(world).getTopTen(); - for (long i = 100; i < 150; i++) { - tt.put(UUID.randomUUID(), i); - } - // Put player as lowest rank - tt.put(uuid, 10L); - assertEquals(51, lm.getRank(world, uuid)); - // Put player as highest rank - tt.put(uuid, 1000L); - assertEquals(1, lm.getRank(world, uuid)); - // Unknown UUID - lowest rank + 1 - assertEquals(52, lm.getRank(world, UUID.randomUUID())); + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getRank(World, UUID)} + */ + @Test + public void testGetRank() { + lm.createAndCleanRankings(world); + Map ttl = lm.getTopTenLists(); + Map tt = ttl.get(world).getTopTen(); + for (long i = 100; i < 150; i++) { + tt.put(UUID.randomUUID().toString(), i); } + // Put island as lowest rank + tt.put(uuid.toString(), 10L); + assertEquals(51, lm.getRank(world, uuid)); + // Put island as highest rank + tt.put(uuid.toString(), 1000L); + assertEquals(1, lm.getRank(world, uuid)); + // Unknown UUID - lowest rank + 1 + assertEquals(52, lm.getRank(world, UUID.randomUUID())); + } } diff --git a/src/test/java/world/bentobox/level/PlaceholderManagerTest.java b/src/test/java/world/bentobox/level/PlaceholderManagerTest.java index b780b8e..63cc597 100644 --- a/src/test/java/world/bentobox/level/PlaceholderManagerTest.java +++ b/src/test/java/world/bentobox/level/PlaceholderManagerTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -10,9 +11,10 @@ import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -44,7 +46,7 @@ * */ @RunWith(PowerMockRunner.class) -@PrepareForTest({BentoBox.class}) +@PrepareForTest({ BentoBox.class }) public class PlaceholderManagerTest { @Mock @@ -54,7 +56,7 @@ public class PlaceholderManagerTest { @Mock private BentoBox plugin; - private PlaceholderManager pm; + private PlaceholderManager phm; @Mock private PlaceholdersManager bpm; @Mock @@ -67,13 +69,24 @@ public class PlaceholderManagerTest { private Island island; @Mock private User user; - private Map names = new HashMap<>(); - private static final List NAMES = List.of("tasty", "bento", "fred", "bonne", "cyprien", "mael", "joe", "horacio", "steph", "vicky"); - private Map islands = new HashMap<>(); - private Map map = new HashMap<>(); + private static final Map names = new LinkedHashMap<>(); + static { + names.put(UUID.randomUUID(), "tasty"); + names.put(UUID.randomUUID(), "bento"); + names.put(UUID.randomUUID(), "fred"); + names.put(UUID.randomUUID(), "bonne"); + names.put(UUID.randomUUID(), "cyprien"); + names.put(UUID.randomUUID(), "mael"); + names.put(UUID.randomUUID(), "joe"); + names.put(UUID.randomUUID(), "horacio"); + names.put(UUID.randomUUID(), "steph"); + names.put(UUID.randomUUID(), "vicky"); + } + private Map islands = new HashMap<>(); + private Map map = new HashMap<>(); private @NonNull IslandLevels data; @Mock - private PlayersManager players; + private PlayersManager pm; /** * @throws java.lang.Exception @@ -83,29 +96,31 @@ public void setUp() throws Exception { when(addon.getPlugin()).thenReturn(plugin); // Users - when(addon.getPlayers()).thenReturn(players); + when(addon.getPlayers()).thenReturn(pm); + // Users when(user.getWorld()).thenReturn(world); when(user.getLocation()).thenReturn(mock(Location.class)); - for (int i = 0; i < Level.TEN; i++) { - UUID uuid = UUID.randomUUID(); - names.put(uuid, NAMES.get(i)); - map.put(uuid, (long)(100 - i)); + int i = 0; + for (Entry n : names.entrySet()) { + UUID uuid = UUID.randomUUID(); // Random island ID + map.put(uuid.toString(), (long)(100 - i++)); // level Island is = new Island(); - is.setOwner(uuid); - is.setName(NAMES.get(i) + "'s island"); - islands.put(uuid, is); + is.setUniqueId(uuid.toString()); + is.setOwner(n.getKey()); + is.setName(n.getValue() + "'s island"); + islands.put(uuid.toString(), is); } // Sort map = map.entrySet().stream() .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); - when(players.getName(any())).thenAnswer((Answer) invocation -> names.getOrDefault(invocation.getArgument(0, UUID.class), "unknown")); + when(pm.getName(any())).thenAnswer((Answer) invocation -> names.getOrDefault(invocation.getArgument(0, UUID.class), "unknown")); Map members = new HashMap<>(); - map.forEach((uuid, l) -> members.put(uuid, RanksManager.MEMBER_RANK)); - islands.values().forEach(i -> i.setMembers(members)); + names.forEach((uuid, l) -> members.put(uuid, RanksManager.MEMBER_RANK)); + islands.values().forEach(is -> is.setMembers(members)); // Placeholders manager for plugin @@ -120,7 +135,8 @@ public void setUp() throws Exception { // Islands when(im.getIsland(any(World.class), any(User.class))).thenReturn(island); when(im.getIslandAt(any(Location.class))).thenReturn(Optional.of(island)); - when(im.getIsland(any(World.class), any(UUID.class))).thenAnswer((Answer) invocation -> islands.get(invocation.getArgument(1, UUID.class))); + when(im.getIslandById(anyString())).thenAnswer((Answer>) invocation -> Optional.of(islands.get(invocation.getArgument(0, String.class)))); + when(im.getIslands(any(), any(UUID.class))).thenReturn(new HashSet<>(islands.values())); when(addon.getIslands()).thenReturn(im); // Levels Manager @@ -136,127 +152,134 @@ public void setUp() throws Exception { when(lm.getLevelsData(island)).thenReturn(data); when(addon.getManager()).thenReturn(lm); - pm = new PlaceholderManager(addon); + phm = new PlaceholderManager(addon); } /** - * Test method for {@link world.bentobox.level.PlaceholderManager#PlaceholderManager(world.bentobox.level.Level)}. + * Test method for + * {@link world.bentobox.level.PlaceholderManager#PlaceholderManager(world.bentobox.level.Level)}. */ @Test public void testPlaceholderManager() { - verify(addon).getPlugin(); + verify(addon).getPlugin(); } /** - * Test method for {@link world.bentobox.level.PlaceholderManager#registerPlaceholders(world.bentobox.bentobox.api.addons.GameModeAddon)}. + * Test method for + * {@link world.bentobox.level.PlaceholderManager#registerPlaceholders(world.bentobox.bentobox.api.addons.GameModeAddon)}. */ @Test public void testRegisterPlaceholders() { - pm.registerPlaceholders(gm); - // Island Level - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level"), any()); - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level_raw"), any()); - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_total_points"), any()); + phm.registerPlaceholders(gm); + // Island Level + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level"), any()); + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level_raw"), any()); + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_total_points"), any()); - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_points_to_next_level"), any()); - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level_max"), any()); + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_points_to_next_level"), any()); + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_island_level_max"), any()); - // Visited Island Level - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_visited_island_level"), any()); + // Visited Island Level + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_visited_island_level"), any()); - // Register Top Ten Placeholders - for (int i = 1; i < 11; i++) { - // Name - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_name_" + i), any()); - // Island Name - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_island_name_" + i), any()); - // Members - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_members_" + i), any()); - // Level - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_value_" + i), any()); - } + // Register Top Ten Placeholders + for (int i = 1; i < 11; i++) { + // Name + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_name_" + i), any()); + // Island Name + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_island_name_" + i), any()); + // Members + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_members_" + i), any()); + // Level + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_top_value_" + i), any()); + } + + // Personal rank + verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_rank_value"), any()); - // Personal rank - verify(bpm).registerPlaceholder(eq(addon), eq("aoneblock_rank_value"), any()); - } /** - * Test method for {@link world.bentobox.level.PlaceholderManager#getRankName(org.bukkit.World, int)}. + * Test method for + * {@link world.bentobox.level.PlaceholderManager#getRankName(org.bukkit.World, int)}. */ @Test public void testGetRankName() { - // Test extremes - assertEquals("tasty", pm.getRankName(world, 0)); - assertEquals("vicky", pm.getRankName(world, 100)); - // Test the ranks - int rank = 1; - for (String name : NAMES) { - assertEquals(name, pm.getRankName(world, rank++)); - } - + // Test extremes + assertEquals("tasty", phm.getRankName(world, 0)); + assertEquals("vicky", phm.getRankName(world, 100)); + // Test the ranks + int rank = 1; + for (String name : names.values()) { + assertEquals(name, phm.getRankName(world, rank++)); + } + } /** - * Test method for {@link world.bentobox.level.PlaceholderManager#getRankIslandName(org.bukkit.World, int)}. + * Test method for + * {@link world.bentobox.level.PlaceholderManager#getRankIslandName(org.bukkit.World, int)}. */ @Test public void testGetRankIslandName() { - // Test extremes - assertEquals("tasty's island", pm.getRankIslandName(world, 0)); - assertEquals("vicky's island", pm.getRankIslandName(world, 100)); - // Test the ranks - int rank = 1; - for (String name : NAMES) { - assertEquals(name + "'s island", pm.getRankIslandName(world, rank++)); - } - + // Test extremes + assertEquals("tasty's island", phm.getRankIslandName(world, 0)); + assertEquals("vicky's island", phm.getRankIslandName(world, 100)); + // Test the ranks + int rank = 1; + for (String name : names.values()) { + assertEquals(name + "'s island", phm.getRankIslandName(world, rank++)); + } + } /** - * Test method for {@link world.bentobox.level.PlaceholderManager#getRankMembers(org.bukkit.World, int)}. + * Test method for + * {@link world.bentobox.level.PlaceholderManager#getRankMembers(org.bukkit.World, int)}. */ @Test public void testGetRankMembers() { - // Test extremes - check(1, pm.getRankMembers(world, 0)); - check(2, pm.getRankMembers(world, 100)); - // Test the ranks - for (int rank = 1; rank < 11; rank++) { - check(3, pm.getRankMembers(world, rank)); - } + // Test extremes + check(1, phm.getRankMembers(world, 0)); + check(2, phm.getRankMembers(world, 100)); + // Test the ranks + for (int rank = 1; rank < 11; rank++) { + check(3, phm.getRankMembers(world, rank)); + } } - + void check(int indicator, String list) { - for (String n : NAMES) { - assertTrue(n + " is missing for twst " + indicator, list.contains(n)); - } + for (String n : names.values()) { + assertTrue(n + " is missing for test " + indicator, list.contains(n)); + } } /** - * Test method for {@link world.bentobox.level.PlaceholderManager#getRankLevel(org.bukkit.World, int)}. + * Test method for + * {@link world.bentobox.level.PlaceholderManager#getRankLevel(org.bukkit.World, int)}. */ @Test public void testGetRankLevel() { - // Test extremes - assertEquals("100", pm.getRankLevel(world, 0)); - assertEquals("91", pm.getRankLevel(world, 100)); - // Test the ranks - for (int rank = 1; rank < 11; rank++) { - assertEquals(String.valueOf(101 - rank), pm.getRankLevel(world, rank)); - } - + // Test extremes + assertEquals("100", phm.getRankLevel(world, 0)); + assertEquals("91", phm.getRankLevel(world, 100)); + // Test the ranks + for (int rank = 1; rank < 11; rank++) { + assertEquals(String.valueOf(101 - rank), phm.getRankLevel(world, rank)); + } + } /** - * Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. + * Test method for + * {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. */ @Test public void testGetVisitedIslandLevelNullUser() { - assertEquals("", pm.getVisitedIslandLevel(gm, null)); - + assertEquals("", phm.getVisitedIslandLevel(gm, null)); + } - + /** * Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. */ @@ -264,26 +287,27 @@ public void testGetVisitedIslandLevelNullUser() { public void testGetVisitedIslandLevelUserNotInWorld() { // Another world when(user.getWorld()).thenReturn(mock(World.class)); - assertEquals("", pm.getVisitedIslandLevel(gm, user)); + assertEquals("", phm.getVisitedIslandLevel(gm, user)); } - + /** - * Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. + * Test method for + * {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. */ @Test public void testGetVisitedIslandLevel() { - assertEquals("1234567", pm.getVisitedIslandLevel(gm, user)); - + assertEquals("1234567", phm.getVisitedIslandLevel(gm, user)); + } - + /** * Test method for {@link world.bentobox.level.PlaceholderManager#getVisitedIslandLevel(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.api.user.User)}. */ @Test public void testGetVisitedIslandLevelNoIsland() { when(im.getIslandAt(any(Location.class))).thenReturn(Optional.empty()); - assertEquals("0", pm.getVisitedIslandLevel(gm, user)); + assertEquals("0", phm.getVisitedIslandLevel(gm, user)); } diff --git a/src/test/java/world/bentobox/level/commands/AdminStatsCommandTest.java b/src/test/java/world/bentobox/level/commands/AdminStatsCommandTest.java index f00cd24..21d26d0 100644 --- a/src/test/java/world/bentobox/level/commands/AdminStatsCommandTest.java +++ b/src/test/java/world/bentobox/level/commands/AdminStatsCommandTest.java @@ -52,132 +52,132 @@ @PrepareForTest({ Bukkit.class, BentoBox.class }) public class AdminStatsCommandTest { - @Mock - private CompositeCommand ic; - private UUID uuid; - @Mock - private User user; - @Mock - private IslandsManager im; - @Mock - private Island island; - @Mock - private Level addon; - @Mock - private World world; - @Mock - private IslandWorldManager iwm; - @Mock - private GameModeAddon gameModeAddon; - @Mock - private Player p; - @Mock - private LocalesManager lm; - @Mock - private PlayersManager pm; - - private AdminStatsCommand asc; - private TopTenData ttd; - @Mock - private LevelsManager manager; - @Mock - private Server server; - - @Before - public void setUp() { - // Set up plugin - BentoBox plugin = mock(BentoBox.class); - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - User.setPlugin(plugin); - when(addon.getPlugin()).thenReturn(plugin); - - // Addon - when(ic.getAddon()).thenReturn(addon); - when(ic.getPermissionPrefix()).thenReturn("bskyblock."); - when(ic.getLabel()).thenReturn("island"); - when(ic.getTopLabel()).thenReturn("island"); - when(ic.getWorld()).thenReturn(world); - when(ic.getTopLabel()).thenReturn("bsb"); - - // IWM friendly name - when(plugin.getIWM()).thenReturn(iwm); - when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); - - // World - when(world.toString()).thenReturn("world"); - when(world.getName()).thenReturn("BSkyBlock_world"); - - // Player manager - when(plugin.getPlayers()).thenReturn(pm); - when(pm.getUser(anyString())).thenReturn(user); - // topTen - when(addon.getManager()).thenReturn(manager); - // User - uuid = UUID.randomUUID(); - when(user.getUniqueId()).thenReturn(uuid); - when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); - - // Bukkit - PowerMockito.mockStatic(Bukkit.class); - when(Bukkit.getServer()).thenReturn(server); - // Mock item factory (for itemstacks) - ItemFactory itemFactory = mock(ItemFactory.class); - ItemMeta itemMeta = mock(ItemMeta.class); - when(itemFactory.getItemMeta(any())).thenReturn(itemMeta); - when(server.getItemFactory()).thenReturn(itemFactory); - when(Bukkit.getItemFactory()).thenReturn(itemFactory); - - // Top ten - ttd = new TopTenData(world); - Map topten = new HashMap<>(); - Random r = new Random(); - for (int i = 0; i < 1000; i++) { - topten.put(UUID.randomUUID(), r.nextLong(20000)); - } - ttd.setTopTen(topten); - asc = new AdminStatsCommand(addon, ic); - } - - @After - public void tearDown() { - User.clearUsers(); - } - - /** - * Test method for - * {@link world.bentobox.level.commands.AdminStatsCommand#setup()}. - */ - @Test - public void testSetup() { - assertEquals("bskyblock.admin.stats", asc.getPermission()); - assertFalse(asc.isOnlyPlayer()); - assertEquals("admin.stats.description", asc.getDescription()); - - } - - /** - * Test method for - * {@link world.bentobox.level.commands.AdminStatsCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testExecuteUserStringListOfString() { - assertFalse(asc.execute(user, "", List.of())); - verify(user).sendMessage("admin.stats.title"); - verify(user).sendMessage("admin.stats.no-data"); - } - - /** - * Test method for - * {@link world.bentobox.level.commands.AdminStatsCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testExecuteUserStringListOfStringLevels() { - Map map = new HashMap<>(); - map.put(world, ttd); - when(manager.getTopTenLists()).thenReturn(map); - assertTrue(asc.execute(user, "", List.of())); - verify(user).sendMessage("admin.stats.title"); - verify(user, never()).sendMessage("admin.stats.no-data"); + @Mock + private CompositeCommand ic; + private UUID uuid; + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private Level addon; + @Mock + private World world; + @Mock + private IslandWorldManager iwm; + @Mock + private GameModeAddon gameModeAddon; + @Mock + private Player p; + @Mock + private LocalesManager lm; + @Mock + private PlayersManager pm; + + private AdminStatsCommand asc; + private TopTenData ttd; + @Mock + private LevelsManager manager; + @Mock + private Server server; + + @Before + public void setUp() { + // Set up plugin + BentoBox plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + User.setPlugin(plugin); + when(addon.getPlugin()).thenReturn(plugin); + + // Addon + when(ic.getAddon()).thenReturn(addon); + when(ic.getPermissionPrefix()).thenReturn("bskyblock."); + when(ic.getLabel()).thenReturn("island"); + when(ic.getTopLabel()).thenReturn("island"); + when(ic.getWorld()).thenReturn(world); + when(ic.getTopLabel()).thenReturn("bsb"); + + // IWM friendly name + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); + + // World + when(world.toString()).thenReturn("world"); + when(world.getName()).thenReturn("BSkyBlock_world"); + + // Player manager + when(plugin.getPlayers()).thenReturn(pm); + when(pm.getUser(anyString())).thenReturn(user); + // topTen + when(addon.getManager()).thenReturn(manager); + // User + uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); + + // Bukkit + PowerMockito.mockStatic(Bukkit.class); + when(Bukkit.getServer()).thenReturn(server); + // Mock item factory (for itemstacks) + ItemFactory itemFactory = mock(ItemFactory.class); + ItemMeta itemMeta = mock(ItemMeta.class); + when(itemFactory.getItemMeta(any())).thenReturn(itemMeta); + when(server.getItemFactory()).thenReturn(itemFactory); + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + + // Top ten + ttd = new TopTenData(world); + Map topten = new HashMap<>(); + Random r = new Random(); + for (int i = 0; i < 1000; i++) { + topten.put(UUID.randomUUID().toString(), r.nextLong(20000)); } + ttd.setTopTen(topten); + asc = new AdminStatsCommand(addon, ic); + } + + @After + public void tearDown() { + User.clearUsers(); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.AdminStatsCommand#setup()}. + */ + @Test + public void testSetup() { + assertEquals("bskyblock.admin.stats", asc.getPermission()); + assertFalse(asc.isOnlyPlayer()); + assertEquals("admin.stats.description", asc.getDescription()); + + } + + /** + * Test method for + * {@link world.bentobox.level.commands.AdminStatsCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfString() { + assertFalse(asc.execute(user, "", List.of())); + verify(user).sendMessage("admin.stats.title"); + verify(user).sendMessage("admin.stats.no-data"); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.AdminStatsCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringLevels() { + Map map = new HashMap<>(); + map.put(world, ttd); + when(manager.getTopTenLists()).thenReturn(map); + assertTrue(asc.execute(user, "", List.of())); + verify(user).sendMessage("admin.stats.title"); + verify(user, never()).sendMessage("admin.stats.no-data"); + } } diff --git a/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java b/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java index af38901..9e82384 100644 --- a/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java +++ b/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java @@ -5,12 +5,12 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Collections; +import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; @@ -51,122 +51,130 @@ @PrepareForTest({ Bukkit.class, BentoBox.class }) public class AdminTopRemoveCommandTest { - @Mock - private CompositeCommand ic; - private UUID uuid; - @Mock - private User user; - @Mock - private IslandsManager im; - @Mock - private Island island; - @Mock - private Level addon; - @Mock - private World world; - @Mock - private IslandWorldManager iwm; - @Mock - private GameModeAddon gameModeAddon; - @Mock - private Player p; - @Mock - private LocalesManager lm; - @Mock - private PlayersManager pm; - - private AdminTopRemoveCommand atrc; - @Mock - private TopTenData ttd; - @Mock - private LevelsManager manager; - @Mock - private Server server; - - @Before - public void setUp() { - // Set up plugin - BentoBox plugin = mock(BentoBox.class); - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - User.setPlugin(plugin); - // Addon - when(ic.getAddon()).thenReturn(addon); - when(ic.getPermissionPrefix()).thenReturn("bskyblock."); - when(ic.getLabel()).thenReturn("island"); - when(ic.getTopLabel()).thenReturn("island"); - when(ic.getWorld()).thenReturn(world); - when(ic.getTopLabel()).thenReturn("bsb"); - - // IWM friendly name - when(plugin.getIWM()).thenReturn(iwm); - when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); - - // World - when(world.toString()).thenReturn("world"); - when(world.getName()).thenReturn("BSkyBlock_world"); - - // Player manager - when(plugin.getPlayers()).thenReturn(pm); - when(pm.getUser(anyString())).thenReturn(user); - // topTen - when(addon.getManager()).thenReturn(manager); - // User - uuid = UUID.randomUUID(); - when(user.getUniqueId()).thenReturn(uuid); - when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); - - // Bukkit - PowerMockito.mockStatic(Bukkit.class); - when(Bukkit.getServer()).thenReturn(server); - // Mock item factory (for itemstacks) - ItemFactory itemFactory = mock(ItemFactory.class); - ItemMeta itemMeta = mock(ItemMeta.class); - when(itemFactory.getItemMeta(any())).thenReturn(itemMeta); - when(server.getItemFactory()).thenReturn(itemFactory); - when(Bukkit.getItemFactory()).thenReturn(itemFactory); - - atrc = new AdminTopRemoveCommand(addon, ic); - } - - @After - public void tearDown() { - User.clearUsers(); - } - - /** - * Test method for - * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#AdminTopRemoveCommand(world.bentobox.level.Level, world.bentobox.bentobox.api.commands.CompositeCommand)}. - */ - @Test - public void testAdminTopRemoveCommand() { - assertEquals("remove", atrc.getLabel()); - assertEquals("delete", atrc.getAliases().get(0)); - } - - /** - * Test method for - * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#setup()}. - */ - @Test - public void testSetup() { - assertEquals("bskyblock.admin.top.remove", atrc.getPermission()); - assertEquals("admin.top.remove.parameters", atrc.getParameters()); - assertEquals("admin.top.remove.description", atrc.getDescription()); - assertFalse(atrc.isOnlyPlayer()); - - } - - /** - * Test method for - * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testCanExecuteWrongArgs() { - assertFalse(atrc.canExecute(user, "delete", Collections.emptyList())); - verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "BSkyBlock"); - } - - /** + @Mock + private CompositeCommand ic; + private UUID uuid; + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private Island island; + @Mock + private Level addon; + @Mock + private World world; + @Mock + private IslandWorldManager iwm; + @Mock + private GameModeAddon gameModeAddon; + @Mock + private Player p; + @Mock + private LocalesManager lm; + @Mock + private PlayersManager pm; + + private AdminTopRemoveCommand atrc; + @Mock + private TopTenData ttd; + @Mock + private LevelsManager manager; + @Mock + private Server server; + + @Before + public void setUp() { + // Set up plugin + BentoBox plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + User.setPlugin(plugin); + + // Addon + when(ic.getAddon()).thenReturn(addon); + when(ic.getPermissionPrefix()).thenReturn("bskyblock."); + when(ic.getLabel()).thenReturn("island"); + when(ic.getTopLabel()).thenReturn("island"); + when(ic.getWorld()).thenReturn(world); + when(ic.getTopLabel()).thenReturn("bsb"); + + // IWM friendly name + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); + + // World + when(world.toString()).thenReturn("world"); + when(world.getName()).thenReturn("BSkyBlock_world"); + + // Player manager + when(plugin.getPlayers()).thenReturn(pm); + when(pm.getUser(anyString())).thenReturn(user); + // topTen + when(addon.getManager()).thenReturn(manager); + // User + uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); + // Island + when(island.getUniqueId()).thenReturn(uuid.toString()); + when(island.getOwner()).thenReturn(uuid); + // Island Manager + when(plugin.getIslands()).thenReturn(im); + when(im.getIslands(any(), any(User.class))).thenReturn(Set.of(island)); + when(im.getIslands(any(), any(UUID.class))).thenReturn(Set.of(island)); + + // Bukkit + PowerMockito.mockStatic(Bukkit.class); + when(Bukkit.getServer()).thenReturn(server); + // Mock item factory (for itemstacks) + ItemFactory itemFactory = mock(ItemFactory.class); + ItemMeta itemMeta = mock(ItemMeta.class); + when(itemFactory.getItemMeta(any())).thenReturn(itemMeta); + when(server.getItemFactory()).thenReturn(itemFactory); + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + + atrc = new AdminTopRemoveCommand(addon, ic); + } + + @After + public void tearDown() { + User.clearUsers(); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#AdminTopRemoveCommand(world.bentobox.level.Level, world.bentobox.bentobox.api.commands.CompositeCommand)}. + */ + @Test + public void testAdminTopRemoveCommand() { + assertEquals("remove", atrc.getLabel()); + assertEquals("delete", atrc.getAliases().get(0)); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#setup()}. + */ + @Test + public void testSetup() { + assertEquals("bskyblock.admin.top.remove", atrc.getPermission()); + assertEquals("admin.top.remove.parameters", atrc.getParameters()); + assertEquals("admin.top.remove.description", atrc.getDescription()); + assertFalse(atrc.isOnlyPlayer()); + + } + + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteWrongArgs() { + assertFalse(atrc.canExecute(user, "delete", Collections.emptyList())); + verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "BSkyBlock"); + } + + /** * Test method for {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. */ @Test @@ -176,25 +184,25 @@ public void testCanExecuteUnknown() { verify(user).sendMessage("general.errors.unknown-player", TextVariables.NAME, "tastybento"); } - /** - * Test method for - * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testCanExecuteKnown() { - assertTrue(atrc.canExecute(user, "delete", Collections.singletonList("tastybento"))); - } - - /** - * Test method for - * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testExecuteUserStringListOfString() { - testCanExecuteKnown(); - assertTrue(atrc.execute(user, "delete", Collections.singletonList("tastybento"))); - verify(manager).removeEntry(any(World.class), eq(uuid)); - verify(user).sendMessage("general.success"); - } + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteKnown() { + assertTrue(atrc.canExecute(user, "delete", Collections.singletonList("tastybento"))); + } + + /** + * Test method for + * {@link world.bentobox.level.commands.admin.AdminTopRemoveCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfString() { + testCanExecuteKnown(); + assertTrue(atrc.execute(user, "delete", Collections.singletonList("tastybento"))); + verify(manager).removeEntry(world, uuid.toString()); + verify(user).sendMessage("general.success"); + } } From 117d15f3d021dc88d0546c6a00766789f90184af Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 24 Nov 2023 09:33:16 -0800 Subject: [PATCH 082/106] Added more placeholders. #296 Refactored how the top ten maps are structured. In the future, it may be best to have the key be the island. --- .../world/bentobox/level/LevelsManager.java | 35 ++++++ .../bentobox/level/PlaceholderManager.java | 101 +++++++++--------- .../bentobox/level/LevelsManagerTest.java | 13 +++ .../level/PlaceholderManagerTest.java | 49 ++++++--- 4 files changed, 135 insertions(+), 63 deletions(-) diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index e9193d7..f3532aa 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -2,6 +2,7 @@ import java.math.BigInteger; import java.text.DecimalFormat; +import java.util.AbstractMap; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -293,6 +294,40 @@ public String getPointsToNextString(@NonNull World world, @Nullable UUID targetP return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel()); } + /** + * Get the weighted top ten for this world. Weighting is based on number of + * players per team. + * + * @param world - world requested + * @param size - size of the top ten + * @return sorted top ten map. The key is the island unique ID + */ + @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 -> { + + 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); + + } + /** * Get the top ten for this world. Returns offline players or players with the * intopten permission. diff --git a/src/main/java/world/bentobox/level/PlaceholderManager.java b/src/main/java/world/bentobox/level/PlaceholderManager.java index 8277a64..5f0bd17 100644 --- a/src/main/java/world/bentobox/level/PlaceholderManager.java +++ b/src/main/java/world/bentobox/level/PlaceholderManager.java @@ -2,7 +2,6 @@ import java.util.Collections; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -66,19 +65,29 @@ protected void registerPlaceholders(GameModeAddon gm) { final int rank = i; // Name bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_name_" + i, - u -> getRankName(gm.getOverWorld(), rank)); + u -> getRankName(gm.getOverWorld(), rank, false)); // Island Name bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_island_name_" + i, - u -> getRankIslandName(gm.getOverWorld(), rank)); + u -> getRankIslandName(gm.getOverWorld(), rank, false)); // Members bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_members_" + i, - u -> getRankMembers(gm.getOverWorld(), rank)); + u -> getRankMembers(gm.getOverWorld(), rank, false)); // Level bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_value_" + i, - u -> getRankLevel(gm.getOverWorld(), rank)); + u -> getRankLevel(gm.getOverWorld(), rank, false)); + // Weighted Level Name (Level / number of members) + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_weighted_name_" + i, + u -> getRankName(gm.getOverWorld(), rank, true)); + // Weighted Island Name + bpm.registerPlaceholder(addon, + gm.getDescription().getName().toLowerCase() + "_top_weighted_island_name_" + i, + u -> getRankIslandName(gm.getOverWorld(), rank, true)); + // Weighted Members + bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_weighted_members_" + i, + u -> getRankMembers(gm.getOverWorld(), rank, true)); // Weighted Level (Level / number of members) bpm.registerPlaceholder(addon, gm.getDescription().getName().toLowerCase() + "_top_weighted_value_" + i, - u -> getWeightedRankLevel(gm.getOverWorld(), rank)); + u -> getRankLevel(gm.getOverWorld(), rank, true)); } // Personal rank @@ -89,13 +98,18 @@ protected void registerPlaceholders(GameModeAddon gm) { /** * Get the name of the owner of the island who holds the rank in this world. * - * @param world world - * @param rank rank 1 to 10 + * @param world world + * @param rank rank 1 to 10 + * @param weighted if true, then the weighted rank name is returned * @return rank name */ - String getRankName(World world, int rank) { + String getRankName(World world, int rank, boolean weighted) { // Ensure rank is within bounds rank = Math.max(1, Math.min(rank, Level.TEN)); + if (weighted) { + return addon.getManager().getWeightedTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L) + .findFirst().map(Island::getOwner).map(addon.getPlayers()::getName).orElse(""); + } @Nullable UUID owner = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L) .findFirst().flatMap(addon.getIslands()::getIslandById).map(Island::getOwner).orElse(null); @@ -106,13 +120,18 @@ String getRankName(World world, int rank) { /** * Get the island name for this rank * - * @param world world - * @param rank rank 1 to 10 + * @param world world + * @param rank rank 1 to 10 + * @param weighted if true, then the weighted rank name is returned * @return name of island or nothing if there isn't one */ - String getRankIslandName(World world, int rank) { + String getRankIslandName(World world, int rank, boolean weighted) { // Ensure rank is within bounds rank = Math.max(1, Math.min(rank, Level.TEN)); + if (weighted) { + return addon.getManager().getWeightedTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L) + .findFirst().map(Island::getName).orElse(""); + } return addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L).findFirst() .flatMap(addon.getIslands()::getIslandById).map(Island::getName).orElse(""); } @@ -120,13 +139,23 @@ String getRankIslandName(World world, int rank) { /** * Gets a comma separated string of island member names * - * @param world world - * @param rank rank to request + * @param world world + * @param rank rank to request + * @param weighted if true, then the weighted rank name is returned * @return comma separated string of island member names */ - String getRankMembers(World world, int rank) { + String getRankMembers(World world, int rank, boolean weighted) { // Ensure rank is within bounds rank = Math.max(1, Math.min(rank, Level.TEN)); + if (weighted) { + return addon.getManager().getWeightedTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L).limit(1L) + .findFirst() + .map(is -> is.getMembers().entrySet().stream().filter(e -> e.getValue() >= RanksManager.MEMBER_RANK) + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).map(Map.Entry::getKey) + .map(addon.getPlayers()::getName).collect(Collectors.joining(","))) + .orElse(""); + } + Optional island = addon.getManager().getTopTen(world, Level.TEN).keySet().stream().skip(rank - 1L) .limit(1L).findFirst().flatMap(addon.getIslands()::getIslandById); @@ -140,44 +169,20 @@ String getRankMembers(World world, int rank) { } /** - * Gets the weighted level, which is the level / number of players + * Get the level for the rank requested * - * @param world world - * @param rank level - * @return weighted level + * @param world world + * @param rank rank wanted + * @param weighted true if weighted (level/number of team members) + * @return level for the rank requested */ - String getWeightedRankLevel(World world, int rank) { + String getRankLevel(World world, int rank, boolean weighted) { // Ensure rank is within bounds rank = Math.max(1, Math.min(rank, Level.TEN)); - - // Retrieve the top ten entries - Map topTen = addon.getManager().getTopTen(world, Level.TEN); - if (topTen.isEmpty()) { - return ""; + if (weighted) { + return addon.getManager().formatLevel(addon.getManager().getWeightedTopTen(world, Level.TEN).values() + .stream().skip(rank - 1L).limit(1L).findFirst().orElse(null)); } - - // Find the entry corresponding to the rank - Entry entry = topTen.entrySet().stream().skip(rank - 1).findFirst().orElse(null); - if (entry == null) { - return ""; - } - - // Calculate the score - Island island = addon.getIslands().getIslandById(entry.getKey()).orElse(null); - if (island == null || island.getMemberSet().isEmpty()) { - return ""; - } - - double score = (double) entry.getValue() / island.getMemberSet().size(); - - // Format and return the level - return addon.getManager().formatLevel((long) score); - - } - - String getRankLevel(World world, int rank) { - // Ensure rank is within bounds - rank = Math.max(1, Math.min(rank, Level.TEN)); return addon.getManager().formatLevel(addon.getManager().getTopTen(world, Level.TEN).values().stream() .skip(rank - 1L).limit(1L).findFirst().orElse(null)); } diff --git a/src/test/java/world/bentobox/level/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java index 44b1b9b..551c573 100644 --- a/src/test/java/world/bentobox/level/LevelsManagerTest.java +++ b/src/test/java/world/bentobox/level/LevelsManagerTest.java @@ -364,6 +364,19 @@ public void testGetTopTen() { assertEquals(1, lm.getTopTen(world, 1).size()); } + /** + * Test method for + * {@link world.bentobox.level.LevelsManager#getWeightedTopTen(org.bukkit.World, int)}. + */ + @Test + public void testGetWeightedTopTen() { + testLoadTopTens(); + Map tt = lm.getWeightedTopTen(world, Level.TEN); + assertFalse(tt.isEmpty()); + assertEquals(1, tt.size()); + assertEquals(1, lm.getTopTen(world, 1).size()); + } + /** * Test method for * {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}. diff --git a/src/test/java/world/bentobox/level/PlaceholderManagerTest.java b/src/test/java/world/bentobox/level/PlaceholderManagerTest.java index 63cc597..56f0a1d 100644 --- a/src/test/java/world/bentobox/level/PlaceholderManagerTest.java +++ b/src/test/java/world/bentobox/level/PlaceholderManagerTest.java @@ -83,7 +83,8 @@ public class PlaceholderManagerTest { names.put(UUID.randomUUID(), "vicky"); } private Map islands = new HashMap<>(); - private Map map = new HashMap<>(); + private Map map = new LinkedHashMap<>(); + private Map map2 = new LinkedHashMap<>(); private @NonNull IslandLevels data; @Mock private PlayersManager pm; @@ -105,13 +106,14 @@ public void setUp() throws Exception { int i = 0; for (Entry n : names.entrySet()) { UUID uuid = UUID.randomUUID(); // Random island ID - map.put(uuid.toString(), (long)(100 - i++)); // level + Long value = (long)(100 - i++); + map.put(uuid.toString(), value); // level Island is = new Island(); is.setUniqueId(uuid.toString()); is.setOwner(n.getKey()); is.setName(n.getValue() + "'s island"); islands.put(uuid.toString(), is); - + map2.put(is, value); } // Sort map = map.entrySet().stream() @@ -145,6 +147,7 @@ public void setUp() throws Exception { when(lm.getPointsToNextString(any(), any())).thenReturn("1234567"); when(lm.getIslandMaxLevel(any(), any())).thenReturn(987654L); when(lm.getTopTen(world, Level.TEN)).thenReturn(map); + when(lm.getWeightedTopTen(world, Level.TEN)).thenReturn(map2); when(lm.formatLevel(any())).thenAnswer((Answer) invocation -> invocation.getArgument(0, Long.class).toString()); data = new IslandLevels("uniqueId"); @@ -206,12 +209,12 @@ public void testRegisterPlaceholders() { @Test public void testGetRankName() { // Test extremes - assertEquals("tasty", phm.getRankName(world, 0)); - assertEquals("vicky", phm.getRankName(world, 100)); + assertEquals("tasty", phm.getRankName(world, 0, false)); + assertEquals("vicky", phm.getRankName(world, 100, false)); // Test the ranks int rank = 1; for (String name : names.values()) { - assertEquals(name, phm.getRankName(world, rank++)); + assertEquals(name, phm.getRankName(world, rank++, false)); } } @@ -223,12 +226,12 @@ public void testGetRankName() { @Test public void testGetRankIslandName() { // Test extremes - assertEquals("tasty's island", phm.getRankIslandName(world, 0)); - assertEquals("vicky's island", phm.getRankIslandName(world, 100)); + assertEquals("tasty's island", phm.getRankIslandName(world, 0, false)); + assertEquals("vicky's island", phm.getRankIslandName(world, 100, false)); // Test the ranks int rank = 1; for (String name : names.values()) { - assertEquals(name + "'s island", phm.getRankIslandName(world, rank++)); + assertEquals(name + "'s island", phm.getRankIslandName(world, rank++, false)); } } @@ -240,11 +243,11 @@ public void testGetRankIslandName() { @Test public void testGetRankMembers() { // Test extremes - check(1, phm.getRankMembers(world, 0)); - check(2, phm.getRankMembers(world, 100)); + check(1, phm.getRankMembers(world, 0, false)); + check(2, phm.getRankMembers(world, 100, false)); // Test the ranks for (int rank = 1; rank < 11; rank++) { - check(3, phm.getRankMembers(world, rank)); + check(3, phm.getRankMembers(world, rank, false)); } } @@ -261,11 +264,27 @@ void check(int indicator, String list) { @Test public void testGetRankLevel() { // Test extremes - assertEquals("100", phm.getRankLevel(world, 0)); - assertEquals("91", phm.getRankLevel(world, 100)); + assertEquals("100", phm.getRankLevel(world, 0, false)); + assertEquals("91", phm.getRankLevel(world, 100, false)); // Test the ranks for (int rank = 1; rank < 11; rank++) { - assertEquals(String.valueOf(101 - rank), phm.getRankLevel(world, rank)); + assertEquals(String.valueOf(101 - rank), phm.getRankLevel(world, rank, false)); + } + + } + + /** + * Test method for + * {@link world.bentobox.level.PlaceholderManager#getRankLevel(org.bukkit.World, int)}. + */ + @Test + public void testGetWeightedRankLevel() { + // Test extremes + assertEquals("100", phm.getRankLevel(world, 0, true)); + assertEquals("91", phm.getRankLevel(world, 100, true)); + // Test the ranks + for (int rank = 1; rank < 11; rank++) { + assertEquals(String.valueOf(101 - rank), phm.getRankLevel(world, rank, true)); } } From cfb35909f0223c655f07f15c72fda98beca5d25f Mon Sep 17 00:00:00 2001 From: "gitlocalize-app[bot]" <55277160+gitlocalize-app[bot]@users.noreply.github.com> Date: Sun, 26 Nov 2023 10:06:35 -0800 Subject: [PATCH 083/106] Translate uk.yml via GitLocalize (#297) Co-authored-by: GIGABAIT --- src/main/resources/locales/uk.yml | 184 ++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 src/main/resources/locales/uk.yml diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml new file mode 100644 index 0000000..14962a0 --- /dev/null +++ b/src/main/resources/locales/uk.yml @@ -0,0 +1,184 @@ +--- +admin: + level: + parameters: "" + description: розрахувати рівень острова для гравця + sethandicap: + parameters: " " + description: встановити гандикап острова, як правило, рівень острова стартера + changed: "&a Початковий гандикап острова змінено з [number] на [new_number]." + invalid-level: "&c Недійсний гандикап. Використовуйте ціле число." + levelstatus: + description: показати, скільки островів у черзі на сканування + islands-in-queue: "&a Острови в черзі: [number]" + top: + description: показати першу десятку списку + unknown-world: "&c Невідомий світ!" + display: "&f[rank]. &a[name] &7- &b[level]" + remove: + description: видалити гравця з першої десятки + parameters: "" + stats: + description: показати статистику островів на цьому сервері + title: Статистика острова сервера + world: "&a [name]" + no-data: "&c Немає даних для обробки." + average-level: 'Середній рівень острова: [number]' + median-level: 'Середній рівень острова: [number]' + mode-level: 'Рівень острова режиму: [number]' + highest-level: 'Найвищий рівень острова: [number]' + lowest-level: 'Найнижчий рівень острова: [number]' + distribution: 'Розподіл на рівні острова:' + islands: острови +island: + level: + parameters: "[player]" + description: обчисліть свій рівень острова або покажіть рівень [player] + calculating: "&a Розрахунок рівня..." + estimated-wait: "&a Приблизне очікування: [number] секунд" + in-queue: "&a Ви номер [number] у черзі" + island-level-is: "&a Рівень острова &b[level]" + required-points-to-next-level: "&a [points] потрібні бали до наступного рівня" + deaths: "&c([number] смерті)" + cooldown: "&c Ви повинні зачекати &b[time] &c секунд, поки ви зможете зробити + це знову" + in-progress: "&6 Розрахунок рівня острова триває..." + time-out: "&c Розрахунок рівня тривав занадто довго. Будь-ласка спробуйте пізніше." + top: + description: показати першу десятку + gui-title: "& Десятка Кращих" + gui-heading: "&6[name]: &B[rank]" + island-level: "&b Рівень [level]" + warp-to: "&A Варп на острів [name]." + level-details: + above-sea-level-blocks: Блоки над рівнем моря + spawners: Спавера + underwater-blocks: Підводні блоки + all-blocks: Всі блоки + no-island: "&c Немає острова!" + names-island: острів [name]. + syntax: "[name] x [number]" + hint: "&c Запустіть рівень, щоб переглянути звіт про блокування" +level: + commands: + value: + parameters: "[hand|]" + description: показує значення блоків. Додайте 'hand' в кінці, щоб відобразити + значення предмета в руках. + gui: + titles: + top: "&0&l Топ островів" + detail-panel: "&0&l острів [name]." + value-panel: "&0&l Значення блоку" + buttons: + island: + empty: "&f&l [name]. місце" + name: "&f&l [name]" + description: |- + [owner] + [members] + [place] + [level] + owners-island: Острів [player]. + owner: "&7&l Власник: &r&b [player]" + members-title: "&7&l Члени:" + member: "&b - [player]" + unknown: невідомий + place: "&7&o [number]. &r&7 місце" + level: "&7 Рівень: &o [number]" + material: + name: "&f&l [number] x [material]" + description: |- + [description] + [count] + [value] + [calculated] + [limit] + [id] + id: "&7 Ідентифікатор блоку: &e [id]" + value: "&7 Значення блоку: &e [number]" + limit: "&7 Обмеження блоку: &e [number]" + count: "&7 Кількість блоків: &e [number]" + calculated: "&7 Розраховане значення: &e [number]" + all_blocks: + name: "&f&l Усі блоки" + description: |- + &7 Показати всі блоки + &7 на острові. + above_sea_level: + name: "&f&l Блоки над рівнем моря" + description: |- + &7 Показувати лише блоки + &7, які знаходяться над морем + &7 рівень. + underwater: + name: "&f&l Блоки під рівнем моря" + description: |- + &7 Показувати лише блоки + &7, які знаходяться нижче моря + &7 рівень. + spawner: + name: "&f&l Спанера" + description: "&7 Відображати лише спавнери." + filters: + name: + name: "&f&l Сортувати за назвою" + description: "&7 Сортувати всі блоки за назвою." + value: + name: "&f&l Сортувати за значенням" + description: "&7 Сортувати всі блоки за їх значенням." + count: + name: "&f&l Сортувати за кількістю" + description: "&7 Відсортуйте всі блоки за їх кількістю." + value: + name: "&f&l [material]" + description: |- + [description] + [value] + [underwater] + [limit] + [id] + id: "&7 Ідентифікатор блоку: &e [id]" + value: "&7 Значення блоку: &e [number]" + underwater: "&7 Нижче рівня моря: &e [number]" + limit: "&7 Обмеження блоку: &e [number]" + previous: + name: "&f&l Попередня сторінка" + description: "&7 Перейти на сторінку [number]." + next: + name: "&f&l Наступна сторінка" + description: "&7 Перейти на сторінку [number]." + search: + name: "&f&l Пошук" + description: |- + &7 Пошук конкретного + &7 значення. + search: "&b Значення: [value]" + tips: + click-to-view: "&e Натисніть &7, щоб переглянути." + click-to-previous: "&e Натисніть &7, щоб переглянути попередню сторінку." + click-to-next: "&e Натисніть &7, щоб переглянути наступну сторінку." + click-to-select: "&e Натисніть &7, щоб вибрати." + left-click-to-cycle-up: "&e Клацніть лівою кнопкою миші &7, щоб перейти вгору." + right-click-to-cycle-down: "&e Клацніть правою кнопкою миші &7, щоб перейти + вниз." + left-click-to-change: "&e Клацніть лівою кнопкою миші &7 для редагування." + right-click-to-clear: "&e Клацніть правою кнопкою миші &7, щоб очистити." + click-to-asc: "&e Клацніть &7, щоб відсортувати в порядку збільшення." + click-to-desc: "&e Клацніть &7, щоб відсортувати в порядку зменшення." + click-to-warp: "&e Натисніть &7, щоб деформувати." + click-to-visit: "&e Натисніть &7, щоб відвідати." + right-click-to-visit: "&e Клацніть правою кнопкою миші &7, щоб відвідати." + conversations: + prefix: "&l&6 [BentoBox]: &r" + no-data: "&c Запустіть рівень, щоб переглянути звіт про блокування." + cancel-string: cancel + exit-string: cancel, exit, quit + write-search: "&e Введіть пошукове значення. (Напишіть 'cancel', щоб вийти)" + search-updated: "&a Значення пошуку оновлено." + cancelled: "&c Розмова скасована!" + no-value: "&c Цей предмет не має цінності." + unknown-item: "&c '[material]' не існує в грі." + value: "&7 Значення '[material]' таке: &e[value]" + value-underwater: "&7 Значення '[material]' нижче рівня моря: &e[value]" + empty-hand: "&c У вашій руці немає блоків" From cec620162b17293a312e66290f34fffdbc8b58bc Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 10 Dec 2023 10:06:33 -0800 Subject: [PATCH 084/106] Move to 1.20.4 Refactored the calculator code for clarity. Added Jacoco line to prvent issues with the bigger Material class. --- pom.xml | 4 +- .../level/calculators/EquationEvaluator.java | 121 ++ .../calculators/IslandLevelCalculator.java | 1023 ++++++++--------- 3 files changed, 603 insertions(+), 545 deletions(-) create mode 100644 src/main/java/world/bentobox/level/calculators/EquationEvaluator.java diff --git a/pom.xml b/pom.xml index 46b6078..3d0fae9 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 2.0.9 - 1.19.4-R0.1-SNAPSHOT + 1.20.4-R0.1-SNAPSHOT 2.0.0-SNAPSHOT 1.12.0 @@ -410,6 +410,8 @@ **/*Names* + + org/bukkit/Material* diff --git a/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java b/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java new file mode 100644 index 0000000..c822410 --- /dev/null +++ b/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java @@ -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(); + } + +} diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 9c2a6cf..7fc1cca 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -1,6 +1,6 @@ package world.bentobox.level.calculators; -import java.io.IOException; +import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -16,9 +16,6 @@ import java.util.concurrent.CompletableFuture; 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.Chunk; import org.bukkit.ChunkSnapshot; @@ -27,7 +24,11 @@ import org.bukkit.Tag; import org.bukkit.World; 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.type.Slab; import org.bukkit.inventory.ItemStack; @@ -39,6 +40,9 @@ 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; @@ -54,121 +58,21 @@ 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); + 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); 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 Queue> chunksToCheck; private final Island island; private final Map limitCount; private final CompletableFuture r; - private final Results results; private long duration; private final boolean zeroIsland; @@ -178,569 +82,600 @@ void nextChar() { private final Set chestBlocks = new HashSet<>(); private BukkitTask finishTask; - /** * Constructor to get the level for an island - * @param addon - Level addon - * @param island - the island to scan - * @param r - completable result that will be completed when the calculation is complete + * + * @param addon - Level addon + * @param island - the island to scan + * @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 */ public IslandLevelCalculator(Level addon, Island island, CompletableFuture r, boolean zeroIsland) { - this.addon = addon; - this.island = island; - this.r = r; - this.zeroIsland = zeroIsland; - results = new Results(); - duration = System.currentTimeMillis(); - chunksToCheck = getChunksToScan(island); - this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits()); - // Get the initial island level - results.initialLevel.set(addon.getInitialIslandLevel(island)); - // Set up the worlds - worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld())); - // Nether - if (addon.getSettings().isNether()) { - World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); - if (nether != null) { - worlds.put(Environment.NETHER, nether); - } - } - // End - if (addon.getSettings().isEnd()) { - World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); - if (end != null) { - worlds.put(Environment.THE_END, end); - } - } - // Sea Height - seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld()); + this.addon = addon; + this.island = island; + this.r = r; + this.zeroIsland = zeroIsland; + results = new Results(); + duration = System.currentTimeMillis(); + chunksToCheck = getChunksToScan(island); + this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits()); + // Get the initial island level + results.initialLevel.set(addon.getInitialIslandLevel(island)); + // Set up the worlds + worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld())); + // Nether + if (addon.getSettings().isNether()) { + World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); + if (nether != null) { + worlds.put(Environment.NETHER, nether); + } + } + // End + if (addon.getSettings().isEnd()) { + World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); + if (end != null) { + worlds.put(Environment.THE_END, end); + } + } + // Sea Height + seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld()); } /** * Calculate the level based on the raw points + * * @param blockAndDeathPoints - raw points counted on island * @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)eval(withValues); - return evalWithValues - (addon.getSettings().isZeroNewIslandLevels() ? results.initialLevel.get() : 0); - - } catch (IOException e) { - addon.getPlugin().logStacktrace(e); - return 0L; - } + 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; + } } /** - * 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 + * 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 belowSeaLevel - true if below sea level */ private void checkBlock(Material mat, boolean belowSeaLevel) { - int count = limitCount(mat); - if (belowSeaLevel) { - results.underWaterBlockCount.addAndGet(count); - results.uwCount.add(mat); - } else { - results.rawBlockCount.addAndGet(count); - results.mdCount.add(mat); - } + int count = limitCount(mat); + if (belowSeaLevel) { + results.underWaterBlockCount.addAndGet(count); + results.uwCount.add(mat); + } else { + results.rawBlockCount.addAndGet(count); + results.mdCount.add(mat); + } } /** * Get a set of all the chunks in island + * * @param island - island * @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; } - /** * @return the island */ public Island getIsland() { - return island; + return island; } /** * Get the completable result for this calculation + * * @return the r */ public CompletableFuture getR() { - return r; + return r; } /** * Get the full analysis report + * * @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)); - - 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; + 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); + + return reportLines; } /** * @return the results */ public Results getResults() { - return results; + return results; } + /** - * Get value of a material - * World blocks trump regular block values + * Get value of a material World blocks trump regular block values + * * @param md - Material to check * @return value of a material */ private int getValue(Material md) { - Integer value = addon.getBlockConfig().getValue(island.getWorld(), md); - if (value == null) { - // Not in config - results.ncCount.add(md); - return 0; - } - return value; + Integer value = addon.getBlockConfig().getValue(island.getWorld(), md); + if (value == null) { + // Not in config + results.ncCount.add(md); + return 0; + } + return value; } /** * Get a chunk async - * @param env - the environment + * + * @param env - the environment * @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> getWorldChunk(Environment env, Queue> pairList) { - if (worlds.containsKey(env)) { - CompletableFuture> r2 = new CompletableFuture<>(); - List chunkList = new ArrayList<>(); - World world = worlds.get(env); - // Get the chunk, and then coincidentally check the RoseStacker - loadChunks(r2, world, pairList, chunkList); - return r2; - } - return CompletableFuture.completedFuture(Collections.emptyList()); + if (worlds.containsKey(env)) { + CompletableFuture> r2 = new CompletableFuture<>(); + List chunkList = new ArrayList<>(); + World world = worlds.get(env); + // Get the chunk, and then coincidentally check the RoseStacker + loadChunks(r2, world, pairList, chunkList); + return r2; + } + return CompletableFuture.completedFuture(Collections.emptyList()); } private void loadChunks(CompletableFuture> r2, World world, Queue> pairList, - List chunkList) { - if (pairList.isEmpty()) { - r2.complete(chunkList); - return; - } - Pair p = pairList.poll(); - Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { - if (chunk != null) { - chunkList.add(chunk); - roseStackerCheck(chunk); - } - loadChunks(r2, world, pairList, chunkList); // Iteration - }); + List chunkList) { + if (pairList.isEmpty()) { + r2.complete(chunkList); + return; + } + Pair p = pairList.poll(); + Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { + if (chunk != null) { + chunkList.add(chunk); + roseStackerCheck(chunk); + } + loadChunks(r2, world, pairList, chunkList); // Iteration + }); } private void roseStackerCheck(Chunk chunk) { - if (addon.isRoseStackersEnabled()) { - RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { - // Blocks below sea level can be scored differently - boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight; - // Check block once because the base block will be counted in the chunk snapshot - for (int _x = 0; _x < e.getStackSize() - 1; _x++) { - checkBlock(e.getBlock().getType(), belowSeaLevel); - } - }); - } + if (addon.isRoseStackersEnabled()) { + RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { + // Blocks below sea level can be scored differently + boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight; + // Check block once because the base block will be counted in the chunk snapshot + for (int _x = 0; _x < e.getStackSize() - 1; _x++) { + checkBlock(e.getBlock().getType(), belowSeaLevel); + } + }); + } } /** - * 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 * @return value of the block if can be counted */ private int limitCount(Material md) { - if (limitCount.containsKey(md)) { - int count = limitCount.get(md); - if (count > 0) { - limitCount.put(md, --count); - return getValue(md); - } else { - results.ofCount.add(md); - return 0; - } - } - return getValue(md); + if (limitCount.containsKey(md)) { + int count = limitCount.get(md); + if (count > 0) { + limitCount.put(md, --count); + return getValue(md); + } else { + results.ofCount.add(md); + return 0; + } + } + return getValue(md); } - /** * Scan all containers in a chunk and count their blocks + * * @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; - - 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); - } + 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); + } + + checkBlock(i.getType(), false); + } } /** - * Scan the chunk chests and count the blocks. Note that the chunks are a list of all the island chunks - * in a particular world, so the memory usage is high, but I think most servers can handle it. + * Scan the chunk chests and count the blocks. Note that the chunks are a list + * 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 - * @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 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) {} + record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) { + } /** * Count the blocks on the island + * * @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)); - } - - Block block = cp.chunk.getBlock(x, y, z); - - 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); - } - } - } + 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)); + } + + Block block = cp.chunk.getBlock(x, y, z); + + 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 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 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())))) - ) - ) - ); - - return result; + 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; } 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(); - - 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("Subtotal = " + total); - result.add(LINE_BREAK); - return result; + Collection result = new ArrayList<>(); + Iterable> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount) + .entrySet(); + for (Entry en : entriesSortedByCount) { + Material type = en.getElement(); + + 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("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())); - - // 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); - - 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); - - // Report - results.report = getReport(); - // Set the duration - addon.getPipeliner().setTime(System.currentTimeMillis() - duration); - // All done. + // 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())); + } + + 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)); + + // 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. } /** * @return the zeroIsland */ boolean isNotZeroIsland() { - return !zeroIsland; + return !zeroIsland; } 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() { - // Deal with any stacked blocks - Iterator it = stackedBlocks.iterator(); - while (it.hasNext()) { - Location v = it.next(); - Util.getChunkAtAsync(v).thenAccept(c -> { - Block stackedBlock = v.getBlock(); - boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; - if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { - StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); - int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); - for (int _x = 0; _x < barrelAmt; _x++) { - checkBlock(barrel.getType(), belowSeaLevel); - } - } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { - int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); - for (int _x = 0; _x < spawnerAmt; _x++) { - checkBlock(stackedBlock.getType(), belowSeaLevel); - } - } - it.remove(); - }); - } + // Deal with any stacked blocks + Iterator it = stackedBlocks.iterator(); + while (it.hasNext()) { + Location v = it.next(); + Util.getChunkAtAsync(v).thenAccept(c -> { + Block stackedBlock = v.getBlock(); + boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; + if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { + StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); + int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); + for (int _x = 0; _x < barrelAmt; _x++) { + checkBlock(barrel.getType(), belowSeaLevel); + } + } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { + int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); + for (int _x = 0; _x < spawnerAmt; _x++) { + checkBlock(stackedBlock.getType(), belowSeaLevel); + } + } + it.remove(); + }); + } } } From 82174649b4535d8b85a0ae3cea043a3a3e2263b1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 18 Dec 2023 17:57:36 -0800 Subject: [PATCH 085/106] Added comments on the panel templates. --- src/main/resources/panels/detail_panel.yml | 46 ++++++++++++++++++++-- src/main/resources/panels/top_panel.yml | 13 ++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/main/resources/panels/detail_panel.yml b/src/main/resources/panels/detail_panel.yml index 927a788..da08f6c 100644 --- a/src/main/resources/panels/detail_panel.yml +++ b/src/main/resources/panels/detail_panel.yml @@ -1,25 +1,49 @@ +# Name of panel used for indentification in the code detail_panel: + # Title of the panel shown to the user. This is a reference and the reference will be translatable in the locale file title: level.gui.titles.detail-panel + # The type of panel to show. Options are INVENTORY, HOPPER, DROPPER. INVENTORY is that standard chest inventory and + # the others refer to the inventories shown for those items. type: INVENTORY + # The background of the panel. These items will be shown if other items are not there. STAINED_GLASS_PANEs give a good effect. background: icon: BLACK_STAINED_GLASS_PANE - title: "&b&r" # Empty text + # Each item may have text applied to it, but usually for background items, nothing is shown. + title: "&b&r" # Empty text. This is using the Bukkit chat color coding with &'s. border: + # The border of each panel may be shown as a different item. + # It can be used to provide a contrast to items in the panel. icon: BLACK_STAINED_GLASS_PANE title: "&b&r" # Empty text + # This tag indicates which rows in the panel must be shown. The panel will be sized vertically accordingly. This does not include the borders. + # This can be a list and rows must be between 1 and 6, if used. force-shown: [] + # The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item. content: + # Row number 1: + # Column number 2: + # Icon is a Bukkit Material. icon: STONE + # Title of the button shown to the user. This is a reference and the reference will be translatable in the locale file title: level.gui.buttons.all_blocks.name + # Description of the button shown to the user in the lore. This is a reference and the reference will be translatable in the locale file description: level.gui.buttons.all_blocks.description + # The data section is a key-value list of data relavent for this button. It is interpreted by the code implemented the panel. + # The convention is to specify the type and the panel tab that will open if pressed. These are Enums in the code. data: + # Type button will go to the ALL_BLOCKS tab when clicked. type: TAB tab: ALL_BLOCKS + # Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different + # click-types. actions: + # Each action has an arbitrary descriptive name to define it. view: + # The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default. click-type: unknown + # tooltip is a locale reference that will be translated for the user and shown when they hover over the button. tooltip: level.gui.tips.click-to-view 3: icon: GRASS_BLOCK @@ -57,12 +81,12 @@ detail_panel: 9: # You can create multiple buttons. By default it is one. icon: IRON_TRAPDOOR - # [filter] is placeholder for different filter types. It will be replaced with name, value, count. + # [filter] is a placeholder for different filter types. It will be replaced with name, value, count. title: level.gui.buttons.filters.[filter].name description: level.gui.buttons.filters.[filter].description data: type: FILTER - # the value of filter button. Suggestion is to leave fist value to name if you use single button. + # the value of filter button. Suggestion is to leave first value to name if you use single button. filter: NAME actions: up: @@ -76,6 +100,7 @@ detail_panel: # click-type: unknown # tooltip: level.gui.tips.click-to-select 2: + # If a button is used repeatedly then it can be mentioned by name and then defined in the 'reusable' section 2: material_button 3: material_button 4: material_button @@ -85,6 +110,17 @@ detail_panel: 8: material_button 3: 1: + # In this case, the icon is defined as a TIPPED_ARROW with and enchantment applied. The format for the enchantment is + # define in {@link world.bentobox.bentobox.util.ItemParser} and available for POTIONS or TIPPED_ARROWs. + # Format TIPPED_ARROW:NAME::::QTY + # LEVEL, EXTENDED, SPLASH, LINGER are optional. + # LEVEL is a number, 1 or 2 + # LINGER is for V1.9 servers and later + # Examples: + # TIPPED_ARROW:STRENGTH:1:EXTENDED:SPLASH:1 + # 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 title: level.gui.buttons.previous.name description: level.gui.buttons.previous.description @@ -121,8 +157,12 @@ detail_panel: 6: material_button 7: material_button 8: material_button + # This is where reuable buttons are defined. reusable: + # This is the name of the button that is referenced material_button: + # If the icon for a button is not defined, it defaults to AIR and so effectively will not be shown. + # icons are usually not defined if the icon is going to be dynamically set in the panel, e.g. in this case the material will vary #icon: STONE title: level.gui.buttons.material.name description: level.gui.buttons.material.description diff --git a/src/main/resources/panels/top_panel.yml b/src/main/resources/panels/top_panel.yml index 3b80784..05d6151 100644 --- a/src/main/resources/panels/top_panel.yml +++ b/src/main/resources/panels/top_panel.yml @@ -1,15 +1,28 @@ +# Name of panel used for indentification in the code top_panel: + # Title of the panel shown to the user. This is a reference and the reference will be translatable in the locale file title: level.gui.titles.top + # The type of panel to show. Options are INVENTORY, HOPPER, DROPPER. INVENTORY is that standard chest inventory and + # the others refer to the inventories shown for those items. type: INVENTORY + # The background of the panel. These items will be shown if other items are not there. STAINED_GLASS_PANEs give a good effect. background: icon: BLACK_STAINED_GLASS_PANE + # Each item may have text applied to it, but usually for background items, nothing is shown. title: "&b&r" # Empty text border: + # The border of each panel may be shown as a different item. + # It can be used to provide a contrast to items in the panel. icon: BLACK_STAINED_GLASS_PANE title: "&b&r" # Empty text + # This tag indicates which rows in the panel must be shown. The panel will be sized vertically accordingly. This does not include the borders. + # This can be a list and rows must be between 1 and 6, if used. force-shown: [2,3,4,5] + # The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item. content: + # Row number 2: + # Column number 5: #icon: PLAYER_HEAD title: level.gui.buttons.island.name From 5f83a81f1805f1d4bcef5aab795934ae8448b9c0 Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 2 Jan 2024 11:24:07 +0900 Subject: [PATCH 086/106] Add protection around unknown blockconfig.yml entries. GRASS>SHORT_GRASS --- .../world/bentobox/level/config/BlockConfig.java | 16 +++++++++++----- src/main/resources/blockconfig.yml | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/world/bentobox/level/config/BlockConfig.java b/src/main/java/world/bentobox/level/config/BlockConfig.java index 2180048..efcb3ee 100644 --- a/src/main/java/world/bentobox/level/config/BlockConfig.java +++ b/src/main/java/world/bentobox/level/config/BlockConfig.java @@ -60,10 +60,16 @@ private void loadWorlds(YamlConfiguration blockValues2) { if (bWorld != null) { ConfigurationSection worldValues = worlds.getConfigurationSection(world); for (String material : Objects.requireNonNull(worldValues).getKeys(false)) { - Material mat = Material.valueOf(material); - Map values = worldBlockValues.getOrDefault(bWorld, new EnumMap<>(Material.class)); - values.put(mat, worldValues.getInt(material)); - worldBlockValues.put(bWorld, values); + try { + Material mat = Material.valueOf(material); + Map values = worldBlockValues.getOrDefault(bWorld, + new EnumMap<>(Material.class)); + values.put(mat, worldValues.getInt(material)); + worldBlockValues.put(bWorld, values); + } catch (Exception e) { + addon.logError( + "Unknown material (" + material + ") in blockconfig.yml worlds section. Skipping..."); + } } } else { addon.logWarning("Level Addon: No such world in blockconfig.yml : " + world); @@ -97,7 +103,7 @@ private Map loadBlockLimits(YamlConfiguration blockValues2) { Material mat = Material.valueOf(material); bl.put(mat, limits.getInt(material, 0)); } catch (Exception e) { - addon.logWarning("Unknown material (" + material + ") in blockconfig.yml Limits section. Skipping..."); + addon.logError("Unknown material (" + material + ") in blockconfig.yml Limits section. Skipping..."); } } return bl; diff --git a/src/main/resources/blockconfig.yml b/src/main/resources/blockconfig.yml index d1580ac..3af622c 100644 --- a/src/main/resources/blockconfig.yml +++ b/src/main/resources/blockconfig.yml @@ -287,7 +287,7 @@ blocks: GRANITE_SLAB: 1 GRANITE_STAIRS: 1 GRANITE_WALL: 1 - GRASS: 4 + SHORT_GRASS: 4 GRASS_BLOCK: 4 GRAVEL: 1 GRAY_BANNER: 2 From 2b373f62d92065f5f888a97a614c5c17db0afadb Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 2 Jan 2024 14:19:16 +0900 Subject: [PATCH 087/106] Uses latest Visit API to avoid chat spam. Fixes #299 (#300) --- pom.xml | 2 +- src/main/java/world/bentobox/level/panels/TopLevelPanel.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3d0fae9..881d55e 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 1.12.0 - 1.4.0 + 1.6.0 1.1.0 diff --git a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java index 0254204..81910b8 100644 --- a/src/main/java/world/bentobox/level/panels/TopLevelPanel.java +++ b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java @@ -177,7 +177,7 @@ private PanelItem createIslandIcon(ItemTemplateRecord template, IslandTopRecord } case "VISIT" -> { return island.getOwner() == null || this.addon.getVisitHook() == null - || !this.addon.getVisitHook().getAddonManager().preprocessTeleportation(this.user, island); + || !this.addon.getVisitHook().getAddonManager().preprocessTeleportation(this.user, island, true); } case "VIEW" -> { return island.getOwner() == null From 76e0bad88a56f0b9d7ef2da8280b2f410e1ee729 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 13 Jan 2024 08:50:37 -0800 Subject: [PATCH 088/106] Added test class for EquationEvaluator --- .../level/calculators/EquationEvaluator.java | 210 +++++++++--------- .../calculators/EquationEvaluatorTest.java | 37 +++ 2 files changed, 142 insertions(+), 105 deletions(-) create mode 100644 src/test/java/world/bentobox/level/calculators/EquationEvaluatorTest.java diff --git a/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java b/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java index c822410..3014542 100644 --- a/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java +++ b/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java @@ -3,119 +3,119 @@ import java.text.ParseException; /** - * @author tastybento + * Utility class to evaluate equations */ 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; - } - } + private final String input; + private int pos = -1; + private int currentChar; + + @SuppressWarnings("unused") + private Parser() { + throw new IllegalStateException("Utility class"); + } + + 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(); + x = switch (func) { + case "sqrt" -> Math.sqrt(x); + case "sin" -> Math.sin(Math.toRadians(x)); + case "cos" -> Math.cos(Math.toRadians(x)); + case "tan" -> Math.tan(Math.toRadians(x)); + case "log" -> Math.log(x); + 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(); + return new Parser(equation).evaluate(); } } diff --git a/src/test/java/world/bentobox/level/calculators/EquationEvaluatorTest.java b/src/test/java/world/bentobox/level/calculators/EquationEvaluatorTest.java new file mode 100644 index 0000000..9bcb5e2 --- /dev/null +++ b/src/test/java/world/bentobox/level/calculators/EquationEvaluatorTest.java @@ -0,0 +1,37 @@ +package world.bentobox.level.calculators; + +import static org.junit.Assert.assertEquals; + +import java.text.ParseException; + +import org.junit.Test; + +/** + * Test the equation evaluation + */ +public class EquationEvaluatorTest { + + /** + * Test method for {@link world.bentobox.level.calculators.EquationEvaluator#eval(java.lang.String)}. + * @throws ParseException + */ + @Test + public void testEval() throws ParseException { + assertEquals(4D, EquationEvaluator.eval("2+2"), 0D); + assertEquals(0D, EquationEvaluator.eval("2-2"), 0D); + assertEquals(1D, EquationEvaluator.eval("2/2"), 0D); + assertEquals(4D, EquationEvaluator.eval("2*2"), 0D); + assertEquals(8D, EquationEvaluator.eval("2+2+2+2"), 0D); + assertEquals(5D, EquationEvaluator.eval("2.5+2.5"), 0D); + assertEquals(1.414, EquationEvaluator.eval("sqrt(2)"), 0.001D); + assertEquals(3.414, EquationEvaluator.eval("2 + sqrt(2)"), 0.001D); + assertEquals(0D, EquationEvaluator.eval("sin(0)"), 0.1D); + assertEquals(1D, EquationEvaluator.eval("cos(0)"), 0.1D); + assertEquals(0D, EquationEvaluator.eval("tan(0)"), 0.1D); + assertEquals(0D, EquationEvaluator.eval("log(1)"), 0.1D); + assertEquals(27D, EquationEvaluator.eval("3^3"), 0.D); + assertEquals(84.70332D, EquationEvaluator.eval("3^3 + 2 + 2.65 * (3 / 4) - sin(45) * log(10) + 55.344"), + 0.0001D); + + } +} From 79f988f4605910434e93810c0786f03d0a053dd0 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 13 Jan 2024 19:48:14 -0800 Subject: [PATCH 089/106] Fix merge error --- src/main/java/world/bentobox/level/Level.java | 392 +----------------- 1 file changed, 1 insertion(+), 391 deletions(-) diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 14fea5f..e13a70d 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -447,395 +447,5 @@ public VisitAddon getVisitHook() { public Warp getWarpHook() { return this.warpHook; } -======= - // The 10 in top ten - public static final int TEN = 10; - - // Settings - private ConfigSettings settings; - private Config configObject = new Config<>(this, ConfigSettings.class); - private BlockConfig blockConfig; - private Pipeliner pipeliner; - private LevelsManager manager; - private boolean stackersEnabled; - private boolean advChestEnabled; - private boolean roseStackersEnabled; - private boolean ultimateStackerEnabled; - private final List registeredGameModes = new ArrayList<>(); - - /** - * Local variable that stores if warpHook is present. - */ - private Warp warpHook; - - /** - * Local variable that stores if visitHook is present. - */ - private VisitAddon visitHook; - - - @Override - public void onLoad() { - // Save the default config from config.yml - saveDefaultConfig(); - if (loadSettings()) { - // Disable - logError("Level settings could not load! Addon disabled."); - setState(State.DISABLED); - } else { - configObject.saveConfigObject(settings); - } - - // Save existing panels. - this.saveResource("panels/top_panel.yml", false); - this.saveResource("panels/detail_panel.yml", false); - this.saveResource("panels/value_panel.yml", false); - } - - private boolean loadSettings() { - // Load settings again to get worlds - settings = configObject.loadConfigObject(); - - return settings == null; - } - - @Override - public void onEnable() { - loadBlockSettings(); - // Start pipeline - pipeliner = new Pipeliner(this); - // Start Manager - manager = new LevelsManager(this); - // Register listeners - this.registerListener(new IslandActivitiesListeners(this)); - this.registerListener(new JoinLeaveListener(this)); - this.registerListener(new MigrationListener(this)); - - // Register commands for GameModes - registeredGameModes.clear(); - getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) - .forEach(gm -> { - log("Level hooking into " + gm.getDescription().getName()); - registerCommands(gm); - new PlaceholderManager(this).registerPlaceholders(gm); - registeredGameModes.add(gm); - }); - // Register request handlers - registerRequestHandler(new LevelRequestHandler(this)); - registerRequestHandler(new TopTenRequestHandler(this)); - - // Check if WildStackers is enabled on the server - // I only added support for counting blocks into the island level - // Someone else can PR if they want spawners added to the Leveling system :) - stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker"); - if (stackersEnabled) { - log("Hooked into WildStackers."); - } - // Check if AdvancedChests is enabled on the server - Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests"); - advChestEnabled = advChest != null; - if (advChestEnabled) { - // Check version - if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) { - log("Hooked into AdvancedChests."); - } else { - logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + " - requires version 23.0 or later"); - advChestEnabled = false; - } - } - // Check if RoseStackers is enabled - roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker"); - if (roseStackersEnabled) { - log("Hooked into RoseStackers."); - } - - // Check if UltimateStacker is enabled - ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker"); - if (ultimateStackerEnabled) { - log("Hooked into UltimateStacker."); - } - } - - @Override - public void allLoaded() - { - super.allLoaded(); - - if (this.isEnabled()) - { - this.hookExtensions(); - } - } - - - /** - * This method tries to hook into addons and plugins - */ - private void hookExtensions() - { - // Try to find Visit addon and if it does not exist, display a warning - this.getAddonByName("Visit").ifPresentOrElse(addon -> - { - this.visitHook = (VisitAddon) addon; - this.log("Level Addon hooked into Visit addon."); - }, () -> this.visitHook = null); - - // Try to find Warps addon and if it does not exist, display a warning - this.getAddonByName("Warps").ifPresentOrElse(addon -> - { - this.warpHook = (Warp) addon; - this.log("Level Addon hooked into Warps addon."); - }, () -> this.warpHook = null); - } - - - /** - * Compares versions - * @param version1 version 1 - * @param version2 version 2 - * @return {@code <0 if version 1 is older than version 2, =0 if the same, >0 if version 1 is newer than version 2} - */ - public static int compareVersions(String version1, String version2) { - int comparisonResult = 0; - - String[] version1Splits = version1.split("\\."); - String[] version2Splits = version2.split("\\."); - int maxLengthOfVersionSplits = Math.max(version1Splits.length, version2Splits.length); - - for (int i = 0; i < maxLengthOfVersionSplits; i++){ - Integer v1 = i < version1Splits.length ? Integer.parseInt(version1Splits[i]) : 0; - Integer v2 = i < version2Splits.length ? Integer.parseInt(version2Splits[i]) : 0; - int compare = v1.compareTo(v2); - if (compare != 0) { - comparisonResult = compare; - break; - } - } - return comparisonResult; - } - - private void registerCommands(GameModeAddon gm) { - gm.getAdminCommand().ifPresent(adminCommand -> { - new AdminLevelCommand(this, adminCommand); - new AdminTopCommand(this, adminCommand); - new AdminLevelStatusCommand(this, adminCommand); - if (getSettings().isZeroNewIslandLevels()) { - new AdminSetInitialLevelCommand(this, adminCommand); - } - }); - gm.getPlayerCommand().ifPresent(playerCmd -> { - new IslandLevelCommand(this, playerCmd); - new IslandTopCommand(this, playerCmd); - new IslandValueCommand(this, playerCmd); - }); - } - - @Override - public void onDisable() { - // Stop the pipeline - this.getPipeliner().stop(); - } - - private void loadBlockSettings() { - // Save the default blockconfig.yml - this.saveResource("blockconfig.yml", false); - - YamlConfiguration blockValues = new YamlConfiguration(); - try { - File file = new File(this.getDataFolder(), "blockconfig.yml"); - blockValues.load(file); - // Load the block config class - blockConfig = new BlockConfig(this, blockValues, file); - } catch (IOException | InvalidConfigurationException e) { - // Disable - logError("Level blockconfig.yml settings could not load! Addon disabled."); - setState(State.DISABLED); - } - - } - - - /** - * @return the blockConfig - */ - public BlockConfig getBlockConfig() { - return blockConfig; - } - - /** - * @return the settings - */ - public ConfigSettings getSettings() { - return settings; - } - - /** - * @return the pipeliner - */ - public Pipeliner getPipeliner() { - return pipeliner; - } - - /** - * @return the manager - */ - public LevelsManager getManager() { - return manager; - } - - /** - * Set the config settings - used for tests only - * @param configSettings - config settings - */ - void setSettings(ConfigSettings configSettings) { - this.settings = configSettings; - - } - - /** - * @return the stackersEnabled - */ - public boolean isStackersEnabled() { - return stackersEnabled; - } - - /** - * @return the advChestEnabled - */ - public boolean isAdvChestEnabled() { - return advChestEnabled; - } - - /** - * Get level from cache for a player. - * @param targetPlayer - target player UUID - * @return Level of player or zero if player is unknown or UUID is null - */ - public long getIslandLevel(World world, @Nullable UUID targetPlayer) { - return getManager().getIslandLevel(world, targetPlayer); - } - - /** - * Sets the player's level to a value - * @param world - world - * @param targetPlayer - target player - * @param level - level - */ - public void setIslandLevel(World world, UUID targetPlayer, long level) { - getManager().setIslandLevel(world, targetPlayer, level); - } - - /** - * Zeros the initial island level - * @param island - island - * @param level - initial calculated island level - */ - public void setInitialIslandLevel(@NonNull Island island, long level) { - getManager().setInitialIslandLevel(island, level); - } - - /** - * Get the initial island level - * @param island - island - * @return level or 0 by default - */ - public long getInitialIslandLevel(@NonNull Island island) { - return getManager().getInitialLevel(island); - } - - /** - * Calculates a user's island - * @param world - the world where this island is - * @param user - not used! See depecration message - * @param playerUUID - the target island member's UUID - * @deprecated Do not use this anymore. Use getManager().calculateLevel(playerUUID, island) - */ - @Deprecated(since="2.3.0", forRemoval=true) - public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) { - Island island = getIslands().getIsland(world, playerUUID); - if (island != null) getManager().calculateLevel(playerUUID, island); - } - - /** - * Provide the levels data for the target player - * @param targetPlayer - UUID of target player - * @return LevelsData object or null if not found. Only island levels are set! - * @deprecated Do not use this anymore. Use {@link #getIslandLevel(World, UUID)} - */ - @Deprecated(since="2.3.0", forRemoval=true) - public LevelsData getLevelsData(UUID targetPlayer) { - LevelsData ld = new LevelsData(targetPlayer); - getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) - .forEach(gm -> { - if (getSettings().isZeroNewIslandLevels()) { - Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer); - if (island != null) { - ld.setInitialLevel(gm.getOverWorld(), this.getInitialIslandLevel(island)); - } - } - ld.setLevel(gm.getOverWorld(), this.getIslandLevel(gm.getOverWorld(), targetPlayer)); - }); - return ld; - } - - /** - * @return the registeredGameModes - */ - public List getRegisteredGameModes() { - return registeredGameModes; - } - - /** - * Check if Level addon is active in game mode - * @param gm Game Mode Addon - * @return true if active, false if not - */ - public boolean isRegisteredGameMode(GameModeAddon gm) { - return registeredGameModes.contains(gm); - } - - /** - * Checks if Level addon is active in world - * @param world world - * @return true if active, false if not - */ - public boolean isRegisteredGameModeWorld(World world) { - return registeredGameModes.stream().map(GameModeAddon::getOverWorld).anyMatch(w -> Util.sameWorld(world, w)); - } - - /** - * @return the roseStackersEnabled - */ - public boolean isRoseStackersEnabled() { - return roseStackersEnabled; - } - - /** - * @return the ultimateStackerEnabled - */ - public boolean isUltimateStackerEnabled() { - return ultimateStackerEnabled; - } - - /** - * Method Level#getVisitHook returns the visitHook of this object. - * - * @return {@code Visit} of this object, {@code null} otherwise. - */ - public VisitAddon getVisitHook() - { - return this.visitHook; - } - - /** - * Method Level#getWarpHook returns the warpHook of this object. - * - * @return {@code Warp} of this object, {@code null} otherwise. - */ - public Warp getWarpHook() - { - return this.warpHook; - } + } From 952a2a6e81d3b6626f68d08a97b28575a382671a Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 21 Jan 2024 09:08:30 -0800 Subject: [PATCH 090/106] Version 2.12.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 881d55e..7b1c361 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ -LOCAL - 2.12.0 + 2.12.1 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From 02a19d1bdb200ab585f51941e96149d9010bb87b Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 21 Jan 2024 09:08:35 -0800 Subject: [PATCH 091/106] Update tipped arrows in GUI Panel --- src/main/resources/panels/detail_panel.yml | 4 ++-- src/main/resources/panels/value_panel.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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: From 774bbd034c48c168aaa1726d2475d68c5b617899 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 3 Feb 2024 08:28:44 -0800 Subject: [PATCH 092/106] Version 2.13.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7b1c361..4a4d790 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ -LOCAL - 2.12.1 + 2.13.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From 2d1f61867635851422d841477ca64c2ecc2d50ad Mon Sep 17 00:00:00 2001 From: add5tar Date: Wed, 17 Apr 2024 10:55:17 +1000 Subject: [PATCH 093/106] Add more string replacements for /is level output (#303) * Add more string replacements to output * Forgot to include the locale change --- .../world/bentobox/level/commands/IslandLevelCommand.java | 6 +++++- src/main/resources/locales/en-US.yml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) 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 @@ private void showResult(User user, UUID playerUUID, Island island, long oldLevel } // 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/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index f71b52d..0e6df70 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -42,7 +42,7 @@ 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" + required-points-to-next-level: "&aLevel 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..." From 1bd6219f9436d57fbc47d7cea6c7d2a951751989 Mon Sep 17 00:00:00 2001 From: Huynh Tien Date: Fri, 19 Apr 2024 21:31:23 +0700 Subject: [PATCH 094/106] Update hooks and fix UltimateStacker API (#305) --- pom.xml | 23 +++++++++++-------- .../calculators/IslandLevelCalculator.java | 8 +++---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index 4a4d790..4adb4dd 100644 --- a/pom.xml +++ b/pom.xml @@ -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/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 7fc1cca..935313e 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -40,9 +40,8 @@ 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 com.craftaro.ultimatestacker.api.UltimateStackerApi; +import com.craftaro.ultimatestacker.api.utils.Stackable; import dev.rosewood.rosestacker.api.RoseStackerAPI; import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI; @@ -469,8 +468,7 @@ private void scanAsync(ChunkPair cp) { if (addon.isUltimateStackerEnabled()) { if (!blockData.getMaterial().equals(Material.AIR)) { - BlockStack stack = UltimateStacker.getInstance().getBlockStackManager().getBlock(block, - CompatibleMaterial.getMaterial(block)); + Stackable stack = UltimateStackerApi.getBlockStackManager().getBlock(block.getLocation()); if (stack != null) { int value = limitCount(blockData.getMaterial()); if (belowSeaLevel) { From a4f8d121389ac833839c13a9b92d6c7d9509ddde Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 20 Apr 2024 09:50:17 -0700 Subject: [PATCH 095/106] Isolate UltimateStacker imports so no errors if US is not installed (#307) Fixes #306 --- .../calculators/IslandLevelCalculator.java | 853 +++++++++--------- .../calculators/UltimateStackerCalc.java | 29 + 2 files changed, 448 insertions(+), 434 deletions(-) create mode 100644 src/main/java/world/bentobox/level/calculators/UltimateStackerCalc.java diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 935313e..18ced59 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -40,8 +40,6 @@ import com.google.common.collect.Multiset; import com.google.common.collect.Multiset.Entry; 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 us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI; @@ -58,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; @@ -91,34 +89,34 @@ public class IslandLevelCalculator { * @param zeroIsland - true if the calculation is due to an island zeroing */ public IslandLevelCalculator(Level addon, Island island, CompletableFuture r, boolean zeroIsland) { - this.addon = addon; - this.island = island; - this.r = r; - this.zeroIsland = zeroIsland; - results = new Results(); - duration = System.currentTimeMillis(); - chunksToCheck = getChunksToScan(island); - this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits()); - // Get the initial island level - results.initialLevel.set(addon.getInitialIslandLevel(island)); - // Set up the worlds - worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld())); - // Nether - if (addon.getSettings().isNether()) { - World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); - if (nether != null) { - worlds.put(Environment.NETHER, nether); - } - } - // End - if (addon.getSettings().isEnd()) { - World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); - if (end != null) { - worlds.put(Environment.THE_END, end); - } - } - // Sea Height - seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld()); + this.addon = addon; + this.island = island; + this.r = r; + this.zeroIsland = zeroIsland; + results = new Results(); + duration = System.currentTimeMillis(); + chunksToCheck = getChunksToScan(island); + this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits()); + // Get the initial island level + results.initialLevel.set(addon.getInitialIslandLevel(island)); + // Set up the worlds + worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld())); + // Nether + if (addon.getSettings().isNether()) { + World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); + if (nether != null) { + worlds.put(Environment.NETHER, nether); + } + } + // End + if (addon.getSettings().isEnd()) { + World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); + if (end != null) { + worlds.put(Environment.THE_END, end); + } + } + // Sea Height + seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld()); } /** @@ -128,18 +126,18 @@ public IslandLevelCalculator(Level addon, Island island, CompletableFuture> 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; } /** * @return the island */ public Island getIsland() { - return island; + return island; } /** @@ -191,7 +189,7 @@ public Island getIsland() { * @return the r */ public CompletableFuture getR() { - return r; + return r; } /** @@ -200,67 +198,67 @@ public CompletableFuture getR() { * @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)); - - 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; + 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); + + return reportLines; } /** * @return the results */ public Results getResults() { - return results; + return results; } /** @@ -270,13 +268,13 @@ public Results getResults() { * @return value of a material */ private int getValue(Material md) { - Integer value = addon.getBlockConfig().getValue(island.getWorld(), md); - if (value == null) { - // Not in config - results.ncCount.add(md); - return 0; - } - return value; + Integer value = addon.getBlockConfig().getValue(island.getWorld(), md); + if (value == null) { + // Not in config + results.ncCount.add(md); + return 0; + } + return value; } /** @@ -288,44 +286,44 @@ private int getValue(Material md) { * there is no island nether */ private CompletableFuture> getWorldChunk(Environment env, Queue> pairList) { - if (worlds.containsKey(env)) { - CompletableFuture> r2 = new CompletableFuture<>(); - List chunkList = new ArrayList<>(); - World world = worlds.get(env); - // Get the chunk, and then coincidentally check the RoseStacker - loadChunks(r2, world, pairList, chunkList); - return r2; - } - return CompletableFuture.completedFuture(Collections.emptyList()); + if (worlds.containsKey(env)) { + CompletableFuture> r2 = new CompletableFuture<>(); + List chunkList = new ArrayList<>(); + World world = worlds.get(env); + // Get the chunk, and then coincidentally check the RoseStacker + loadChunks(r2, world, pairList, chunkList); + return r2; + } + return CompletableFuture.completedFuture(Collections.emptyList()); } private void loadChunks(CompletableFuture> r2, World world, Queue> pairList, - List chunkList) { - if (pairList.isEmpty()) { - r2.complete(chunkList); - return; - } - Pair p = pairList.poll(); - Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { - if (chunk != null) { - chunkList.add(chunk); - roseStackerCheck(chunk); - } - loadChunks(r2, world, pairList, chunkList); // Iteration - }); + List chunkList) { + if (pairList.isEmpty()) { + r2.complete(chunkList); + return; + } + Pair p = pairList.poll(); + Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { + if (chunk != null) { + chunkList.add(chunk); + roseStackerCheck(chunk); + } + loadChunks(r2, world, pairList, chunkList); // Iteration + }); } private void roseStackerCheck(Chunk chunk) { - if (addon.isRoseStackersEnabled()) { - RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { - // Blocks below sea level can be scored differently - boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight; - // Check block once because the base block will be counted in the chunk snapshot - for (int _x = 0; _x < e.getStackSize() - 1; _x++) { - checkBlock(e.getBlock().getType(), belowSeaLevel); - } - }); - } + if (addon.isRoseStackersEnabled()) { + RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { + // Blocks below sea level can be scored differently + boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight; + // Check block once because the base block will be counted in the chunk snapshot + for (int _x = 0; _x < e.getStackSize() - 1; _x++) { + checkBlock(e.getBlock().getType(), belowSeaLevel); + } + }); + } } /** @@ -336,17 +334,17 @@ private void roseStackerCheck(Chunk chunk) { * @return value of the block if can be counted */ private int limitCount(Material md) { - if (limitCount.containsKey(md)) { - int count = limitCount.get(md); - if (count > 0) { - limitCount.put(md, --count); - return getValue(md); - } else { - results.ofCount.add(md); - return 0; - } - } - return getValue(md); + if (limitCount.containsKey(md)) { + int count = limitCount.get(md); + if (count > 0) { + limitCount.put(md, --count); + return getValue(md); + } else { + results.ofCount.add(md); + return 0; + } + } + return getValue(md); } /** @@ -355,39 +353,39 @@ private int limitCount(Material md) { * @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; - - 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); - } + 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); + } + + checkBlock(i.getType(), false); + } } /** @@ -400,26 +398,26 @@ private void countItemStack(ItemStack i) { * 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) { @@ -431,66 +429,53 @@ record ChunkPair(World world, Chunk chunk, ChunkSnapshot chunkSnapshot) { * @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)); - } - - Block block = cp.chunk.getBlock(x, y, z); - - if (addon.isUltimateStackerEnabled()) { - if (!blockData.getMaterial().equals(Material.AIR)) { - Stackable stack = UltimateStackerApi.getBlockStackManager().getBlock(block.getLocation()); - 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); - } - } - } + 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)); + } + + if (addon.isUltimateStackerEnabled() && !m.isAir()) { + Location l = new Location(cp.chunk.getWorld(), x, y, z); + UltimateStackerCalc.addStackers(m, l, results, belowSeaLevel, limitCount(m)); + } + + // Scan chests + if (addon.getSettings().isIncludeChests() && CHESTS.contains(m)) { + chestBlocks.add(cp.chunk); + } + // Add the value of the block's material + checkBlock(m, belowSeaLevel); + } + } + } } /** @@ -500,180 +485,180 @@ private void scanAsync(ChunkPair cp) { * 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()))))))); - - return result; + 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; } 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(); - - 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("Subtotal = " + total); - result.add(LINE_BREAK); - return result; + Collection result = new ArrayList<>(); + Iterable> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount) + .entrySet(); + for (Entry en : entriesSortedByCount) { + Material type = en.getElement(); + + 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("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())); - - // 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); - - 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); - - // Report - results.report = getReport(); - // Set the duration - addon.getPipeliner().setTime(System.currentTimeMillis() - duration); - // All done. + // 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())); + } + + 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)); + + // 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. } /** * @return the zeroIsland */ boolean isNotZeroIsland() { - return !zeroIsland; + return !zeroIsland; } 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() { - // Deal with any stacked blocks - Iterator it = stackedBlocks.iterator(); - while (it.hasNext()) { - Location v = it.next(); - Util.getChunkAtAsync(v).thenAccept(c -> { - Block stackedBlock = v.getBlock(); - boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; - if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { - StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); - int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); - for (int _x = 0; _x < barrelAmt; _x++) { - checkBlock(barrel.getType(), belowSeaLevel); - } - } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { - int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); - for (int _x = 0; _x < spawnerAmt; _x++) { - checkBlock(stackedBlock.getType(), belowSeaLevel); - } - } - it.remove(); - }); - } + // Deal with any stacked blocks + Iterator it = stackedBlocks.iterator(); + while (it.hasNext()) { + Location v = it.next(); + Util.getChunkAtAsync(v).thenAccept(c -> { + Block stackedBlock = v.getBlock(); + boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; + if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { + StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); + int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); + for (int _x = 0; _x < barrelAmt; _x++) { + checkBlock(barrel.getType(), belowSeaLevel); + } + } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { + int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); + for (int _x = 0; _x < spawnerAmt; _x++) { + checkBlock(stackedBlock.getType(), belowSeaLevel); + } + } + it.remove(); + }); + } } } 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); + } + } + } +} + From 4d16e9c227030d952e246b43598ba7355d0984aa Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 4 May 2024 12:36:03 -0700 Subject: [PATCH 096/106] Implement a cache for top tens (#309) --- .../world/bentobox/level/LevelsManager.java | 466 +++++++++--------- .../world/bentobox/level/util/CachedData.java | 30 ++ 2 files changed, 272 insertions(+), 224 deletions(-) create mode 100644 src/main/java/world/bentobox/level/util/CachedData.java diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index f3532aa..ee7924a 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -2,6 +2,7 @@ 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,18 +32,19 @@ 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"; private static final TreeMap LEVELS; private static final BigInteger THOUSAND = BigInteger.valueOf(1000); static { - LEVELS = new TreeMap<>(); + LEVELS = new TreeMap<>(); - LEVELS.put(THOUSAND, "k"); - LEVELS.put(THOUSAND.pow(2), "M"); - LEVELS.put(THOUSAND.pow(3), "G"); - LEVELS.put(THOUSAND.pow(4), "T"); + LEVELS.put(THOUSAND, "k"); + LEVELS.put(THOUSAND.pow(2), "M"); + LEVELS.put(THOUSAND.pow(3), "G"); + LEVELS.put(THOUSAND.pow(4), "T"); } private final Level addon; @@ -52,49 +54,51 @@ 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; - // Get the BentoBox database - // Set up the database handler to store and retrieve data - // Note that these are saved by the BentoBox database - handler = new Database<>(addon, IslandLevels.class); - // Initialize the cache - levelsCache = new HashMap<>(); - // Initialize top ten lists - topTenLists = new ConcurrentHashMap<>(); + this.addon = addon; + // Get the BentoBox database + // Set up the database handler to store and retrieve data + // Note that these are saved by the BentoBox database + handler = new Database<>(addon, IslandLevels.class); + // Initialize the cache + levelsCache = new HashMap<>(); + // Initialize top ten lists + topTenLists = new ConcurrentHashMap<>(); } 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 void migrate() { * @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 @@ private boolean addToTopTen(Island island, long lv) { * @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 CompletableFuture calculateLevel(UUID targetPlayer, Island islan * @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 @@ private boolean fireIslandLevelCalcEvent(UUID targetPlayer, Island island, Resul * @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); - - 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 (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); + + 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; } /** @@ -204,7 +208,7 @@ public String formatLevel(@Nullable Long lvl) { * @return initial level of island */ public long getInitialLevel(Island island) { - return getLevelsData(island).getInitialLevel(); + return getLevelsData(island).getInitialLevel(); } /** @@ -216,11 +220,11 @@ public long getInitialLevel(Island island) { * 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 long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) { * 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(); } /** @@ -248,7 +252,7 @@ public long getIslandMaxLevel(@NonNull World world, @Nullable UUID targetPlayer) * null */ public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { - return formatLevel(getIslandLevel(world, targetPlayer)); + return formatLevel(getIslandLevel(world, targetPlayer)); } /** @@ -259,24 +263,24 @@ public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPl */ @NonNull public IslandLevels getLevelsData(@NonNull Island island) { - String id = island.getUniqueId(); - if (levelsCache.containsKey(id)) { - return levelsCache.get(id); - } - // Get from database if not in cache - if (handler.objectExists(id)) { - IslandLevels ld = handler.loadObject(id); - if (ld != null) { - levelsCache.put(id, ld); - } else { - handler.deleteID(id); - levelsCache.put(id, new IslandLevels(id)); - } - } else { - levelsCache.put(id, new IslandLevels(id)); - } - // Return cached value - return levelsCache.get(id); + String id = island.getUniqueId(); + if (levelsCache.containsKey(id)) { + return levelsCache.get(id); + } + // Get from database if not in cache + if (handler.objectExists(id)) { + IslandLevels ld = handler.loadObject(id); + if (ld != null) { + levelsCache.put(id, ld); + } else { + handler.deleteID(id); + levelsCache.put(id, new IslandLevels(id)); + } + } else { + levelsCache.put(id, new IslandLevels(id)); + } + // Return cached value + return levelsCache.get(id); } /** @@ -288,10 +292,10 @@ public IslandLevels getLevelsData(@NonNull Island island) { * @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,27 +308,27 @@ public String getPointsToNextString(@NonNull World world, @Nullable UUID targetP */ @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 -> { - - 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); + 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); } @@ -338,26 +342,38 @@ public Map getWeightedTopTen(@NonNull World world, int size) { */ @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 +384,13 @@ public Map getTopTenLists() { * @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 +401,26 @@ public int getRank(@NonNull World world, UUID uuid) { * @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,9 +430,11 @@ public void loadTopTens() { * @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 +445,10 @@ public void removeEntry(World world, String uuid) { * @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 +460,21 @@ public void setInitialIslandLevel(@NonNull Island island, long lv) { * @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 +486,18 @@ public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, lon * @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()); } /** @@ -488,8 +506,8 @@ private void setIslandResults(Island island, Results r) { * @param uniqueId - id of island */ public void deleteIsland(String uniqueId) { - levelsCache.remove(uniqueId); - handler.deleteID(uniqueId); + levelsCache.remove(uniqueId); + handler.deleteID(uniqueId); } } 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; + } +} From 1369ffb8c814b794912b8416f3fb42bbefafad88 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 5 May 2024 13:34:25 -0700 Subject: [PATCH 097/106] Remove unused imports. --- .../bentobox/level/calculators/IslandLevelCalculator.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index bca6aff..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.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; From 5dee0d2426b1c87cb8c254f21a24a22cccfff7e7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 5 May 2024 13:37:24 -0700 Subject: [PATCH 098/106] Fix space in locale file --- src/main/resources/locales/en-US.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 0e6df70..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: "&aLevel progress: &6[progress]&b/&e[levelcost]&a points" - 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." From 0bb6eacaf723170b0d8f86d5d185e2cebab3672d Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 31 May 2024 13:11:57 -0700 Subject: [PATCH 099/106] Update IslandLevelCalculator.java (#314) Fix for occasional errors likely due to the use of the remove() method within a lambda expression inside the thenAccept method. This lambda expression is executed asynchronously, which means that the iterator may not be in a consistent state when remove() is called. --- .../calculators/IslandLevelCalculator.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 8904cdf..0db44ef 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -638,27 +638,37 @@ private void handleChests() { } private void handleStackedBlocks() { - // Deal with any stacked blocks - Iterator it = stackedBlocks.iterator(); - while (it.hasNext()) { - Location v = it.next(); - Util.getChunkAtAsync(v).thenAccept(c -> { - Block stackedBlock = v.getBlock(); - boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; - if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { - StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); - int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); - for (int _x = 0; _x < barrelAmt; _x++) { - checkBlock(barrel.getType(), belowSeaLevel); - } - } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { - int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); - for (int _x = 0; _x < spawnerAmt; _x++) { - checkBlock(stackedBlock.getType(), belowSeaLevel); - } - } - it.remove(); - }); - } + // Deal with any stacked blocks + List toRemove = new ArrayList<>(); + Iterator it = stackedBlocks.iterator(); + while (it.hasNext()) { + Location v = it.next(); + Util.getChunkAtAsync(v).thenAccept(c -> { + Block stackedBlock = v.getBlock(); + boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; + if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { + StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); + int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); + for (int _x = 0; _x < barrelAmt; _x++) { + checkBlock(barrel.getType(), belowSeaLevel); + } + } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { + int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); + for (int _x = 0; _x < spawnerAmt; _x++) { + checkBlock(stackedBlock.getType(), belowSeaLevel); + } + } + synchronized (toRemove) { + toRemove.add(v); + } + }); + } + + // Wait for all asynchronous tasks to complete before removing elements + // Remove the elements collected in toRemove + synchronized (toRemove) { + stackedBlocks.removeAll(toRemove); } } + +} From d37f9ddcdd71e8c3786046c92e602c46663125a5 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 31 May 2024 16:17:05 -0700 Subject: [PATCH 100/106] Version 2.14.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4adb4dd..a24e216 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 2.0.9 1.20.4-R0.1-SNAPSHOT - 2.0.0-SNAPSHOT + 2.4.0-SNAPSHOT 1.12.0 @@ -71,7 +71,7 @@ -LOCAL - 2.13.0 + 2.14.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io From 7a241f898d65f643be0d476a549b3b9d155bcb62 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 31 May 2024 17:13:05 -0700 Subject: [PATCH 101/106] Fix tests to run with latest BentoBox --- .../java/world/bentobox/level/LevelTest.java | 18 ++++++++++++++++-- .../bentobox/level/PlaceholderManagerTest.java | 4 ++-- .../commands/AdminTopRemoveCommandTest.java | 6 +++--- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/test/java/world/bentobox/level/LevelTest.java b/src/test/java/world/bentobox/level/LevelTest.java index 5f8074a..3c9362b 100644 --- a/src/test/java/world/bentobox/level/LevelTest.java +++ b/src/test/java/world/bentobox/level/LevelTest.java @@ -2,7 +2,9 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -19,6 +21,7 @@ import java.util.Comparator; import java.util.Optional; import java.util.UUID; +import java.util.function.Consumer; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.logging.Logger; @@ -30,7 +33,9 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitScheduler; import org.eclipse.jdt.annotation.NonNull; import org.junit.After; @@ -47,6 +52,8 @@ import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; +import com.github.puregero.multilib.MultiLib; + import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.addons.AddonDescription; @@ -72,7 +79,7 @@ */ @SuppressWarnings("deprecation") @RunWith(PowerMockRunner.class) -@PrepareForTest({ Bukkit.class, BentoBox.class, User.class }) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, MultiLib.class }) public class LevelTest { private static File jFile; @@ -144,8 +151,15 @@ public static void beforeClass() throws IOException { /** * @throws java.lang.Exception */ - @Before + @SuppressWarnings("unchecked") + @Before public void setUp() throws Exception { + // Mock MultiLib + PowerMockito.mockStatic(MultiLib.class); + // Mock the behavior of the onString method + PowerMockito.doNothing().when(MultiLib.class); + MultiLib.onString(any(Plugin.class), anyString(), (Consumer) any(Consumer.class)); + // Set up plugin Whitebox.setInternalState(BentoBox.class, "instance", plugin); when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); diff --git a/src/test/java/world/bentobox/level/PlaceholderManagerTest.java b/src/test/java/world/bentobox/level/PlaceholderManagerTest.java index 56f0a1d..10bf0d0 100644 --- a/src/test/java/world/bentobox/level/PlaceholderManagerTest.java +++ b/src/test/java/world/bentobox/level/PlaceholderManagerTest.java @@ -9,9 +9,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; @@ -138,7 +138,7 @@ public void setUp() throws Exception { when(im.getIsland(any(World.class), any(User.class))).thenReturn(island); when(im.getIslandAt(any(Location.class))).thenReturn(Optional.of(island)); when(im.getIslandById(anyString())).thenAnswer((Answer>) invocation -> Optional.of(islands.get(invocation.getArgument(0, String.class)))); - when(im.getIslands(any(), any(UUID.class))).thenReturn(new HashSet<>(islands.values())); + when(im.getIslands(any(), any(UUID.class))).thenReturn(new ArrayList<>(islands.values())); when(addon.getIslands()).thenReturn(im); // Levels Manager diff --git a/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java b/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java index 9e82384..8a49c25 100644 --- a/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java +++ b/src/test/java/world/bentobox/level/commands/AdminTopRemoveCommandTest.java @@ -10,7 +10,7 @@ import static org.mockito.Mockito.when; import java.util.Collections; -import java.util.Set; +import java.util.List; import java.util.UUID; import org.bukkit.Bukkit; @@ -120,8 +120,8 @@ public void setUp() { when(island.getOwner()).thenReturn(uuid); // Island Manager when(plugin.getIslands()).thenReturn(im); - when(im.getIslands(any(), any(User.class))).thenReturn(Set.of(island)); - when(im.getIslands(any(), any(UUID.class))).thenReturn(Set.of(island)); + when(im.getIslands(any(), any(User.class))).thenReturn(List.of(island)); + when(im.getIslands(any(), any(UUID.class))).thenReturn(List.of(island)); // Bukkit PowerMockito.mockStatic(Bukkit.class); From c3e03a4f59bdf8da4b44cc30611a9fecfd7bbd7b Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 31 May 2024 17:59:56 -0700 Subject: [PATCH 102/106] Uses better approach #313 This uses CompleteableFutures instead of a recurring Bukkit task to check if collections have been removed. This is a much more reliable way to do it because it will complete when all the tasks are done and not before. --- .../calculators/IslandLevelCalculator.java | 265 +++++++++--------- 1 file changed, 125 insertions(+), 140 deletions(-) diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 0db44ef..025381a 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -89,34 +89,34 @@ public class IslandLevelCalculator { * @param zeroIsland - true if the calculation is due to an island zeroing */ public IslandLevelCalculator(Level addon, Island island, CompletableFuture r, boolean zeroIsland) { - this.addon = addon; - this.island = island; - this.r = r; - this.zeroIsland = zeroIsland; - results = new Results(); - duration = System.currentTimeMillis(); - chunksToCheck = getChunksToScan(island); - this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits()); - // Get the initial island level - results.initialLevel.set(addon.getInitialIslandLevel(island)); - // Set up the worlds - worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld())); - // Nether - if (addon.getSettings().isNether()) { - World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); - if (nether != null) { - worlds.put(Environment.NETHER, nether); - } - } - // End - if (addon.getSettings().isEnd()) { - World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); - if (end != null) { - worlds.put(Environment.THE_END, end); - } - } - // Sea Height - seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld()); + this.addon = addon; + this.island = island; + this.r = r; + this.zeroIsland = zeroIsland; + results = new Results(); + duration = System.currentTimeMillis(); + chunksToCheck = getChunksToScan(island); + this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits()); + // Get the initial island level + results.initialLevel.set(addon.getInitialIslandLevel(island)); + // Set up the worlds + worlds.put(Environment.NORMAL, Util.getWorld(island.getWorld())); + // Nether + if (addon.getSettings().isNether()) { + World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); + if (nether != null) { + worlds.put(Environment.NETHER, nether); + } + } + // End + if (addon.getSettings().isEnd()) { + World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); + if (end != null) { + worlds.put(Environment.THE_END, end); + } + } + // Sea Height + seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld()); } /** @@ -148,14 +148,14 @@ private long calculateLevel(long blockAndDeathPoints) { * @param belowSeaLevel - true if below sea level */ private void checkBlock(Material mat, boolean belowSeaLevel) { - int count = limitCount(mat); - if (belowSeaLevel) { - results.underWaterBlockCount.addAndGet(count); - results.uwCount.add(mat); - } else { - results.rawBlockCount.addAndGet(count); - results.mdCount.add(mat); - } + int count = limitCount(mat); + if (belowSeaLevel) { + results.underWaterBlockCount.addAndGet(count); + results.uwCount.add(mat); + } else { + results.rawBlockCount.addAndGet(count); + results.mdCount.add(mat); + } } /** @@ -180,7 +180,7 @@ private Queue> getChunksToScan(Island island) { * @return the island */ public Island getIsland() { - return island; + return island; } /** @@ -189,7 +189,7 @@ public Island getIsland() { * @return the r */ public CompletableFuture getR() { - return r; + return r; } /** @@ -258,7 +258,7 @@ private List getReport() { * @return the results */ public Results getResults() { - return results; + return results; } /** @@ -268,13 +268,13 @@ public Results getResults() { * @return value of a material */ private int getValue(Material md) { - Integer value = addon.getBlockConfig().getValue(island.getWorld(), md); - if (value == null) { - // Not in config - results.ncCount.add(md); - return 0; - } - return value; + Integer value = addon.getBlockConfig().getValue(island.getWorld(), md); + if (value == null) { + // Not in config + results.ncCount.add(md); + return 0; + } + return value; } /** @@ -286,44 +286,44 @@ private int getValue(Material md) { * there is no island nether */ private CompletableFuture> getWorldChunk(Environment env, Queue> pairList) { - if (worlds.containsKey(env)) { - CompletableFuture> r2 = new CompletableFuture<>(); - List chunkList = new ArrayList<>(); - World world = worlds.get(env); - // Get the chunk, and then coincidentally check the RoseStacker - loadChunks(r2, world, pairList, chunkList); - return r2; - } - return CompletableFuture.completedFuture(Collections.emptyList()); + if (worlds.containsKey(env)) { + CompletableFuture> r2 = new CompletableFuture<>(); + List chunkList = new ArrayList<>(); + World world = worlds.get(env); + // Get the chunk, and then coincidentally check the RoseStacker + loadChunks(r2, world, pairList, chunkList); + return r2; + } + return CompletableFuture.completedFuture(Collections.emptyList()); } private void loadChunks(CompletableFuture> r2, World world, Queue> pairList, - List chunkList) { - if (pairList.isEmpty()) { - r2.complete(chunkList); - return; - } - Pair p = pairList.poll(); - Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { - if (chunk != null) { - chunkList.add(chunk); - roseStackerCheck(chunk); - } - loadChunks(r2, world, pairList, chunkList); // Iteration - }); + List chunkList) { + if (pairList.isEmpty()) { + r2.complete(chunkList); + return; + } + Pair p = pairList.poll(); + Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> { + if (chunk != null) { + chunkList.add(chunk); + roseStackerCheck(chunk); + } + loadChunks(r2, world, pairList, chunkList); // Iteration + }); } private void roseStackerCheck(Chunk chunk) { - if (addon.isRoseStackersEnabled()) { - RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { - // Blocks below sea level can be scored differently - boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight; - // Check block once because the base block will be counted in the chunk snapshot - for (int _x = 0; _x < e.getStackSize() - 1; _x++) { - checkBlock(e.getBlock().getType(), belowSeaLevel); - } - }); - } + if (addon.isRoseStackersEnabled()) { + RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> { + // Blocks below sea level can be scored differently + boolean belowSeaLevel = seaHeight > 0 && e.getLocation().getY() <= seaHeight; + // Check block once because the base block will be counted in the chunk snapshot + for (int _x = 0; _x < e.getStackSize() - 1; _x++) { + checkBlock(e.getBlock().getType(), belowSeaLevel); + } + }); + } } /** @@ -334,17 +334,17 @@ private void roseStackerCheck(Chunk chunk) { * @return value of the block if can be counted */ private int limitCount(Material md) { - if (limitCount.containsKey(md)) { - int count = limitCount.get(md); - if (count > 0) { - limitCount.put(md, --count); - return getValue(md); - } else { - results.ofCount.add(md); - return 0; - } - } - return getValue(md); + if (limitCount.containsKey(md)) { + int count = limitCount.get(md); + if (count > 0) { + limitCount.put(md, --count); + return getValue(md); + } else { + results.ofCount.add(md); + return 0; + } + } + return getValue(md); } /** @@ -579,7 +579,7 @@ public void tidyUp() { * @return the zeroIsland */ boolean isNotZeroIsland() { - return !zeroIsland; + return !zeroIsland; } public void scanIsland(Pipeliner pipeliner) { @@ -608,67 +608,52 @@ public void scanIsland(Pipeliner pipeliner) { // 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); - + // This was the last chunk. Handle stacked blocks, then chests and exit + handleStackedBlocks().thenCompose(v -> handleChests()).thenRun(() -> { + this.tidyUp(); + this.getR().complete(getResults()); + }); } }); } - private void handleChests() { - Iterator it = chestBlocks.iterator(); - while (it.hasNext()) { - Chunk v = it.next(); - Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> { + private CompletableFuture handleChests() { + List> futures = new ArrayList<>(); + for (Chunk v : chestBlocks) { + CompletableFuture future = Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> { scanChests(c); - it.remove(); }); + futures.add(future); } + // Return a CompletableFuture that completes when all futures are done + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); } - private void handleStackedBlocks() { - // Deal with any stacked blocks - List toRemove = new ArrayList<>(); - Iterator it = stackedBlocks.iterator(); - while (it.hasNext()) { - Location v = it.next(); - Util.getChunkAtAsync(v).thenAccept(c -> { - Block stackedBlock = v.getBlock(); - boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; - if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { - StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); - int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); - for (int _x = 0; _x < barrelAmt; _x++) { - checkBlock(barrel.getType(), belowSeaLevel); - } - } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { - int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); - for (int _x = 0; _x < spawnerAmt; _x++) { - checkBlock(stackedBlock.getType(), belowSeaLevel); + private CompletableFuture handleStackedBlocks() { + // Deal with any stacked blocks + List> futures = new ArrayList<>(); + for (Location v : stackedBlocks) { + CompletableFuture future = Util.getChunkAtAsync(v).thenAccept(c -> { + Block stackedBlock = v.getBlock(); + boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight; + if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(stackedBlock)) { + StackedBarrel barrel = WildStackerAPI.getStackedBarrel(stackedBlock); + int barrelAmt = WildStackerAPI.getBarrelAmount(stackedBlock); + for (int _x = 0; _x < barrelAmt; _x++) { + checkBlock(barrel.getType(), belowSeaLevel); + } + } else if (WildStackerAPI.getWildStacker().getSystemManager().isStackedSpawner(stackedBlock)) { + int spawnerAmt = WildStackerAPI.getSpawnersAmount((CreatureSpawner) stackedBlock.getState()); + for (int _x = 0; _x < spawnerAmt; _x++) { + checkBlock(stackedBlock.getType(), belowSeaLevel); + } } - } - synchronized (toRemove) { - toRemove.add(v); - } - }); - } + }); + futures.add(future); + } + // Return a CompletableFuture that completes when all futures are done + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - // Wait for all asynchronous tasks to complete before removing elements - // Remove the elements collected in toRemove - synchronized (toRemove) { - stackedBlocks.removeAll(toRemove); } -} } From f2da5ba1045559d6e099f1a8037a3d7636efc474 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 1 Jun 2024 08:40:29 -0700 Subject: [PATCH 103/106] Use new BentoBox API to avoid loading islands into cache on startup Relates to #312 --- .../world/bentobox/level/LevelsManager.java | 77 ++++++++++--------- src/main/resources/addon.yml | 2 +- .../bentobox/level/LevelsManagerTest.java | 6 +- 3 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index f0680c5..46d47b3 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -39,12 +39,12 @@ public class LevelsManager { private static final TreeMap LEVELS; private static final BigInteger THOUSAND = BigInteger.valueOf(1000); static { - LEVELS = new TreeMap<>(); + LEVELS = new TreeMap<>(); - LEVELS.put(THOUSAND, "k"); - LEVELS.put(THOUSAND.pow(2), "M"); - LEVELS.put(THOUSAND.pow(3), "G"); - LEVELS.put(THOUSAND.pow(4), "T"); + LEVELS.put(THOUSAND, "k"); + LEVELS.put(THOUSAND.pow(2), "M"); + LEVELS.put(THOUSAND.pow(3), "G"); + LEVELS.put(THOUSAND.pow(4), "T"); } private final Level addon; @@ -58,15 +58,15 @@ public class LevelsManager { private Map cache = new HashMap<>(); public LevelsManager(Level addon) { - this.addon = addon; - // Get the BentoBox database - // Set up the database handler to store and retrieve data - // Note that these are saved by the BentoBox database - handler = new Database<>(addon, IslandLevels.class); - // Initialize the cache - levelsCache = new HashMap<>(); - // Initialize top ten lists - topTenLists = new ConcurrentHashMap<>(); + this.addon = addon; + // Get the BentoBox database + // Set up the database handler to store and retrieve data + // Note that these are saved by the BentoBox database + handler = new Database<>(addon, IslandLevels.class); + // Initialize the cache + levelsCache = new HashMap<>(); + // Initialize top ten lists + topTenLists = new ConcurrentHashMap<>(); } public void migrate() { @@ -208,7 +208,7 @@ public String formatLevel(@Nullable Long lvl) { * @return initial level of island */ public long getInitialLevel(Island island) { - return getLevelsData(island).getInitialLevel(); + return getLevelsData(island).getInitialLevel(); } /** @@ -252,7 +252,7 @@ public long getIslandMaxLevel(@NonNull World world, @Nullable UUID targetPlayer) * null */ public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { - return formatLevel(getIslandLevel(world, targetPlayer)); + return formatLevel(getIslandLevel(world, targetPlayer)); } /** @@ -263,24 +263,24 @@ public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPl */ @NonNull public IslandLevels getLevelsData(@NonNull Island island) { - String id = island.getUniqueId(); - if (levelsCache.containsKey(id)) { - return levelsCache.get(id); - } - // Get from database if not in cache - if (handler.objectExists(id)) { - IslandLevels ld = handler.loadObject(id); - if (ld != null) { - levelsCache.put(id, ld); - } else { - handler.deleteID(id); - levelsCache.put(id, new IslandLevels(id)); - } - } else { - levelsCache.put(id, new IslandLevels(id)); - } - // Return cached value - return levelsCache.get(id); + String id = island.getUniqueId(); + if (levelsCache.containsKey(id)) { + return levelsCache.get(id); + } + // Get from database if not in cache + if (handler.objectExists(id)) { + IslandLevels ld = handler.loadObject(id); + if (ld != null) { + levelsCache.put(id, ld); + } else { + handler.deleteID(id); + levelsCache.put(id, new IslandLevels(id)); + } + } else { + levelsCache.put(id, new IslandLevels(id)); + } + // Return cached value + return levelsCache.get(id); } /** @@ -414,7 +414,8 @@ public void loadTopTens() { addon.log("Generating rankings"); handler.loadObjects().forEach(il -> { if (il.getLevel() > 0) { - addon.getIslands().getIslandById(il.getUniqueId()) + // Load islands, but don't cache them + addon.getIslands().getIslandById(il.getUniqueId(), false) .ifPresent(i -> this.addToTopTen(i, il.getLevel())); } }); @@ -429,7 +430,7 @@ public void loadTopTens() { * @param uuid - the island's uuid */ public void removeEntry(World world, String uuid) { - if (topTenLists.containsKey(world)) { + if (topTenLists.containsKey(world)) { topTenLists.get(world).getTopTen().remove(uuid); // Invalidate the cache because of this deletion cache.remove(world); @@ -504,8 +505,8 @@ private void setIslandResults(Island island, Results r) { * @param uniqueId - id of island */ public void deleteIsland(String uniqueId) { - levelsCache.remove(uniqueId); - handler.deleteID(uniqueId); + levelsCache.remove(uniqueId); + handler.deleteID(uniqueId); } } diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index d7e5b6d..9edbf7d 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -2,7 +2,7 @@ name: Level main: world.bentobox.level.Level version: ${version}${build.number} icon: DIAMOND -api-version: 1.16.5 +api-version: 2.4.0 authors: tastybento diff --git a/src/test/java/world/bentobox/level/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java index 551c573..c0be60c 100644 --- a/src/test/java/world/bentobox/level/LevelsManagerTest.java +++ b/src/test/java/world/bentobox/level/LevelsManagerTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -165,6 +166,7 @@ public void setUp() throws Exception { when(im.hasIsland(eq(world), any(UUID.class))).thenReturn(true); when(im.getIsland(world, uuid)).thenReturn(island); when(im.getIslandById(anyString())).thenReturn(Optional.of(island)); + when(im.getIslandById(anyString(), eq(false))).thenReturn(Optional.of(island)); // Player when(player.getUniqueId()).thenReturn(uuid); @@ -395,8 +397,8 @@ public void testLoadTopTens() { lm.loadTopTens(); PowerMockito.verifyStatic(Bukkit.class); // 1 Bukkit.getScheduler(); - verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture()); - task.getValue().run(); + verify(scheduler).runTaskAsynchronously(eq(plugin), task.capture()); // Capture the task in the scheduler + task.getValue().run(); // run it verify(addon).log("Generating rankings"); verify(addon).log("Generated rankings for bskyblock-world"); From 00f6fee1bf2ae6d19d6b12e31b271764507d7584 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 1 Jun 2024 10:32:11 -0700 Subject: [PATCH 104/106] Remove unneeded import and reference to MultiLib --- .../java/world/bentobox/level/LevelTest.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/test/java/world/bentobox/level/LevelTest.java b/src/test/java/world/bentobox/level/LevelTest.java index 3c9362b..02cb04e 100644 --- a/src/test/java/world/bentobox/level/LevelTest.java +++ b/src/test/java/world/bentobox/level/LevelTest.java @@ -2,9 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -21,7 +19,6 @@ import java.util.Comparator; import java.util.Optional; import java.util.UUID; -import java.util.function.Consumer; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.logging.Logger; @@ -33,9 +30,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitScheduler; import org.eclipse.jdt.annotation.NonNull; import org.junit.After; @@ -52,8 +47,6 @@ import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; -import com.github.puregero.multilib.MultiLib; - import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.addons.AddonDescription; @@ -79,7 +72,7 @@ */ @SuppressWarnings("deprecation") @RunWith(PowerMockRunner.class) -@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, MultiLib.class }) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class }) public class LevelTest { private static File jFile; @@ -151,15 +144,8 @@ public static void beforeClass() throws IOException { /** * @throws java.lang.Exception */ - @SuppressWarnings("unchecked") @Before public void setUp() throws Exception { - // Mock MultiLib - PowerMockito.mockStatic(MultiLib.class); - // Mock the behavior of the onString method - PowerMockito.doNothing().when(MultiLib.class); - MultiLib.onString(any(Plugin.class), anyString(), (Consumer) any(Consumer.class)); - // Set up plugin Whitebox.setInternalState(BentoBox.class, "instance", plugin); when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); From 7e8392d4f08767b5dc289e1f29eb0727fa51f538 Mon Sep 17 00:00:00 2001 From: Minecraft_15 <147026380+huguyt@users.noreply.github.com> Date: Sat, 8 Jun 2024 23:51:17 +0800 Subject: [PATCH 105/106] Update zh-CN.yml (#315) * Delete src/main/resources/locales/zh-CN.yml * Update zh-CN.yml Update language files to adapt to the latest version of Bentobox-Level --- src/main/resources/locales/zh-CN.yml | 212 ++++++++++++++------------- 1 file changed, 111 insertions(+), 101 deletions(-) mode change 100755 => 100644 src/main/resources/locales/zh-CN.yml diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml old mode 100755 new mode 100644 index d015525..e9283e0 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -1,79 +1,93 @@ ---- admin: level: - parameters: "" + parameters: description: 计算指定玩家的岛屿等级 sethandicap: - parameters: " " - description: 设置偏差值,通常用于抵消初始岛屿等级,来保证岛屿等级从零开始。实际岛屿等级 - = 最终的岛屿等级 - changed: "&a 岛屿的偏差值从 [number] 更改为 [new_number]" - invalid-level: "&c 偏差值无效,请使用整数" + parameters: + description: 设置偏差值, 通常用于抵消初始岛屿等级, 来保证岛屿等级从零开始. 实际岛屿等级 - = 最终的岛屿等级 + changed: '&a岛屿的偏差值从[number]更改为[new_number]' + invalid-level: '&c偏差值无效, 请使用整数' levelstatus: description: 显示等级计算队列中的岛屿 - islands-in-queue: "&a 列队中的岛屿:[number]" + islands-in-queue: '&a列队中的岛屿: [number]' top: description: 显示前十名 - unknown-world: "&c 未知的世界!" - display: "&f[rank]. &a[name] &7- &b[level]" + unknown-world: '&c未知的世界!' + display: '&f[rank]. &a[name] &7- &b[level]' remove: description: 将玩家移出前十名 - parameters: "" + parameters: + stats: + description: 显示该服务器上岛屿的统计数据 + title: 服务器岛屿数据 + world: '&a[name]' + no-data: '&c没有数据.' + average-level: '平均岛屿等级: [number]' + median-level: '中位数岛屿等级: [number]' + mode-level: '众数岛屿等级: [number]' + highest-level: '最高岛屿等级: [number]' + lowest-level: '最低岛屿等级: [number]' + distribution: '岛屿等级分布:' + islands: 个岛屿 island: level: - parameters: "[player]" - description: 计算你或指定玩家 [player] 的岛屿等级 - calculating: "&a 等级计算中..." - estimated-wait: "&a 预计等待时间:[number] 秒" - in-queue: "&a 你处于队列中第 [number] 个" - island-level-is: "&a 岛屿等级为 &b[level]" - required-points-to-next-level: "&a 还需 [points] 点数才能到达下一级" - deaths: "&c([number] 次死亡)" - cooldown: "&c 还需等待 &b[time] &c秒才能再次使用该指令" - in-progress: "&6 岛级等级正在计算中..." - time-out: "&c 等级计算超时。请稍后再试" + parameters: '[player]' + description: 计算你或指定玩家[player]的岛屿等级 + calculating: '&a等级计算中...' + estimated-wait: '&a预计等待时间: [number]秒' + in-queue: '&a你处于队列中第[number]个' + island-level-is: '&a岛屿等级为: &b[level]' + required-points-to-next-level: '&a还需[points]点数才能到达下一级' + deaths: '&c([number]次死亡)' + cooldown: '&c还需等待&b[time]&c秒才能再次使用该指令' + in-progress: '&6岛屿等级正在计算中...' + time-out: '&c等级计算超时, 请稍后再试' + top: description: 显示前十名 - gui-title: "&a 前十" - gui-heading: "&6[name]: &B[rank]" - island-level: "&b 等级 [level]" - warp-to: "&a 正在传送到 [name] 的岛屿" + gui-title: '&a前十' + gui-heading: '&6[name]: &B[rank]' + island-level: '&b等级: [level]' + warp-to: '&a正在传送到[name]的岛屿' + level-details: above-sea-level-blocks: 海平面以上的方块 spawners: 刷怪笼 underwater-blocks: 水下的方块 all-blocks: 所有方块 - no-island: "&c 没有岛屿!" - names-island: "[name] 的岛屿" - syntax: "[name] x [number]" - hint: "&c 运行level指令查看方块报告" + no-island: '&c没有岛屿!' + names-island: '[name]的岛屿' + syntax: '[name] x [number]' + hint: '&c运行level指令查看方块报告' + level: commands: value: - parameters: "[hand|]" - description: 显示方块的价值。在末尾添加 'hand' 可显示手中方块的价值 + parameters: '[hand|]' + description: 显示方块的价值. 在末尾添加'hand'可显示手中方块的价值 gui: titles: - top: "&0&l 岛屿排行榜" - detail-panel: "&0&l [name] 的岛屿" - value-panel: "&0&l 方块价值" + top: '&0&l岛屿排行榜' + detail-panel: '&0&l[name]的岛屿' + value-panel: '&0&l方块价值' buttons: island: - empty: "&f&l 第 [name] 名" - name: "&f&l [name]" + empty: '&f&l第[name]名' + name: '&f&l[name]' description: |- [owner] [members] [place] [level] - owners-island: "[player] 的岛屿" - owner: "&7&l 岛主:&r&b [player]" - members-title: "&7&l 成员:" - member: "&b - [player]" + owners-island: '[player]的岛屿' + owner: '&7&l岛主: &r&b[player]' + members-title: '&7&l成员: ' + member: '&b- [player]' unknown: 未知 - place: "&7第 &7&o[number] &r&7名" - level: "&7 等级: &o [number]" + place: '&7第&7&o[number]&r&7名' + level: '&7等级: &o[number]' material: - name: "&f&l [number] x [material]" + name: '&f&l [number] x [material]' description: |- [description] [count] @@ -81,83 +95,79 @@ level: [calculated] [limit] [id] - id: "&7 方块ID:&e [id]" - value: "&7 方块价值:&e [number]" - limit: "&7 方块限制:&e [number]" - count: "&7 方块数量:&e [number]" - calculated: "&7 计算值:&e [number]" + id: '&7方块ID: &e[id]' + value: '&7方块价值: &e[number]' + limit: '&7方块限制: &e[number]' + count: '&7方块数量: &e[number]' + calculated: '&7计算值: &e[number]' all_blocks: - name: "&f&l 所有方块" - description: "&7 显示岛屿上所有的方块" + name: '&f&l所有方块' + description: '&7显示岛屿上所有的方块' above_sea_level: - name: "&f&l 海平面以上的方块" - description: |- - &7 只显示所有 - &7 海平面以上的方块 + name: '&f&l海平面以上的方块' + description: '&7只显示所有海平面以上的方块' underwater: - name: "&f&l 海平面以下的方块" - description: |- - &7 只显示所有 - &7 海平面以下的方块 + name: '&f&l海平面以下的方块' + description: 只显示所有海平面以下的方块 spawner: - name: "&f&l 刷怪笼" - description: "&7 只显示刷怪笼" + name: '&f&l刷怪笼' + description: '&7只显示刷怪笼' filters: name: - name: "&f&l 按名称排序" - description: "&7 通过名称排序所有的方块" + name: '&f&l按名称排序' + description: '&7通过名称排序所有的方块' value: - name: "&f&l 按价值排序" - description: "&7 通过价值排序所有的方块" + name: '&f&l按价值排序' + description: '&7通过价值排序所有的方块' count: - name: "&f&l 按数量排序" - description: "&7 通过数量排序所有方块" + name: '&f&l按数量排序' + description: '&7通过数量排序所有方块' value: - name: "&f&l [material]" + name: '&f&l[material]' description: |- [description] [value] [underwater] [limit] [id] - id: "&7 方块ID:&e [id]" - value: "&7 方块价值:&e [number]" - underwater: "&7 海平面以下方块的价值:&e [number]" - limit: "&7 方块限制:&e [number]" + id: '&7方块ID: &e[id]' + value: '&7方块价值: &e[number]' + underwater: '&7海平面以下方块的价值: &e[number]' + limit: '&7方块限制: &e[number]' previous: - name: "&f&l 上一页" - description: "&7 切换到第 [number] 页" + name: '&f&l上一页' + description: '&7切换到第[number]页' next: - name: "&f&l 下一页" - description: "&7 切换到第 [number] 页" + name: '&f&l下一页' + description: '&7切换到第[number]页' search: - name: "&f&l 搜索" - description: "&7 搜索特定的内容" - search: "&b 搜索值:[value]" + name: '&f&l搜索' + description: '&7搜索特定的内容' + search: '&b搜索值: [value]' tips: - click-to-view: "&e 点击 &7 查看" - click-to-previous: "&e 点击 &7 查看上一页" - click-to-next: "&e 点击 &7 查看下一页" - click-to-select: "&e 点击 &7 选择" - left-click-to-cycle-up: "&e 左键点击 &7 向上循环" - right-click-to-cycle-down: "&e 右键点击 &7 向下循环" - left-click-to-change: "&e 左键点击 &7 编辑" - right-click-to-clear: "&e 右键点击 &7 清除" - click-to-asc: "&e 点击 &7 以升序排序" - click-to-desc: "&e 点击 &7 以降序排序" - click-to-warp: "&e 点击 &7 去岛屿传送点" - click-to-visit: "&e 点击 &7 参观" - right-click-to-visit: "&e 右键点击 &7 查看" + click-to-view: '&e点击 &7查看' + click-to-previous: '&e点击 &7查看上一页' + click-to-next: '&e点击 &7查看下一页' + click-to-select: '&e点击 &7选择' + left-click-to-cycle-up: '&e左键 &7向上循环' + right-click-to-cycle-down: '&e右键 &7向下循环' + left-click-to-change: '&e左键 &7编辑' + right-click-to-clear: '&e右键 &7清除' + click-to-asc: '&e点击 &7以升序排序' + click-to-desc: '&e点击 &7以降序排序' + click-to-warp: '&e点击 &7去岛屿传送点' + click-to-visit: '&e点击 &7参观' + right-click-to-visit: '&e右键 &7查看' conversations: - prefix: "&l&6 [BentoBox]: &r" - no-data: "&c 运行level指令查看方块报告" + prefix: '&l&6[BentoBox]: &r' + no-data: '&c运行level指令查看方块报告' cancel-string: cancel exit-string: cancel, exit, quit - write-search: "&e 请输入要搜索的值. (输入 'cancel' 退出)" - search-updated: "&a 搜索值已更新" - cancelled: "&c 对话已取消!" - no-value: "&c 这件物品一文不值" - unknown-item: "&c 物品 '[material]' 在游戏中不存在" - value: "&7 物品 '[material]' 的价值:&e[value]" - value-underwater: "&7 物品 '[material]' 在海平面以下的价值:&e[value]" - empty-hand: "&c 你的手中没有拿着方块" + write-search: '&e请输入要搜索的值. (输入''cancel''退出)' + search-updated: '&a搜索值已更新' + cancelled: '&c对话已取消' + no-value: '&c这件物品一文不值' + unknown-item: '&c物品''[material]''在游戏中不存在' + value: '&7物品''[material]''的价值: &e[value]' + value-underwater: '&7物品''[material]''在海平面以下的价值: &e[value]' + empty-hand: '&c你的手中没有拿着方块' From c0653790b14a271fbb5b3976bdfc13534e0d95a3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 30 Jun 2024 14:22:33 -0700 Subject: [PATCH 106/106] Test fixes --- src/main/java/world/bentobox/level/LevelsManager.java | 2 +- src/test/java/world/bentobox/level/LevelTest.java | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index 28064fa..46d47b3 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -414,7 +414,7 @@ public void loadTopTens() { addon.log("Generating rankings"); handler.loadObjects().forEach(il -> { if (il.getLevel() > 0) { -\ // Load islands, but don't cache them + // Load islands, but don't cache them addon.getIslands().getIslandById(il.getUniqueId(), false) .ifPresent(i -> this.addToTopTen(i, il.getLevel())); } diff --git a/src/test/java/world/bentobox/level/LevelTest.java b/src/test/java/world/bentobox/level/LevelTest.java index 02cb04e..7da610f 100644 --- a/src/test/java/world/bentobox/level/LevelTest.java +++ b/src/test/java/world/bentobox/level/LevelTest.java @@ -61,6 +61,7 @@ import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.managers.PlaceholdersManager; +import world.bentobox.bentobox.util.Util; import world.bentobox.level.config.BlockConfig; import world.bentobox.level.config.ConfigSettings; import world.bentobox.level.listeners.IslandActivitiesListeners; @@ -72,7 +73,7 @@ */ @SuppressWarnings("deprecation") @RunWith(PowerMockRunner.class) -@PrepareForTest({ Bukkit.class, BentoBox.class, User.class }) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class }) public class LevelTest { private static File jFile; @@ -189,6 +190,11 @@ public void setUp() throws Exception { when(Bukkit.getServer()).thenReturn(server); when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class)); + when(Bukkit.getBukkitVersion()).thenReturn(""); + + // Util + PowerMockito.mockStatic(Util.class, Mockito.RETURNS_MOCKS); + when(Util.inTest()).thenReturn(true); // Addon addon = new Level(); @@ -221,7 +227,6 @@ public void setUp() throws Exception { when(fm.getFlags()).thenReturn(Collections.emptyList()); // Bukkit - PowerMockito.mockStatic(Bukkit.class); when(Bukkit.getScheduler()).thenReturn(scheduler); ItemMeta meta = mock(ItemMeta.class); ItemFactory itemFactory = mock(ItemFactory.class);