From 95c9e55cfe756dac617d5be94944fa8c6dbea99c Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 15 Apr 2023 16:44:33 -0700 Subject: [PATCH] Version 2.10.0 (#283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Version 2.7.1 * Version 2.7.2 * Use Java 9's takeWhile * Added placeholder %Level_[gamemode]_rank_value Fixes https://github.com/BentoBoxWorld/Level/issues/228 * 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 * Rosestacker (#232) * Add support for RoseStacker 1.3.0 * Made plugin a Pladdon. * Version 2.8.0 * 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 * Update to BentoBox API 1.18 * Open up modules for testing access. * Back support for BentoBox 1.16.5. * Version 2.8.1 * 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 * add Vietnamese (#240) * Raw island level placeholder (#241) * Changed IslandLevelCalculator minHeight to world minHeight for negative blocks height support since 1.18. (#246) * Version 2.9.0 * Chinese Translation (#249) * Translate zh-CN.yml via GitLocalize * Translate zh-CN.yml via GitLocalize Co-authored-by: mt-gitlocalize Co-authored-by: 织梦 <493733933@qq.com> * Translate id.yml via GitLocalize (#250) Co-authored-by: Nathan Adhitya * Translate fr.yml via GitLocalize (#251) Co-authored-by: organizatsiya * Korean translation (#252) * Translate ko.yml via GitLocalize * Translate ko.yml via GitLocalize Co-authored-by: chickiyeah Co-authored-by: mt-gitlocalize * German Translation (#253) * Translate de.yml via GitLocalize * Update de.yml Co-authored-by: Rikamo045 Co-authored-by: tastybento * Translate hu.yml via GitLocalize (#254) Co-authored-by: András Marczinkó * Version 2.9.1 * Attempt to handle WildStacker spawners * Fix error lon loading id locale * Avoid async chunk snapshotting. Fixes https://github.com/BentoBoxWorld/Level/issues/256 * Update to BentoBox API 1.20. Replace plugin.yml with spigot-annotations. Implement customizable TopLevelPanel. * Fixes some small issues with TopLevelPanel Add Utils class that contains some useful things. * Implement customizable DetailsPanel. Remove old DetailsGUITab due to new implementation. * Fix failing test. * Remove blank file * Added repo for maven plugin snapshots * 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 * 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 * Update es.yml (#261) * 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 * Support for AdvancedChests was updated. (#266) * 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. * 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 * Translate pl.yml via GitLocalize (#269) Co-authored-by: wiktorm12 * Translate fr.yml via GitLocalize (#272) Co-authored-by: organizatsiya * Update to Java 17 * Update Github workflow to Java 17 * Adds %Level_[gamemode]_island_level_max% placeholder This records the lifetime maximum level the island has ever had. Addresses #271 * Only shows Members or higher in the top members placeholder Fixes #267 * Add natural log to level-calc formula parsing Relates to #274 * feat: add island total points + placeholder (#264) * feat: add island total points + placeholder * Update IslandLevels.java * Fix JavaDoc * Translate zh-CN.yml via GitLocalize (#276) Co-authored-by: dawnTak * Translate nl.yml via GitLocalize (#277) Co-authored-by: DevSolaris * Add ${argLine} to get jacoco coverage * Updated Jacoco POM * Add shulker to in chest count (#275) * Sonar Cloud code smell clean up (#278) * 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. * Remove dependency * Add UltimateStacker hook for stacked blocks (#281) * 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 * Updated dependencies --------- Co-authored-by: Huynh Tien Co-authored-by: Rubén <44579213+Rubenicos@users.noreply.github.com> Co-authored-by: Pierre Dedrie Co-authored-by: gitlocalize-app[bot] <55277160+gitlocalize-app[bot]@users.noreply.github.com> Co-authored-by: mt-gitlocalize Co-authored-by: 织梦 <493733933@qq.com> Co-authored-by: Nathan Adhitya Co-authored-by: organizatsiya Co-authored-by: chickiyeah Co-authored-by: Rikamo045 Co-authored-by: András Marczinkó Co-authored-by: BONNe Co-authored-by: KrazyxWolf <68208993+KrazyxWolf@users.noreply.github.com> Co-authored-by: DeadSilenceIV Co-authored-by: wiktorm12 Co-authored-by: evlad Co-authored-by: dawnTak Co-authored-by: DevSolaris Co-authored-by: DevSolaris <105156235+DevSolaris@users.noreply.github.com> Co-authored-by: ceze88 --- .github/workflows/build.yml | 4 +- README.md | 14 +- pom.xml | 97 ++- src/main/java/world/bentobox/level/Level.java | 255 +++--- .../world/bentobox/level/LevelPladdon.java | 4 +- .../world/bentobox/level/LevelsManager.java | 143 +--- .../bentobox/level/PlaceholderManager.java | 176 ++++ .../calculators/IslandLevelCalculator.java | 222 +++-- .../bentobox/level/calculators/Results.java | 20 +- .../level/commands/AdminTopRemoveCommand.java | 3 +- .../level/commands/IslandLevelCommand.java | 2 +- .../level/commands/IslandTopCommand.java | 4 +- .../level/commands/IslandValueCommand.java | 135 ++- .../bentobox/level/config/ConfigSettings.java | 21 +- .../level/listeners/MigrationListener.java | 61 ++ .../bentobox/level/objects/IslandLevels.java | 37 + .../bentobox/level/panels/DetailsGUITab.java | 211 ----- .../bentobox/level/panels/DetailsPanel.java | 804 ++++++++++++++++++ .../bentobox/level/panels/TopLevelPanel.java | 547 ++++++++++++ .../bentobox/level/panels/ValuePanel.java | 779 +++++++++++++++++ .../level/util/ConversationUtils.java | 126 +++ .../java/world/bentobox/level/util/Utils.java | 229 +++++ src/main/resources/config.yml | 2 +- src/main/resources/locales/de.yml | 32 +- src/main/resources/locales/en-US.yml | 150 +++- src/main/resources/locales/es.yml | 156 +++- src/main/resources/locales/fr.yml | 166 +++- src/main/resources/locales/hu.yml | 47 +- src/main/resources/locales/id.yml | 56 +- src/main/resources/locales/ko.yml | 51 ++ src/main/resources/locales/nl.yml | 165 ++++ src/main/resources/locales/pl.yml | 133 ++- src/main/resources/locales/ro.yml | 1 - src/main/resources/locales/zh-CN.yml | 179 +++- src/main/resources/panels/detail_panel.yml | 130 +++ src/main/resources/panels/top_panel.yml | 195 +++++ src/main/resources/panels/value_panel.yml | 109 +++ src/main/resources/plugin.yml | 14 +- .../java/world/bentobox/level/LevelTest.java | 8 - .../bentobox/level/LevelsManagerTest.java | 30 +- .../level/PlaceholderManagerTest.java | 290 +++++++ .../admin/AdminTopRemoveCommandTest.java | 6 +- 42 files changed, 5055 insertions(+), 759 deletions(-) create mode 100644 src/main/java/world/bentobox/level/PlaceholderManager.java create mode 100644 src/main/java/world/bentobox/level/listeners/MigrationListener.java 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/java/world/bentobox/level/panels/TopLevelPanel.java 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/java/world/bentobox/level/util/Utils.java create mode 100644 src/main/resources/locales/ko.yml create mode 100644 src/main/resources/locales/nl.yml delete mode 100644 src/main/resources/locales/ro.yml create mode 100644 src/main/resources/panels/detail_panel.yml create mode 100644 src/main/resources/panels/top_panel.yml create mode 100644 src/main/resources/panels/value_panel.yml create mode 100644 src/test/java/world/bentobox/level/PlaceholderManagerTest.java 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: 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/pom.xml b/pom.xml index 5766363..599fd60 100644 --- a/pom.xml +++ b/pom.xml @@ -54,18 +54,24 @@ UTF-8 UTF-8 - 16 + 17 2.0.9 - 1.16.5-R0.1-SNAPSHOT - 1.16.5-SNAPSHOT + 1.19.4-R0.1-SNAPSHOT + 1.23.0 + + 1.12.0 + + 1.4.0 + + 1.1.0 ${build.version}-SNAPSHOT -LOCAL - 2.9.0 + 2.10.0 BentoBoxWorld_Level bentobox-world https://sonarcloud.io @@ -113,6 +119,13 @@ + + + apache.snapshots + https://repository.apache.org/snapshots/ + + + spigot-repo @@ -136,6 +149,11 @@ rosewood-repo https://repo.rosewooddev.io/repository/public/ + + + songoda-public + https://repo.songoda.com/repository/public/ + @@ -171,6 +189,23 @@ ${bentobox.version} provided + + world.bentobox + warps + ${warps.version} + provided + + + world.bentobox + visit + ${visit.version} + provided + + + lv.id.bonne + panelutils + ${panelutils.version} + com.github.OmerBenGera @@ -190,7 +225,7 @@ com.github.DeadSilenceIV AdvancedChestsAPI - 1.8 + 2.9-BETA provided @@ -199,6 +234,12 @@ 1.3.0 provided + + com.songoda + UltimateStacker + 2.3.3 + provided + @@ -249,6 +290,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 @@ -326,10 +368,42 @@ 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 - 0.8.3 + 0.8.7 true @@ -340,20 +414,25 @@ - pre-unit-test + prepare-agent prepare-agent - post-unit-test + report report + + + XML + + - \ No newline at end of file + diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 79c7a5e..61789e5 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -3,18 +3,13 @@ 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; 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 +17,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,16 +32,19 @@ 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; import world.bentobox.level.requests.TopTenRequestHandler; +import world.bentobox.visit.VisitAddon; +import world.bentobox.warps.Warp; + /** * @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; @@ -61,8 +58,20 @@ public class Level extends Addon implements Listener { 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 @@ -74,11 +83,17 @@ public void onLoad() { } 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; } @@ -92,7 +107,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() @@ -100,7 +116,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 @@ -119,10 +135,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; } } @@ -131,8 +147,47 @@ 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 + 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 @@ -158,152 +213,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 - 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())); - - // 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() - .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); @@ -440,7 +349,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); @@ -452,7 +361,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() @@ -501,4 +410,30 @@ 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/LevelPladdon.java b/src/main/java/world/bentobox/level/LevelPladdon.java index 7bebe0c..356833c 100644 --- a/src/main/java/world/bentobox/level/LevelPladdon.java +++ b/src/main/java/world/bentobox/level/LevelPladdon.java @@ -1,16 +1,16 @@ package world.bentobox.level; + import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Pladdon; + /** * @author tastybento * */ public class LevelPladdon extends Pladdon { - @Override public Addon getAddon() { return new Level(); } - } diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index cfa2247..e40ba49 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,13 +30,11 @@ 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"; 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<>(); @@ -55,8 +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; @@ -64,9 +52,6 @@ public class LevelsManager { private final Map levelsCache; // Top ten lists private final Map topTenLists; - // Background - private final PanelItem background; - public LevelsManager(Level addon) { @@ -79,8 +64,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() { @@ -169,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 @@ -198,6 +179,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)); } @@ -226,102 +208,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 @@ -344,6 +230,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 @@ -436,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); } /** @@ -453,7 +352,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"); @@ -462,10 +361,7 @@ 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())); }); } @@ -532,6 +428,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/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/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 845ba5c..9c2a6cf 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; @@ -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; @@ -24,12 +27,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.*; 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; @@ -51,7 +53,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, @@ -66,9 +68,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(); @@ -83,10 +86,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; } @@ -96,7 +99,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 @@ -105,7 +108,7 @@ void nextChar() { } } - double parseFactor() { + double parseFactor() throws IOException { if (eat('+')) return parseFactor(); // unary plus if (eat('-')) return -parseFactor(); // unary minus @@ -134,11 +137,14 @@ 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); + 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 @@ -146,7 +152,7 @@ void nextChar() { return x; } - double parseTerm() { + double parseTerm() throws IOException { double x = parseFactor(); for (;;) { if (eat('*')) x *= parseFactor(); // multiplication @@ -159,7 +165,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; @@ -188,7 +194,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 @@ -219,7 +225,15 @@ public IslandLevelCalculator(Level addon, Island island, CompletableFuture> getWorldChunk(Environment env, Queue> pairList) { @@ -416,48 +429,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 Only) - this has to use the real chunk - if (addon.isStackersEnabled() && blockData.getMaterial() == Material.CAULDRON) { - 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 @@ -465,35 +436,42 @@ private void scanAsync(Chunk chunk) { private void scanChests(Chunk chunk) { // Count blocks in chests for (BlockState bs : chunk.getTileEntities()) { - if (bs instanceof Container) { + if (bs instanceof Container 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; } } // Regular chest - ((Container)bs).getSnapshotInventory().forEach(this::countItemStack); + container.getSnapshotInventory().forEach(this::countItemStack); } } } 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); } } /** - * 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) { @@ -503,14 +481,82 @@ 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 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); + } + } + } + } + /** * 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 @@ -549,21 +595,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; } @@ -590,6 +636,7 @@ public void tidyUp() { } long blockAndDeathPoints = this.results.rawBlockCount.get(); + this.results.totalPoints.set(blockAndDeathPoints); if (this.addon.getSettings().getDeathPenalty() > 0) { @@ -622,7 +669,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!"); } @@ -637,7 +684,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 { @@ -678,14 +725,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(); }); 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/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/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/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/commands/IslandValueCommand.java b/src/main/java/world/bentobox/level/commands/IslandValueCommand.java index ffdb0a7..e6de8e4 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,133 @@ 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 static final String MATERIAL = "[material]"; 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 false; + } + + 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/config/ConfigSettings.java b/src/main/java/world/bentobox/level/config/ConfigSettings.java index 67f7b91..7be3364 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"; @@ -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; + } } 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; +} diff --git a/src/main/java/world/bentobox/level/objects/IslandLevels.java b/src/main/java/world/bentobox/level/objects/IslandLevels.java index 532b509..816376d 100644 --- a/src/main/java/world/bentobox/level/objects/IslandLevels.java +++ b/src/main/java/world/bentobox/level/objects/IslandLevels.java @@ -43,6 +43,17 @@ public class IslandLevels implements DataObject { */ @Expose private long pointsToNextLevel; + /** + * The maximum level this island has ever had + */ + @Expose + private long maxLevel; + + /** + * Total points + */ + @Expose + private long totalPoints; /** * Underwater count @@ -94,6 +105,10 @@ public long getLevel() { */ public void setLevel(long level) { this.level = level; + // Track maximum level + if (level > this.maxLevel) { + maxLevel = level; + } } /** @@ -124,6 +139,28 @@ public void setPointsToNextLevel(long pointsToNextLevel) { this.pointsToNextLevel = pointsToNextLevel; } + /** + * @return the totalPoints + */ + 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 + */ + public long getMaxLevel() { + return maxLevel; + } + /** * @return the uwCount */ 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..395e5ed --- /dev/null +++ b/src/main/java/world/bentobox/level/panels/DetailsPanel.java @@ -0,0 +1,804 @@ +package world.bentobox.level.panels; + + +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 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.database.objects.Island; +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.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/main/java/world/bentobox/level/panels/TopLevelPanel.java b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java new file mode 100644 index 0000000..e60d377 --- /dev/null +++ b/src/main/java/world/bentobox/level/panels/TopLevelPanel.java @@ -0,0 +1,547 @@ +/// +// Created by BONNe +// Copyright - 2021 +/// + +package world.bentobox.level.panels; + + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.event.inventory.ClickType; + +import world.bentobox.bentobox.api.addons.GameModeAddon; +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.database.objects.Island; +import world.bentobox.bentobox.managers.RanksManager; +import world.bentobox.level.Level; +import world.bentobox.level.util.Utils; + + +/** + * 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; + } + + 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); + + 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 -> + { + 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. + * + * @param builder the builder + * @param template the template + * @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)); + } + } + + + /** + * 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 = 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. + * + * @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) + { + // 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("(? 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/java/world/bentobox/level/panels/ValuePanel.java b/src/main/java/world/bentobox/level/panels/ValuePanel.java new file mode 100644 index 0000000..e97ca54 --- /dev/null +++ b/src/main/java/world/bentobox/level/panels/ValuePanel.java @@ -0,0 +1,779 @@ +package world.bentobox.level.panels; + + +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 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.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(rec -> + { + if (rec.material.name().toLowerCase().contains(text) || + Utils.prettifyObject(rec.material(), this.user).toLowerCase().contains(text)) + { + this.elementList.add(rec); + } + }); + } + 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.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.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", + TextVariables.NUMBER, String.valueOf(materialRecord.value())); + + String underWater; + + if (this.addon.getSettings().getUnderWaterMultiplier() > 1.0) + { + underWater = this.user.getTranslationOrNothing(reference + "underwater", + TextVariables.NUMBER, String.valueOf(materialRecord.value() * this.addon.getSettings().getUnderWaterMultiplier())); + } + else + { + underWater = ""; + } + + String limit = materialRecord.limit() > 0 ? this.user.getTranslationOrNothing(reference + "limit", + TextVariables.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("(? { + addon.log("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: Constants + // --------------------------------------------------------------------- + + private static final String BLOCK = "BLOCK"; + + // --------------------------------------------------------------------- + // 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..968f19e --- /dev/null +++ b/src/main/java/world/bentobox/level/util/ConversationUtils.java @@ -0,0 +1,126 @@ +// +// Created by BONNe +// Copyright - 2021 +// + + +package world.bentobox.level.util; + + +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 world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.user.User; + + +public class ConversationUtils +{ + // --------------------------------------------------------------------- + // 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 + * 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 new file mode 100644 index 0000000..1433666 --- /dev/null +++ b/src/main/java/world/bentobox/level/util/Utils.java @@ -0,0 +1,229 @@ +// +// Created by BONNe +// Copyright - 2021 +// + + +package world.bentobox.level.util; + + +import java.util.List; + +import org.bukkit.Material; +import org.bukkit.permissions.PermissionAttachmentInfo; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.hooks.LangUtilsHook; + + +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. + * @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)). + 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; + } + + + /** + * 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; + } + + + /** + * 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/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 # 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!" diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 6b75387..6456818 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -55,9 +55,149 @@ 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]" +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' + 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]" + 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 Number of blocks: &e [number]" + calculated: "&7 Calculated value: &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. + 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. + 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" + 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 + 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." + 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." + 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." + 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" + 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" - no-value: "&c That item has no value." \ No newline at end of file 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." diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index 3f906c0..f7d914b 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -1,37 +1,175 @@ --- admin: level: + parameters: "" description: calcule le niveau d'île d'un joueur - parameters: "" + 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: "" + 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]" - 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-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" +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: - - plagoutte + '0': plagoutte 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" diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index 8b13789..4347616 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -1 +1,55 @@ - +--- +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." 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 해당 항목에는 가치가 없습니다." 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" 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" 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 @@ - diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index 00f291d..c268f5b 100755 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -1,40 +1,163 @@ -########################################################################################### -# 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 列队中的岛屿:[number]" top: - description: "显示前十名" - unknown-world: "&c未知世界!" + 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计算等级中..." - 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秒才能再次使用" - + cooldown: "&c 还需等待 &b[time] &c秒才能再次使用该指令" + in-progress: "&6 岛级等级正在计算中..." + time-out: "&c 等级计算超时。请稍后再试" top: - description: "显示前十名" - gui-title: "&a前十" + description: 显示前十名 + gui-title: "&a 前十" gui-heading: "&6[name]: &B[rank]" - island-level: "&B等级 [level]" - warp-to: "&A正传送到 [name] 的岛屿" - - value: - description: "查看某方块的价值" - success: "&7本方块的价值: &e[value]" - success-underwater: "&7本方块的水下价值: &e[value]" - empty-hand: "&c你手里没有方块" - no-value: "&c这个东西一文不值." + 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指令查看方块报告" +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 你的手中没有拿着方块" diff --git a/src/main/resources/panels/detail_panel.yml b/src/main/resources/panels/detail_panel.yml new file mode 100644 index 0000000..927a788 --- /dev/null +++ b/src/main/resources/panels/detail_panel.yml @@ -0,0 +1,130 @@ +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: unknown + 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: unknown + 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: unknown + 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: 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 + 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 new file mode 100644 index 0000000..3b80784 --- /dev/null +++ b/src/main/resources/panels/top_panel.yml @@ -0,0 +1,195 @@ +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 + 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 + 3: + 4: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + 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 + 6: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + 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 + 4: + 2: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + 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 + 3: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + 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 + 4: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + 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 + 5: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + 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 + 6: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + 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 + 7: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + 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 + 8: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + 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 + 6: + 5: + #icon: PLAYER_HEAD + title: level.gui.buttons.island.name + description: level.gui.buttons.island.description + data: + type: VIEW + actions: + view: + click-type: unknown + tooltip: level.gui.tips.click-to-view \ No newline at end of file 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 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c5bd022..42542f9 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,9 +1,9 @@ -name: Pladdon +name: BentoBox-Level main: world.bentobox.level.LevelPladdon -version: ${version} -api-version: "1.17" -description: Level Addon -author: tastybento -depend: - - BentoBox +version: ${project.version}${build.number} +api-version: "1.19" +authors: [tastybento] +contributors: ["The BentoBoxWorld Community"] +website: https://bentobox.world +description: ${project.description} 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/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java index a8541e1..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 @@ -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)); } @@ -383,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"); } @@ -420,21 +429,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)} 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)); + + } + +} 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"); } }