From 08b7c99c3fbd29acfd9606e46985f35e13a2353f Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 21 Jun 2020 17:54:51 -0700 Subject: [PATCH] Complete rewrite to enable pipelining. Adds ability to scan chest contents. --- pom.xml | 2 +- src/main/java/world/bentobox/level/Level.java | 445 +++++---------- .../world/bentobox/level/LevelPresenter.java | 136 ----- .../world/bentobox/level/LevelsManager.java | 375 ++++++++++++ .../java/world/bentobox/level/TopTen.java | 199 ------- .../level/calculators/CalcIslandLevel.java | 534 ------------------ .../calculators/IslandLevelCalculator.java | 531 ++++++++++++++++- .../bentobox/level/calculators/Pipeliner.java | 100 ++++ .../level/calculators/PlayerLevel.java | 106 ---- .../bentobox/level/calculators/Results.java | 163 ++---- .../{admin => }/AdminLevelCommand.java | 30 +- .../commands/AdminLevelStatusCommand.java | 30 + .../commands/{admin => }/AdminTopCommand.java | 11 +- .../{admin => }/AdminTopRemoveCommand.java | 14 +- .../level/commands/IslandLevelCommand.java | 108 ++++ .../{island => }/IslandTopCommand.java | 10 +- .../{island => }/IslandValueCommand.java | 7 +- .../commands/island/IslandLevelCommand.java | 73 --- .../bentobox/level/config/ConfigSettings.java | 97 ++-- .../bentobox/level/event/TopTenClick.java | 51 -- .../IslandLevelCalculatedEvent.java | 18 +- .../IslandPreLevelEvent.java | 2 +- ...rs.java => IslandActivitiesListeners.java} | 72 +-- .../level/listeners/JoinLeaveListener.java | 11 +- .../bentobox/level/objects/LevelsData.java | 68 ++- .../bentobox/level/objects/TopTenData.java | 85 +-- .../level/requests/LevelRequestHandler.java | 2 +- .../level/requests/TopTenRequestHandler.java | 2 +- src/main/resources/addon.yml | 71 +-- src/main/resources/config.yml | 53 +- src/main/resources/locales/en-US.yml | 25 +- .../bentobox/level/LevelPresenterTest.java | 115 ---- .../java/world/bentobox/level/LevelTest.java | 106 +--- .../bentobox/level/LevelsManagerTest.java | 420 ++++++++++++++ .../java/world/bentobox/level/TopTenTest.java | 278 --------- .../admin/AdminTopRemoveCommandTest.java | 12 +- .../level/objects/TopTenDataTest.java | 124 ---- 37 files changed, 1987 insertions(+), 2499 deletions(-) delete mode 100644 src/main/java/world/bentobox/level/LevelPresenter.java create mode 100644 src/main/java/world/bentobox/level/LevelsManager.java delete mode 100644 src/main/java/world/bentobox/level/TopTen.java delete mode 100644 src/main/java/world/bentobox/level/calculators/CalcIslandLevel.java create mode 100644 src/main/java/world/bentobox/level/calculators/Pipeliner.java delete mode 100644 src/main/java/world/bentobox/level/calculators/PlayerLevel.java rename src/main/java/world/bentobox/level/commands/{admin => }/AdminLevelCommand.java (52%) create mode 100644 src/main/java/world/bentobox/level/commands/AdminLevelStatusCommand.java rename src/main/java/world/bentobox/level/commands/{admin => }/AdminTopCommand.java (78%) rename src/main/java/world/bentobox/level/commands/{admin => }/AdminTopRemoveCommand.java (76%) create mode 100644 src/main/java/world/bentobox/level/commands/IslandLevelCommand.java rename src/main/java/world/bentobox/level/commands/{island => }/IslandTopCommand.java (68%) rename src/main/java/world/bentobox/level/commands/{island => }/IslandValueCommand.java (97%) delete mode 100644 src/main/java/world/bentobox/level/commands/island/IslandLevelCommand.java delete mode 100644 src/main/java/world/bentobox/level/event/TopTenClick.java rename src/main/java/world/bentobox/level/{event => events}/IslandLevelCalculatedEvent.java (94%) rename src/main/java/world/bentobox/level/{event => events}/IslandPreLevelEvent.java (95%) rename src/main/java/world/bentobox/level/listeners/{IslandTeamListeners.java => IslandActivitiesListeners.java} (51%) delete mode 100644 src/test/java/world/bentobox/level/LevelPresenterTest.java create mode 100644 src/test/java/world/bentobox/level/LevelsManagerTest.java delete mode 100644 src/test/java/world/bentobox/level/TopTenTest.java delete mode 100644 src/test/java/world/bentobox/level/objects/TopTenDataTest.java diff --git a/pom.xml b/pom.xml index 6cffba9..497ad1e 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ -LOCAL - 2.1.0 + 2.2.0 diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 6fc0a62..bdd9a1a 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -2,151 +2,53 @@ package world.bentobox.level; import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; 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.user.User; -import world.bentobox.bentobox.database.Database; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.level.calculators.CalcIslandLevel; -import world.bentobox.level.commands.admin.AdminLevelCommand; -import world.bentobox.level.commands.admin.AdminTopCommand; -import world.bentobox.level.commands.island.IslandLevelCommand; -import world.bentobox.level.commands.island.IslandTopCommand; -import world.bentobox.level.commands.island.IslandValueCommand; +import world.bentobox.level.calculators.IslandLevelCalculator; +import world.bentobox.level.calculators.Pipeliner; +import world.bentobox.level.commands.AdminLevelCommand; +import world.bentobox.level.commands.AdminLevelStatusCommand; +import world.bentobox.level.commands.AdminTopCommand; +import world.bentobox.level.commands.IslandLevelCommand; +import world.bentobox.level.commands.IslandTopCommand; +import world.bentobox.level.commands.IslandValueCommand; import world.bentobox.level.config.BlockConfig; import world.bentobox.level.config.ConfigSettings; -import world.bentobox.level.listeners.IslandTeamListeners; +import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.JoinLeaveListener; -import world.bentobox.level.objects.LevelsData; import world.bentobox.level.requests.LevelRequestHandler; import world.bentobox.level.requests.TopTenRequestHandler; /** - * Addon to BSkyBlock/AcidIsland that enables island level scoring and top ten functionality * @author tastybento * */ public class Level extends Addon { - private static Addon addon; - // Settings private ConfigSettings settings; private Config configObject = new Config<>(this, ConfigSettings.class); - - /** - * @param settings the settings to set - */ - public void setSettings(ConfigSettings settings) { - this.settings = settings; - } - - // Database handler for level data - private Database handler; - - // A cache of island levels. - private Map levelsCache; - - // The Top Ten object - private TopTen topTen; - - // Level calculator - private LevelPresenter levelPresenter; - - - private BlockConfig blockConfig; - - /** - * Calculates a user's island - * @param world - the world where this island is - * @param user - the user who is asking, or null if none - * @param playerUUID - the target island member's UUID - */ - public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) { - levelPresenter.calculateIslandLevel(world, user, playerUUID); - } - - /** - * Get level from cache for a player. - * @param targetPlayer - target player UUID - * @return Level of player or zero if player is unknown or UUID is null - */ - public long getIslandLevel(World world, @Nullable UUID targetPlayer) { - if (targetPlayer == null) return 0L; - LevelsData ld = getLevelsData(targetPlayer); - return ld == null ? 0L : ld.getLevel(world); - } - - /** - * Load a player from the cache or database - * @param targetPlayer - UUID of target player - * @return LevelsData object or null if not found - */ - @Nullable - public LevelsData getLevelsData(@NonNull UUID targetPlayer) { - // Get from database if not in cache - if (!levelsCache.containsKey(targetPlayer) && handler.objectExists(targetPlayer.toString())) { - LevelsData ld = handler.loadObject(targetPlayer.toString()); - if (ld != null) { - levelsCache.put(targetPlayer, ld); - } else { - handler.deleteID(targetPlayer.toString()); - } - } - // Return cached value or null - return levelsCache.get(targetPlayer); - } - - /** - * @return the settings - */ - public ConfigSettings getSettings() { - return settings; - } - - public TopTen getTopTen() { - return topTen; - } - - /** - * @return the levelPresenter - */ - public LevelPresenter getLevelPresenter() { - return levelPresenter; - } - - @Override - public void onDisable(){ - // Save the cache - if (levelsCache != null) { - save(); - } - if (topTen != null) { - topTen.saveTopTen(); - } - } + private Pipeliner pipeliner; + private LevelsManager manager; @Override public void onLoad() { // Save the default config from config.yml saveDefaultConfig(); - // Load settings from config.yml. This will check if there are any issues with it too. if (loadSettings()) { + // Disable + logError("Level settings could not load! Addon disabled."); + setState(State.DISABLED); + } else { configObject.saveConfigObject(settings); } } @@ -154,41 +56,105 @@ public class Level extends Addon { private boolean loadSettings() { // Load settings again to get worlds settings = configObject.loadConfigObject(); - if (settings == null) { - // Disable - logError("Level settings could not load! Addon disabled."); - setState(State.DISABLED); - return false; - } - // Check for legacy blocks and limits etc. - if (getConfig().isConfigurationSection("blocks") - || getConfig().isConfigurationSection("limits") - || getConfig().isConfigurationSection("worlds")) { - logWarning("Converting old config.yml format - shifting blocks, limits and worlds to blockconfig.yml"); - File blockConfigFile = new File(this.getDataFolder(), "blockconfig.yml"); - if (blockConfigFile.exists()) { - logError("blockconfig.yml already exists! Saving config as blockconfig.yml.2"); - blockConfigFile = new File(this.getDataFolder(), "blockconfig.yml.2"); - } - YamlConfiguration blockConfig = new YamlConfiguration(); - copyConfigSection(blockConfig, "limits"); - copyConfigSection(blockConfig, "blocks"); - copyConfigSection(blockConfig, "worlds"); - try { - blockConfig.save(blockConfigFile); - } catch (IOException e) { - logError("Could not save! " + e.getMessage()); - } - } - return true; + return settings == null; } - private void copyConfigSection(YamlConfiguration blockConfig, String sectionName) { - if (!getConfig().isConfigurationSection(sectionName)) return; - ConfigurationSection section = getConfig().getConfigurationSection(sectionName); - for (String k:section.getKeys(true)) { - blockConfig.set(sectionName + "." + k, section.get(k)); + @Override + public void onEnable() { + loadBlockSettings(); + // Start pipeline + pipeliner = new Pipeliner(this); + // Start Manager + manager = new LevelsManager(this); + manager.loadTopTens(); + // Register listeners + this.registerListener(new IslandActivitiesListeners(this)); + this.registerListener(new JoinLeaveListener(this)); + // Register commands for GameModes + getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) + .forEach(gm -> { + log("Level hooking into " + gm.getDescription().getName()); + registerCommands(gm); + registerPlaceholders(gm); + }); + // Register request handlers + registerRequestHandler(new LevelRequestHandler(this)); + registerRequestHandler(new TopTenRequestHandler(this)); + + // Check if WildStackers is enabled on the server + // I only added support for counting blocks into the island level + // Someone else can PR if they want spawners added to the Leveling system :) + IslandLevelCalculator.stackersEnabled = Bukkit.getPluginManager().getPlugin("WildStacker") != null; + + } + + 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() + "_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)); + // Level + getPlugin().getPlaceholdersManager().registerPlaceholder(this, + gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank)); } + + } + + private String getRankName(World world, int rank) { + if (rank < 1) rank = 1; + if (rank > 10) rank = 10; + return getPlayers().getName(getManager().getTopTen(world, 10).keySet().stream().skip(rank - 1).limit(1).findFirst().orElse(null)); + } + + private String getRankLevel(World world, int rank) { + if (rank < 1) rank = 1; + if (rank > 10) rank = 10; + return getManager().formatLevel(getManager().getTopTen(world, 10).values().stream().skip(rank - 1).limit(1).findFirst().orElse(null)); + } + + private String getVisitedIslandLevel(GameModeAddon gm, User user) { + if (!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); + new AdminTopCommand(this, adminCommand); + new AdminLevelStatusCommand(this, adminCommand); + }); + gm.getPlayerCommand().ifPresent(playerCmd -> { + new IslandLevelCommand(this, playerCmd); + new IslandTopCommand(this, playerCmd); + new IslandValueCommand(this, playerCmd); + }); + } + + @Override + public void onDisable() { + if (manager != null) { + manager.save(); + } + } private void loadBlockSettings() { @@ -210,165 +176,6 @@ public class Level extends Addon { } - @Override - public void onEnable() { - addon = this; - loadBlockSettings(); - // Get the BSkyBlock database - // Set up the database handler to store and retrieve Island classes - // Note that these are saved by the BSkyBlock database - handler = new Database<>(this, LevelsData.class); - // Initialize the cache - levelsCache = new HashMap<>(); - // Load the calculator - levelPresenter = new LevelPresenter(this, this.getPlugin()); - // Start the top ten and register it for clicks - topTen = new TopTen(this); - registerListener(topTen); - // Register commands for GameModes - getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> settings - .getGameModes() - .contains(gm - .getDescription() - .getName())) - .forEach(gm -> { - log("Level hooking into " + gm.getDescription().getName()); - registerCommands(gm); - // Register placeholders - if (getPlugin().getPlaceholdersManager() != null) { - registerPlaceholders(gm); - } - }); - - // Register new island listener - registerListener(new IslandTeamListeners(this)); - registerListener(new JoinLeaveListener(this)); - - // Register request handlers - registerRequestHandler(new LevelRequestHandler(this)); - registerRequestHandler(new TopTenRequestHandler(this)); - - // Check if WildStackers is enabled on the server - // I only added support for counting blocks into the island level - // Someone else can PR if they want spawners added to the Leveling system :) - CalcIslandLevel.stackersEnabled = Bukkit.getPluginManager().getPlugin("WildStacker") != null; - - // Done - } - - private void registerPlaceholders(GameModeAddon gm) { - // Island Level - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_island_level", - user -> getLevelPresenter().getLevelString(getIslandLevel(gm.getOverWorld(), user.getUniqueId()))); - - // Visited Island Level - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_visited_island_level", user -> getVisitedIslandLevel(gm, user)); - - // Top Ten - for (int i = 1; i <= 10; i++) { - final int rank = i; - // Value - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_top_value_" + rank, u -> String.valueOf(getTopTen().getTopTenLevel(gm.getOverWorld(), rank))); - - // Name - getPlugin().getPlaceholdersManager().registerPlaceholder(this, - gm.getDescription().getName().toLowerCase() + "_top_name_" + rank, - u -> getPlayers().getName(getTopTen().getTopTenUUID(gm.getOverWorld(), rank))); - } - - } - - private String getVisitedIslandLevel(GameModeAddon gm, User user) { - if (!gm.inWorld(user.getLocation())) return ""; - return getIslands().getIslandAt(user.getLocation()) - .map(island -> getLevelPresenter().getLevelString(getIslandLevel(gm.getOverWorld(), island.getOwner()))) - .orElse("0"); - } - - private void registerCommands(GameModeAddon gm) { - gm.getAdminCommand().ifPresent(adminCommand -> { - new AdminLevelCommand(this, adminCommand); - new AdminTopCommand(this, adminCommand); - }); - gm.getPlayerCommand().ifPresent(playerCmd -> { - new IslandLevelCommand(this, playerCmd); - new IslandTopCommand(this, playerCmd); - new IslandValueCommand(this, playerCmd); - }); - } - - /** - * Save the levels to the database - */ - private void save(){ - // Remove any potential null values from the cache - levelsCache.values().removeIf(Objects::isNull); - levelsCache.values().forEach(handler::saveObjectAsync); - } - - /** - * Sets the player's level to a value - * @param world - world - * @param targetPlayer - target player - * @param level - level - */ - public void setIslandLevel(World world, UUID targetPlayer, long level) { - if (world == null || targetPlayer == null) { - this.logError("Level: request to store a null " + world + " " + targetPlayer); - return; - } - LevelsData ld = getLevelsData(targetPlayer); - if (ld == null) { - ld = new LevelsData(targetPlayer, level, world); - } else { - ld.setLevel(world, level); - } - // Add to cache - levelsCache.put(targetPlayer, ld); - topTen.addEntry(world, targetPlayer, getIslandLevel(world, targetPlayer)); - handler.saveObjectAsync(ld); - } - - /** - * Zeros the initial island level - * @param island - island - * @param level - initial calculated island level - */ - public void setInitialIslandLevel(@NonNull Island island, long level) { - if (island.getWorld() == null || island.getOwner() == null) { - this.logError("Level: request to store a null (initial) " + island.getWorld() + " " + island.getOwner()); - return; - } - setIslandLevel(island.getWorld(), island.getOwner(), 0L); - levelsCache.get(island.getOwner()).setInitialLevel(island.getWorld(), level); - } - - /** - * Get the initial island level - * @param island - island - * @return level or 0 by default - */ - public long getInitialIslandLevel(@NonNull Island island) { - // Remove any potential null values from the cache - levelsCache.values().removeIf(Objects::isNull); - return levelsCache.containsKey(island.getOwner()) ? levelsCache.get(island.getOwner()).getInitialLevel(island.getWorld()) : 0L; - } - - /** - * @return database handler - */ - @Nullable - public Database getHandler() { - return handler; - } - - public static Addon getInstance() { - return addon; - } /** * @return the blockConfig @@ -377,4 +184,34 @@ public class Level extends Addon { return blockConfig; } + /** + * @return the settings + */ + public ConfigSettings getSettings() { + return settings; + } + + /** + * @return the pipeliner + */ + public Pipeliner getPipeliner() { + return pipeliner; + } + + /** + * @return the manager + */ + public LevelsManager getManager() { + return manager; + } + + /** + * Set the config settings - used for tests only + * @param configSettings - config settings + */ + void setSettings(ConfigSettings configSettings) { + this.settings = configSettings; + + } + } diff --git a/src/main/java/world/bentobox/level/LevelPresenter.java b/src/main/java/world/bentobox/level/LevelPresenter.java deleted file mode 100644 index f375f66..0000000 --- a/src/main/java/world/bentobox/level/LevelPresenter.java +++ /dev/null @@ -1,136 +0,0 @@ -package world.bentobox.level; - -import java.math.BigInteger; -import java.text.DecimalFormat; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; -import java.util.UUID; - -import org.bukkit.World; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.level.calculators.PlayerLevel; - -public class LevelPresenter { - - private static final BigInteger THOUSAND = BigInteger.valueOf(1000); - private static final TreeMap LEVELS; - static { - LEVELS = new TreeMap<>(); - - LEVELS.put(THOUSAND, "k"); - LEVELS.put(THOUSAND.pow(2), "M"); - LEVELS.put(THOUSAND.pow(3), "G"); - LEVELS.put(THOUSAND.pow(4), "T"); - } - - private int levelWait; - private final Level addon; - private final BentoBox plugin; - - // Level calc cool down - private final HashMap levelWaitTime = new HashMap<>(); - - public LevelPresenter(Level addon, BentoBox plugin) { - this.addon = addon; - this.plugin = plugin; - } - - /** - * Calculates the island level - * @param world - world to check - * @param sender - asker of the level info - * @param targetPlayer - target player - */ - public void calculateIslandLevel(World world, final User sender, UUID targetPlayer) { - // Get permission prefix for this world - String permPrefix = plugin.getIWM().getPermissionPrefix(world); - // Check if target has island - boolean inTeam = false; - if (!plugin.getIslands().hasIsland(world, targetPlayer)) { - // Player may be in a team - if (plugin.getIslands().inTeam(world, targetPlayer)) { - targetPlayer = plugin.getIslands().getOwner(world, targetPlayer); - inTeam = true; - } else { - if (sender != null) sender.sendMessage("general.errors.player-has-no-island"); - return; - } - } - // Player asking for their own island calc - if (sender == null || inTeam || !sender.isPlayer() || sender.getUniqueId().equals(targetPlayer) || sender.isOp() || sender.hasPermission(permPrefix + "mod.info")) { - // Newer better system - uses chunks - if (sender == null || !onLevelWaitTime(sender) || levelWait <= 0 || sender.isOp() || sender.hasPermission(permPrefix + "mod.info")) { - if (sender != null) { - sender.sendMessage("island.level.calculating"); - setLevelWaitTime(sender); - } - new PlayerLevel(addon, plugin.getIslands().getIsland(world, targetPlayer), targetPlayer, sender); - } else { - // Cooldown - sender.sendMessage("island.level.cooldown", "[time]", String.valueOf(getLevelWaitTime(sender))); - } - - } else { - long lvl = addon.getIslandLevel(world, targetPlayer); - - sender.sendMessage("island.level.island-level-is","[level]", getLevelString(lvl)); - } - } - - /** - * Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k - * @param lvl - long value to represent - * @return string of the level. - */ - public String getLevelString(long lvl) { - String level = String.valueOf(lvl); - // Asking for the level of another player - if(addon.getSettings().isShorthand()) { - BigInteger levelValue = BigInteger.valueOf(lvl); - - Map.Entry stage = LEVELS.floorEntry(levelValue); - - if (stage != null) { // level > 1000 - // 1 052 -> 1.0k - // 1 527 314 -> 1.5M - // 3 874 130 021 -> 3.8G - // 4 002 317 889 -> 4.0T - level = new DecimalFormat("#.#").format(levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue()/1000.0) + stage.getValue(); - } - } - return level; - } - - /** - * Sets cool down for the level command - * - * @param user - user - */ - private void setLevelWaitTime(final User user) { - levelWaitTime.put(user.getUniqueId(), Calendar.getInstance().getTimeInMillis() + levelWait * 1000); - } - - private boolean onLevelWaitTime(final User sender) { - if (levelWaitTime.containsKey(sender.getUniqueId())) { - return levelWaitTime.get(sender.getUniqueId()) > Calendar.getInstance().getTimeInMillis(); - } - - return false; - } - - private long getLevelWaitTime(final User sender) { - if (levelWaitTime.containsKey(sender.getUniqueId())) { - if (levelWaitTime.get(sender.getUniqueId()) > Calendar.getInstance().getTimeInMillis()) { - return (levelWaitTime.get(sender.getUniqueId()) - Calendar.getInstance().getTimeInMillis()) / 1000; - } - - return 0L; - } - - return 0L; - } -} diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java new file mode 100644 index 0000000..e39e7ff --- /dev/null +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -0,0 +1,375 @@ +package world.bentobox.level; + +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.TreeMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.events.addon.AddonEvent; +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.user.User; +import world.bentobox.bentobox.database.Database; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.level.calculators.Results; +import world.bentobox.level.events.IslandLevelCalculatedEvent; +import world.bentobox.level.events.IslandPreLevelEvent; +import world.bentobox.level.objects.LevelsData; +import world.bentobox.level.objects.TopTenData; + +public class LevelsManager { + private static final String INTOPTEN = "intopten"; + private static final TreeMap LEVELS; + private static final BigInteger THOUSAND = BigInteger.valueOf(1000); + static { + LEVELS = new TreeMap<>(); + + LEVELS.put(THOUSAND, "k"); + LEVELS.put(THOUSAND.pow(2), "M"); + LEVELS.put(THOUSAND.pow(3), "G"); + LEVELS.put(THOUSAND.pow(4), "T"); + } + private Level addon; + + + // Database handler for level data + private final Database handler; + // A cache of island levels. + private final Map levelsCache; + + private final int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25}; + private final Database topTenHandler; + // Top ten lists + private Map topTenLists; + + + public LevelsManager(Level addon) { + this.addon = addon; + // Get the BentoBox database + // Set up the database handler to store and retrieve data + // Note that these are saved by the BentoBox database + handler = new Database<>(addon, LevelsData.class); + // Top Ten handler + topTenHandler = new Database<>(addon, TopTenData.class); + // Initialize the cache + levelsCache = new HashMap<>(); + // Initialize top ten lists + topTenLists = new HashMap<>(); + } + + private void addToTopTen(@NonNull World world, @NonNull UUID targetPlayer, long lv) { + topTenLists.computeIfAbsent(world, k -> new TopTenData(world)); + if (hasTopTenPerm(world, targetPlayer)) { + topTenLists.get(world).getTopTen().put(targetPlayer, lv); + } else { + topTenLists.get(world).getTopTen().remove(targetPlayer); + } + + } + + /** + * Calculate the island level, set all island member's levels to the result and try to add the owner to the top ten + * @param targetPlayer - uuid of targeted player - owner or team member + * @param island - island to calculate + * @return completable future with the results of the calculation + */ + public CompletableFuture calculateLevel(UUID targetPlayer, Island island) { + CompletableFuture result = new CompletableFuture<>(); + // Fire pre-level calc event + IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island); + Bukkit.getPluginManager().callEvent(e); + if (e.isCancelled()) { + return CompletableFuture.completedFuture(null); + } + // Add island to the pipeline + addon.getPipeliner().addIsland(island).thenAccept(r -> { + // Results are irrelevant because the island is unowned or deleted, or IslandLevelCalcEvent is cancelled + if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) { + result.complete(null); + } + // Save result + island.getMemberSet().forEach(uuid -> setIslandLevel(island.getWorld(), uuid, r.getLevel())); + addToTopTen(island.getWorld(), island.getOwner(), r.getLevel()); + result.complete(r); + }); + return result; + } + + private boolean fireIslandLevelCalcEvent(UUID targetPlayer, Island island, Results results) { + // Fire post calculation event + IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results); + Bukkit.getPluginManager().callEvent(ilce); + // This exposes these values to plugins via the event + Map keyValues = new HashMap<>(); + keyValues.put("eventName", "IslandLevelCalculatedEvent"); + keyValues.put("targetPlayer", targetPlayer); + keyValues.put("islandUUID", island.getUniqueId()); + keyValues.put("level", results.getLevel()); + keyValues.put("pointsToNextLevel", results.getPointsToNextLevel()); + keyValues.put("deathHandicap", results.getDeathHandicap()); + keyValues.put("initialLevel", results.getInitialLevel()); + new AddonEvent().builder().addon(addon).keyValues(keyValues).build(); + results = ilce.getResults(); + return ilce.isCancelled(); + } + + /** + * Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k + * @param lvl - long value to represent + * @return string of the level. + */ + public String formatLevel(long lvl) { + String level = String.valueOf(lvl); + // Asking for the level of another player + if(addon.getSettings().isShorthand()) { + BigInteger levelValue = BigInteger.valueOf(lvl); + + Map.Entry stage = LEVELS.floorEntry(levelValue); + + if (stage != null) { // level > 1000 + // 1 052 -> 1.0k + // 1 527 314 -> 1.5M + // 3 874 130 021 -> 3.8G + // 4 002 317 889 -> 4.0T + level = new DecimalFormat("#.#").format(levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue()/1000.0) + stage.getValue(); + } + } + return level; + } + + /** + * 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, 10); + + PanelBuilder panel = new PanelBuilder() + .name(user.getTranslation("island.top.gui-title")) + .user(user); + int i = 0; + for (Entry m : topTen.entrySet()) { + panel.item(SLOTS[i], getHead(i + 1, m.getValue(), m.getKey(), user, world)); + } + panel.build(); + } + + /** + * 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<>(); + 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 + * @return initial level of island + */ + public long getInitialLevel(Island island) { + @Nullable + LevelsData ld = getLevelsData(island.getOwner()); + return ld == null ? 0 : ld.getInitialLevel(island.getWorld()); + } + + /** + * Get level from cache for a player. + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return Level of player or zero if player is unknown or UUID is null + */ + public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) { + if (targetPlayer == null) return 0L; + LevelsData ld = getLevelsData(targetPlayer); + return ld == null ? 0L : ld.getLevel(world); + } + + /** + * Returns a formatted string of the target player's island level + * @param world - world where the island is + * @param targetPlayer - target player's UUID + * @return Formatted level of player or zero if player is unknown or UUID is null + */ + public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { + return formatLevel(getIslandLevel(world, targetPlayer)); + } + + /** + * Load a player from the cache or database + * @param targetPlayer - UUID of target player + * @return LevelsData object or null if not found + */ + @Nullable + public LevelsData getLevelsData(@NonNull UUID targetPlayer) { + // Get from database if not in cache + if (!levelsCache.containsKey(targetPlayer) && handler.objectExists(targetPlayer.toString())) { + LevelsData ld = handler.loadObject(targetPlayer.toString()); + if (ld != null) { + levelsCache.put(targetPlayer, ld); + } else { + handler.deleteID(targetPlayer.toString()); + } + } + // Return cached value or null + return levelsCache.get(targetPlayer); + } + + /** + * Get the number of points required until the next level since the last level calc + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return string with the number required or blank if the player is unknown + */ + public String getPointsToNextString(@NonNull World world, @Nullable UUID targetPlayer) { + if (targetPlayer == null) return ""; + LevelsData ld = getLevelsData(targetPlayer); + return ld == null ? "" : String.valueOf(ld.getPointsToNextLevel(world)); + } + + /** + * Get the top ten for this world. Returns offline players or players with the intopten permission. + * @param world - world requested + * @param size - size of the top ten + * @return sorted top ten map + */ + public Map getTopTen(World world, int size) { + topTenLists.computeIfAbsent(world, k -> new TopTenData(k)); + // Remove player from top ten if they are online and do not have the perm + topTenLists.get(world).getTopTen().keySet().removeIf(u -> !hasTopTenPerm(world, u)); + // Return the sorted map + return Collections.unmodifiableMap(topTenLists.get(world).getTopTen().entrySet().stream() + .filter(l -> l.getValue() > 0) + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(size) + .collect(Collectors.toMap( + Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new))); + } + + /** + * Checks if player has the correct top ten perm to have their level saved + * @param world + * @param targetPlayer + * @return + */ + boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) { + String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world); + boolean hasPerm = Bukkit.getPlayer(targetPlayer) != null && Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN); + return hasPerm; + } + + /** + * Loads all the top tens from the database + */ + void loadTopTens() { + topTenLists = new HashMap<>(); + topTenHandler.loadObjects().forEach(tt -> { + World world = Bukkit.getWorld(tt.getUniqueId()); + if (world != null) { + topTenLists.put(world, tt); + addon.log("Loaded TopTen for " + world.getName()); + // Update based on user data + for (UUID uuid : tt.getTopTen().keySet()) { + tt.getTopTen().compute(uuid, (k,v) -> v = updateLevel(k, world)); + } + } else { + addon.logError("TopTen world '" + tt.getUniqueId() + "' is not known on server. You might want to delete this table. Skipping..."); + } + }); + } + + /** + * Removes a player from a world's top ten and removes world from player's level data + * @param world - world + * @param uuid - the player's uuid + */ + public void removeEntry(World world, UUID uuid) { + if (levelsCache.containsKey(uuid)) { + levelsCache.get(uuid).remove(world); + // Save + handler.saveObjectAsync(levelsCache.get(uuid)); + } + if (topTenLists.containsKey(world)) { + topTenLists.get(world).getTopTen().remove(uuid); + topTenHandler.saveObjectAsync(topTenLists.get(world)); + } + + } + + /** + * Saves all player data and the top ten + */ + public void save() { + levelsCache.values().forEach(handler::saveObjectAsync); + topTenLists.values().forEach(topTenHandler::saveObjectAsync); + } + + /** + * Set an initial island level for player + * @param island - the island to set. Must have a non-null world and owner + * @param lv - initial island level + */ + public void setInitialIslandLevel(@NonNull Island island, long lv) { + if (island.getOwner() == null || island.getWorld() == null) return; + levelsCache.computeIfAbsent(island.getOwner(), k -> new LevelsData(k)).setInitialLevel(island.getWorld(), lv); + handler.saveObjectAsync(levelsCache.get(island.getOwner())); + } + + public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) { + levelsCache.computeIfAbsent(targetPlayer, k -> new LevelsData(targetPlayer)).setLevel(world, lv); + handler.saveObjectAsync(levelsCache.get(targetPlayer)); + // Add to Top Ten + addToTopTen(world, targetPlayer, lv); + } + + private Long updateLevel(UUID uuid, World world) { + if (handler.objectExists(uuid.toString())) { + @Nullable + LevelsData ld = handler.loadObject(uuid.toString()); + if (ld != null) { + return ld.getLevel(world); + } + } + return 0L; + } + + + +} diff --git a/src/main/java/world/bentobox/level/TopTen.java b/src/main/java/world/bentobox/level/TopTen.java deleted file mode 100644 index f45072e..0000000 --- a/src/main/java/world/bentobox/level/TopTen.java +++ /dev/null @@ -1,199 +0,0 @@ -package world.bentobox.level; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.Listener; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; - -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.user.User; -import world.bentobox.bentobox.database.Database; -import world.bentobox.level.objects.TopTenData; - -/** - * Handles all Top Ten List functions - * - * @author tastybento - * - */ -public class TopTen implements Listener { - private final Level addon; - // Top ten list of players - private Map topTenList; - private final int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25}; - private final Database handler; - - public TopTen(Level addon) { - this.addon = addon; - // Set up the database handler to store and retrieve the TopTenList class - // Note that these are saved in the BSkyBlock database - handler = new Database<>(addon, TopTenData.class); - loadTopTen(); - } - - /** - * Loads all the top tens from the database - */ - private void loadTopTen() { - topTenList = new HashMap<>(); - handler.loadObjects().forEach(tt -> { - World world = Bukkit.getWorld(tt.getUniqueId()); - if (world != null) { - topTenList.put(world, tt); - addon.log("Loaded TopTen for " + world.getName()); - } else { - addon.logError("TopTen world " + tt.getUniqueId() + " is not known on server. Skipping..."); - } - }); - } - - /** - * Adds a player to the top ten, if the level is good enough - * - * @param ownerUUID - owner UUID - * @param l - level - */ - public void addEntry(World world, UUID ownerUUID, long l) { - // Check if player is an island owner or not - if (!addon.getIslands().isOwner(world, ownerUUID)) { - return; - } - // Set up world data - topTenList.putIfAbsent(world, new TopTenData()); - topTenList.get(world).setUniqueId(world.getName()); - - // Try and see if the player is online - Player player = Bukkit.getServer().getPlayer(ownerUUID); - if (player != null && !player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(world) + "intopten")) { - topTenList.get(world).remove(ownerUUID); - return; - } - topTenList.get(world).addLevel(ownerUUID, l); - } - - /** - * Displays the Top Ten list - * @param world - world - * @param user - the requesting player - */ - public void getGUI(World world, final User user, String permPrefix) { - // Check world - topTenList.putIfAbsent(world, new TopTenData()); - topTenList.get(world).setUniqueId(world.getName()); - - PanelBuilder panel = new PanelBuilder() - .name(user.getTranslation("island.top.gui-title")) - .user(user); - - int i = 1; - Iterator> it = topTenList.get(world).getTopTen().entrySet().iterator(); - while (it.hasNext()) { - Map.Entry m = it.next(); - UUID topTenUUID = m.getKey(); - // Remove from TopTen if the player is online and has the permission - Player entry = Bukkit.getServer().getPlayer(topTenUUID); - boolean show = true; - if (entry != null) { - if (!entry.hasPermission(permPrefix + "intopten")) { - it.remove(); - show = false; - // Remove from Top Ten completely - topTenList.get(world).remove(topTenUUID); - } - } - if (show) { - panel.item(SLOTS[i-1], getHead(i, m.getValue(), topTenUUID, user, world)); - if (i++ == 10) break; - } - } - panel.build(); - } - - /** - * 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<>(); - description.add(asker.getTranslation("island.top.gui-heading", "[name]", name, "[rank]", String.valueOf(rank))); - description.add(asker.getTranslation("island.top.island-level","[level]", addon.getLevelPresenter().getLevelString(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 top ten list for this world - * @param world - world - * @return top ten data object - */ - @NonNull - public TopTenData getTopTenList(World world) { - return topTenList.computeIfAbsent(world, k -> new TopTenData()); - } - - /** - * Get the UUID for this rank in this world - * @param world - world - * @param rank - rank between 1 and 10 - * @return UUID or null - */ - @Nullable - public UUID getTopTenUUID(World world, int rank) { - return getTopTenList(world).getTopTenUUID(rank); - } - - /** - * Get the island level for this rank in this world - * @param world - world - * @param rank - rank between 1 and 10 - * @return level or 0 - */ - public long getTopTenLevel(World world, int rank) { - return getTopTenList(world).getTopTenLevel(rank); - } - - /** - * Removes ownerUUID from the top ten list - * - * @param ownerUUID - uuid to remove - */ - public void removeEntry(World world, UUID ownerUUID) { - topTenList.putIfAbsent(world, new TopTenData()); - topTenList.get(world).setUniqueId(world.getName()); - topTenList.get(world).remove(ownerUUID); - } - - public void saveTopTen() { - topTenList.values().forEach(handler::saveObjectAsync); - } - -} diff --git a/src/main/java/world/bentobox/level/calculators/CalcIslandLevel.java b/src/main/java/world/bentobox/level/calculators/CalcIslandLevel.java deleted file mode 100644 index c38ba7a..0000000 --- a/src/main/java/world/bentobox/level/calculators/CalcIslandLevel.java +++ /dev/null @@ -1,534 +0,0 @@ -package world.bentobox.level.calculators; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -import com.bgsoftware.wildstacker.api.WildStackerAPI; -import com.bgsoftware.wildstacker.api.objects.StackedBarrel; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.ChunkSnapshot; -import org.bukkit.Material; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.type.Slab; - -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Multiset; -import com.google.common.collect.Multiset.Entry; -import com.google.common.collect.Multisets; - -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.util.Pair; -import world.bentobox.bentobox.util.Util; -import world.bentobox.level.Level; - - -public class CalcIslandLevel { - - private static final String LINE_BREAK = "=================================="; - - public static final long MAX_AMOUNT = 10000; - public static Boolean stackersEnabled; - - private final Level addon; - - private final Set> chunksToScan; - private final Island island; - private final Results result; - private final Runnable onExit; - - // Copy the limits hash map - private final HashMap limitCount; - private final List worlds; - private final World world; - - private AtomicInteger count; - - private int total; - private Queue q; - private int queueid; - - /** - * Calculate the island's level - * Results are available in {@link CalcIslandLevel.Results} - * @param addon - Level addon - * @param island - island to be calculated - * @param onExit - what to run when done - */ - public CalcIslandLevel(final Level addon, final Island island, final Runnable onExit) { - this.addon = addon; - this.island = island; - this.world = island.getWorld(); - this.limitCount = new HashMap<>(addon.getBlockConfig().getBlockLimits()); - this.onExit = onExit; - this.worlds = new ArrayList<>(); - this.worlds.add(world); - q = new LinkedList<>(); - - // Results go here - result = new Results(); - - // Set the initial island handicap - result.setInitialLevel(addon.getInitialIslandLevel(island)); - - // Get chunks to scan - chunksToScan = getChunksToScan(island); - count = new AtomicInteger(); - // Total number of chunks to scan - total = chunksToScan.size(); - // Add nether world scanning - if (addon.getSettings().isNether()) { - World netherWorld = addon.getPlugin().getIWM().getNetherWorld(world); - if (netherWorld != null) { - this.worlds.add(netherWorld); - total += chunksToScan.size(); - } - } - // Add End world scanning - if (addon.getSettings().isEnd()) { - World endWorld = addon.getPlugin().getIWM().getEndWorld(world); - if (endWorld != null) { - this.worlds.add(endWorld); - total += chunksToScan.size(); - } - } - queueid = Bukkit.getScheduler().scheduleSyncRepeatingTask(addon.getPlugin(), () -> { - for (int i = 0; i < addon.getSettings().getChunks(); i++) { - if (q.size() == 0) { - return; - } - Chunk c = q.remove(); - getChunk(c); - } - }, addon.getSettings().getTaskDelay(), addon.getSettings().getTaskDelay()); - chunksToScan.forEach(c -> worlds.forEach(w -> Util.getChunkAtAsync(w, c.x, c.z).thenAccept(this::addChunkQueue))); - - } - - private void addChunkQueue(Chunk ch) { - q.add(ch); - } - - private void getChunk(Chunk ch) { - ChunkSnapshot snapShot = ch.getChunkSnapshot(); - - Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> { - this.scanChunk(snapShot, ch); - count.getAndIncrement(); - if (count.get() == total) { - Bukkit.getScheduler().cancelTask(queueid); - this.tidyUp(); - } - }); - } - - private void scanChunk(ChunkSnapshot chunk, Chunk physicalChunk) { - World chunkWorld = Bukkit.getWorld(chunk.getWorldName()); - if (chunkWorld == null) return; - int maxHeight = chunkWorld.getMaxHeight(); - - 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 (chunk.getX() * 16 + x < island.getMinProtectedX() || chunk.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 (chunk.getZ() * 16 + z < island.getMinProtectedZ() || chunk.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) { - continue; - } - - for (int y = 0; y < maxHeight; y++) { - BlockData blockData = chunk.getBlockData(x, y, z); - int seaHeight = addon.getPlugin().getIWM().getSeaHeight(chunkWorld); - 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) - if (stackersEnabled && blockData.getMaterial() == Material.CAULDRON) { - Block cauldronBlock = physicalChunk.getBlock(x, y, z); - if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) { - StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock); - int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock); - for (int _x = 0; _x < barrelAmt; _x++) { - checkBlock(barrel.getType(), belowSeaLevel); - } - } - } - - checkBlock(blockData.getMaterial(), belowSeaLevel); - } - } - } - } - - // Didnt see a reason to pass BlockData when all that's used was the material - private void checkBlock(Material mat, boolean belowSeaLevel) { - int count = limitCount(mat); - if (belowSeaLevel) { - result.underWaterBlockCount.addAndGet(count); - result.uwCount.add(mat); - } else { - result.rawBlockCount.addAndGet(count); - result.mdCount.add(mat); - } - } - - /** - * Checks if a block has been limited or not and whether a block has any value or not - * @param md Material - * @return value of the block if can be counted - */ - private int limitCount(Material md) { - if (limitCount.containsKey(md)) { - int count = limitCount.get(md); - if (count > 0) { - limitCount.put(md, --count); - return getValue(md); - } else { - result.ofCount.add(md); - return 0; - } - } - return getValue(md); - } - - /** - * Get value of a material - * World blocks trump regular block values - * @param md - Material to check - * @return value of a material - */ - private int getValue(Material md) { - Integer value = addon.getBlockConfig().getValue(world, md); - if (value == null) { - // Not in config - result.ncCount.add(md); - return 0; - } - return value; - } - - /** - * Get a set of all the chunks in island - * @param island - island - * @return - set of pairs of x,z coordinates to check - */ - private Set> getChunksToScan(Island island) { - Set> chunkSnapshot = new HashSet<>(); - for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 + 16); x += 16) { - for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 + 16); z += 16) { - chunkSnapshot.add(new Pair<>(x >> 4, z >> 4)); - } - } - return chunkSnapshot; - } - - private void tidyUp() { - // Finalize calculations - result.rawBlockCount.addAndGet((long)(result.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier())); - - // Set the death penalty - if (this.addon.getSettings().isSumTeamDeaths()) - { - for (UUID uuid : this.island.getMemberSet()) - { - this.result.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(this.world, uuid)); - } - } - else - { - // At this point, it may be that the island has become unowned. - this.result.deathHandicap.set(this.island.getOwner() == null ? 0 : - this.addon.getPlayers().getDeaths(this.world, this.island.getOwner())); - } - - long blockAndDeathPoints = this.result.rawBlockCount.get(); - - if (this.addon.getSettings().getDeathPenalty() > 0) - { - // Proper death penalty calculation. - blockAndDeathPoints -= this.result.deathHandicap.get() * this.addon.getSettings().getDeathPenalty(); - } - this.result.level.set(calculateLevel(blockAndDeathPoints)); - - // Calculate how many points are required to get to the next level - long nextLevel = this.result.level.get(); - long blocks = blockAndDeathPoints; - while (nextLevel < this.result.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) { - nextLevel = calculateLevel(++blocks); - } - this.result.pointsToNextLevel.set(blocks - blockAndDeathPoints); - - // Report - result.report = getReport(); - // All done. - if (onExit != null) { - Bukkit.getScheduler().runTask(addon.getPlugin(), onExit); - } - } - - - private long calculateLevel(long blockAndDeathPoints) { - String calcString = addon.getSettings().getLevelCalc(); - String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", String.valueOf(this.addon.getSettings().getLevelCost())); - return (long)eval(withValues) - this.island.getLevelHandicap() - result.initialLevel.get(); - } - - private List getReport() { - List reportLines = new ArrayList<>(); - // provide counts - reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld()) + " at " + Util.xyz(island.getCenter().toVector())); - reportLines.add("Island owner UUID = " + island.getOwner()); - reportLines.add("Total block value count = " + String.format("%,d",result.rawBlockCount.get())); - reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc()); - reportLines.add("Level cost = " + addon.getSettings().getLevelCost()); - reportLines.add("Deaths handicap = " + result.deathHandicap.get()); - reportLines.add("Initial island level = " + (0L - result.initialLevel.get())); - reportLines.add("Level calculated = " + result.level.get()); - reportLines.add(LINE_BREAK); - int total = 0; - if (!result.uwCount.isEmpty()) { - reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier() + ") value"); - reportLines.add("Total number of underwater blocks = " + String.format("%,d",result.uwCount.size())); - reportLines.addAll(sortedReport(total, result.uwCount)); - } - reportLines.add("Regular block count"); - reportLines.add("Total number of blocks = " + String.format("%,d",result.mdCount.size())); - reportLines.addAll(sortedReport(total, result.mdCount)); - - reportLines.add("Blocks not counted because they exceeded limits: " + String.format("%,d",result.ofCount.size())); - Iterable> entriesSortedByCount = result.ofCount.entrySet(); - Iterator> it = entriesSortedByCount.iterator(); - while (it.hasNext()) { - Entry type = it.next(); - Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement()); - String explain = ")"; - if (limit == null) { - Material generic = type.getElement(); - limit = addon.getBlockConfig().getBlockLimits().get(generic); - explain = " - All types)"; - } - reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks (max " + limit + explain); - } - reportLines.add(LINE_BREAK); - reportLines.add("Blocks on island that are not in config.yml"); - reportLines.add("Total number = " + String.format("%,d",result.ncCount.size())); - entriesSortedByCount = result.ncCount.entrySet(); - it = entriesSortedByCount.iterator(); - while (it.hasNext()) { - Entry type = it.next(); - reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks"); - } - reportLines.add(LINE_BREAK); - - return reportLines; - } - - private Collection sortedReport(int total, Multiset MaterialCount) { - Collection r = new ArrayList<>(); - Iterable> entriesSortedByCount = Multisets.copyHighestCountFirst(MaterialCount).entrySet(); - for (Entry en : entriesSortedByCount) { - Material type = en.getElement(); - - int value = getValue(type); - - r.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; - } - - /** - * @return the result - */ - public Results getResult() { - return result; - } - - /** - * Results class - * - */ - public class Results { - private List report; - private final Multiset mdCount = HashMultiset.create(); - private final Multiset uwCount = HashMultiset.create(); - private final Multiset ncCount = HashMultiset.create(); - private final Multiset ofCount = HashMultiset.create(); - // AtomicLong and AtomicInteger must be used because they are changed by multiple concurrent threads - private AtomicLong rawBlockCount = new AtomicLong(0); - private AtomicLong underWaterBlockCount = new AtomicLong(0); - private AtomicLong level = new AtomicLong(0); - private AtomicInteger deathHandicap = new AtomicInteger(0); - private AtomicLong pointsToNextLevel = new AtomicLong(0); - private AtomicLong initialLevel = new AtomicLong(0); - - /** - * @return the deathHandicap - */ - public int getDeathHandicap() { - return deathHandicap.get(); - } - - /** - * @return the report - */ - public List getReport() { - return report; - } - /** - * Set level - * @param level - level - */ - public void setLevel(long level) { - this.level.set(level); - } - /** - * @return the level - */ - public long getLevel() { - return level.get(); - } - /** - * @return the pointsToNextLevel - */ - public long getPointsToNextLevel() { - return pointsToNextLevel.get(); - } - - public long getInitialLevel() { - return initialLevel.get(); - } - - public void setInitialLevel(long initialLevel) { - this.initialLevel.set(initialLevel); - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - 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 + "]"; - } - - } - - private static double eval(final String str) { - return new Object() { - int pos = -1, ch; - - void nextChar() { - ch = (++pos < str.length()) ? str.charAt(pos) : -1; - } - - boolean eat(int charToEat) { - while (ch == ' ') nextChar(); - if (ch == charToEat) { - nextChar(); - return true; - } - return false; - } - - double parse() { - nextChar(); - double x = parseExpression(); - if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch); - return x; - } - - // Grammar: - // expression = term | expression `+` term | expression `-` term - // term = factor | term `*` factor | term `/` factor - // factor = `+` factor | `-` factor | `(` expression `)` - // | number | functionName factor | factor `^` factor - - double parseExpression() { - double x = parseTerm(); - for (;;) { - if (eat('+')) x += parseTerm(); // addition - else if (eat('-')) x -= parseTerm(); // subtraction - else return x; - } - } - - double parseTerm() { - double x = parseFactor(); - for (;;) { - if (eat('*')) x *= parseFactor(); // multiplication - else if (eat('/')) x /= parseFactor(); // division - else return x; - } - } - - double parseFactor() { - if (eat('+')) return parseFactor(); // unary plus - if (eat('-')) return -parseFactor(); // unary minus - - double x; - int startPos = this.pos; - if (eat('(')) { // parentheses - x = parseExpression(); - eat(')'); - } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers - while ((ch >= '0' && ch <= '9') || ch == '.') nextChar(); - x = Double.parseDouble(str.substring(startPos, this.pos)); - } else if (ch >= 'a' && ch <= 'z') { // functions - while (ch >= 'a' && ch <= 'z') nextChar(); - String func = str.substring(startPos, this.pos); - x = parseFactor(); - switch (func) { - case "sqrt": - x = Math.sqrt(x); - break; - case "sin": - x = Math.sin(Math.toRadians(x)); - break; - case "cos": - x = Math.cos(Math.toRadians(x)); - break; - case "tan": - x = Math.tan(Math.toRadians(x)); - break; - default: - throw new RuntimeException("Unknown function: " + func); - } - } else { - throw new RuntimeException("Unexpected: " + (char)ch); - } - - if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation - - return x; - } - }.parse(); - } -} diff --git a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java index 5293f3b..f0a4ea8 100644 --- a/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java +++ b/src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java @@ -1,10 +1,535 @@ package world.bentobox.level.calculators; -public interface IslandLevelCalculator { +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Material; +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.data.BlockData; +import org.bukkit.block.data.type.Slab; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; + +import com.bgsoftware.wildstacker.api.WildStackerAPI; +import com.bgsoftware.wildstacker.api.objects.StackedBarrel; +import com.google.common.collect.Multiset; +import com.google.common.collect.Multiset.Entry; +import com.google.common.collect.Multisets; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Pair; +import world.bentobox.bentobox.util.Util; +import world.bentobox.level.Level; + +public class IslandLevelCalculator { + private static final String LINE_BREAK = "=================================="; + public static final long MAX_AMOUNT = 10000; + public static Boolean stackersEnabled; /** - * @return the results of the island calculation + * Method to evaluate a mathematical equation + * @param str - equation to evaluate + * @return value of equation */ - Results getResult(); + private static double eval(final String str) { + return new Object() { + int pos = -1, ch; + boolean eat(int charToEat) { + while (ch == ' ') nextChar(); + if (ch == charToEat) { + nextChar(); + return true; + } + return false; + } + + void nextChar() { + ch = (++pos < str.length()) ? str.charAt(pos) : -1; + } + + double parse() { + nextChar(); + double x = parseExpression(); + if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch); + return x; + } + + // Grammar: + // expression = term | expression `+` term | expression `-` term + // term = factor | term `*` factor | term `/` factor + // factor = `+` factor | `-` factor | `(` expression `)` + // | number | functionName factor | factor `^` factor + + double parseExpression() { + double x = parseTerm(); + for (;;) { + if (eat('+')) x += parseTerm(); // addition + else if (eat('-')) x -= parseTerm(); // subtraction + else return x; + } + } + + double parseFactor() { + if (eat('+')) return parseFactor(); // unary plus + if (eat('-')) return -parseFactor(); // unary minus + + double x; + int startPos = this.pos; + if (eat('(')) { // parentheses + x = parseExpression(); + eat(')'); + } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers + while ((ch >= '0' && ch <= '9') || ch == '.') nextChar(); + x = Double.parseDouble(str.substring(startPos, this.pos)); + } else if (ch >= 'a' && ch <= 'z') { // functions + while (ch >= 'a' && ch <= 'z') nextChar(); + String func = str.substring(startPos, this.pos); + x = parseFactor(); + switch (func) { + case "sqrt": + x = Math.sqrt(x); + break; + case "sin": + x = Math.sin(Math.toRadians(x)); + break; + case "cos": + x = Math.cos(Math.toRadians(x)); + break; + case "tan": + x = Math.tan(Math.toRadians(x)); + break; + default: + throw new RuntimeException("Unknown function: " + func); + } + } else { + throw new RuntimeException("Unexpected: " + (char)ch); + } + + if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation + + return x; + } + + double parseTerm() { + double x = parseFactor(); + for (;;) { + if (eat('*')) x *= parseFactor(); // multiplication + else if (eat('/')) x /= parseFactor(); // division + else return x; + } + } + }.parse(); + } + private final Level addon; + private final Queue> chunksToCheck; + private final Island island; + private final HashMap limitCount; + private final CompletableFuture r; + + + private final Results results; + + /** + * Constructor to get the level for an island + * @param addon - Level addon + * @param island - the island to scan + * @param r - completeable result that will be completed when the calculation is complete + */ + public IslandLevelCalculator(Level addon, Island island, CompletableFuture r) { + this.addon = addon; + this.island = island; + this.r = r; + results = new Results(); + chunksToCheck = getChunksToScan(island); + this.limitCount = new HashMap<>(addon.getBlockConfig().getBlockLimits()); + } + + /** + * Calculate the level based on the raw points + * @param blockAndDeathPoints - raw points counted on island + * @return level of island + */ + private long calculateLevel(long blockAndDeathPoints) { + String calcString = addon.getSettings().getLevelCalc(); + String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", String.valueOf(this.addon.getSettings().getLevelCost())); + return (long)eval(withValues) - this.island.getLevelHandicap() - results.initialLevel.get(); + } + + /** + * Adds value to the results based on the material and whether the block is below sea level or not + * @param mat - material of the block + * @param belowSeaLevel - true if below sea level + */ + private void checkBlock(Material mat, boolean belowSeaLevel) { + int count = limitCount(mat); + if (belowSeaLevel) { + results.underWaterBlockCount.addAndGet(count); + results.uwCount.add(mat); + } else { + results.rawBlockCount.addAndGet(count); + results.mdCount.add(mat); + } + } + + /** + * Get a set of all the chunks in island + * @param island - island + * @return - set of pairs of x,z coordinates to check + */ + private Queue> getChunksToScan(Island island) { + Queue> chunkQueue = new ConcurrentLinkedQueue<>(); + for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 + 16); x += 16) { + for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 + 16); z += 16) { + chunkQueue.add(new Pair<>(x >> 4, z >> 4)); + } + } + return chunkQueue; + } + + + /** + * @return the island + */ + public Island getIsland() { + return island; + } + + /** + * Get the completable result for this calculation + * @return the r + */ + public CompletableFuture getR() { + return r; + } + + /** + * Get the full analysis report + * @return a list of lines + */ + private List getReport() { + List reportLines = new ArrayList<>(); + // provide counts + reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld()) + " at " + Util.xyz(island.getCenter().toVector())); + reportLines.add("Island owner UUID = " + island.getOwner()); + reportLines.add("Total block value count = " + String.format("%,d",results.rawBlockCount.get())); + reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc()); + reportLines.add("Level cost = " + addon.getSettings().getLevelCost()); + reportLines.add("Deaths handicap = " + results.deathHandicap.get()); + reportLines.add("Initial island level = " + (0L - addon.getManager().getInitialLevel(island))); + reportLines.add("Level calculated = " + addon.getManager().getIslandLevel(island.getWorld(), island.getOwner())); + reportLines.add(LINE_BREAK); + int total = 0; + if (!results.uwCount.isEmpty()) { + reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier() + ") value"); + reportLines.add("Total number of underwater blocks = " + String.format("%,d",results.uwCount.size())); + reportLines.addAll(sortedReport(total, results.uwCount)); + } + reportLines.add("Regular block count"); + reportLines.add("Total number of blocks = " + String.format("%,d",results.mdCount.size())); + reportLines.addAll(sortedReport(total, results.mdCount)); + + reportLines.add("Blocks not counted because they exceeded limits: " + String.format("%,d",results.ofCount.size())); + Iterable> entriesSortedByCount = results.ofCount.entrySet(); + Iterator> it = entriesSortedByCount.iterator(); + while (it.hasNext()) { + Entry type = it.next(); + Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement()); + String explain = ")"; + if (limit == null) { + Material generic = type.getElement(); + limit = addon.getBlockConfig().getBlockLimits().get(generic); + explain = " - All types)"; + } + reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks (max " + limit + explain); + } + reportLines.add(LINE_BREAK); + reportLines.add("Blocks on island that are not in config.yml"); + reportLines.add("Total number = " + String.format("%,d",results.ncCount.size())); + entriesSortedByCount = results.ncCount.entrySet(); + it = entriesSortedByCount.iterator(); + while (it.hasNext()) { + Entry type = it.next(); + reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks"); + } + reportLines.add(LINE_BREAK); + + return reportLines; + } + + /** + * @return the results + */ + public Results getResults() { + return results; + } + /** + * Get value of a material + * World blocks trump regular block values + * @param md - Material to check + * @return value of a material + */ + private int getValue(Material md) { + Integer value = addon.getBlockConfig().getValue(island.getWorld(), md); + if (value == null) { + // Not in config + results.ncCount.add(md); + return 0; + } + return value; + } + + /** + * Get a chunk async + * @param world - the world where the chunk is + * @param env - the environment + * @param x - chunk x coordinate + * @param z - chunk z coordinate + * @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether + */ + private CompletableFuture getWorldChunk(@NonNull World world, Environment env, int x, int z) { + switch (env) { + case NETHER: + if (addon.getSettings().isNether()) { + World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld()); + if (nether != null) { + return Util.getChunkAtAsync(nether, x, z, false); + } + } + // There is no chunk to scan, so return a null chunk + return CompletableFuture.completedFuture(null); + case THE_END: + if (addon.getSettings().isEnd()) { + World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld()); + if (end != null) { + return Util.getChunkAtAsync(end, x, z, false); + } + } + // There is no chunk to scan, so return a null chunk + return CompletableFuture.completedFuture(null); + default: + return Util.getChunkAtAsync(world, x, z, false); + + } + } + + /** + * Checks if a block has been limited or not and whether a block has any value or not + * @param md Material + * @return value of the block if can be counted + */ + private int limitCount(Material md) { + if (limitCount.containsKey(md)) { + int count = limitCount.get(md); + if (count > 0) { + limitCount.put(md, --count); + return getValue(md); + } else { + results.ofCount.add(md); + return 0; + } + } + return getValue(md); + } + + + /** + * Count the blocks on the island + * @param result - the CompletableFuture that should be completed when this scan is done + * @param chunk - the chunk to scan + */ + private void scanAsync(CompletableFuture result, Chunk chunk) { + // Get a thread-safe snapshot of the 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 = 0; y < chunkSnapshot.getHighestBlockYAt(x, z); y++) { + BlockData blockData = chunkSnapshot.getBlockData(x, y, z); + int seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld()); + 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 (stackersEnabled && blockData.getMaterial() == Material.CAULDRON) { + Block cauldronBlock = chunk.getBlock(x, y, z); + if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) { + StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock); + int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock); + for (int _x = 0; _x < barrelAmt; _x++) { + checkBlock(barrel.getType(), belowSeaLevel); + } + } + } + // Add the value of the block's material + checkBlock(blockData.getMaterial(), belowSeaLevel); + } + } + } + // Chunk finished + if (chunk.getWorld().getEnvironment().equals(Environment.NORMAL) && chunksToCheck.isEmpty()) { + // This was the last chunk + tidyUp(); + } + // Complete the future - this must go back onto the primary thread to exit async otherwise subsequent actions will be async + Bukkit.getScheduler().runTask(addon.getPlugin(),() -> result.complete(true)); + } + + /** + * Scan all containers in a chunk and count their blocks + * @param chunk - the chunk to scan + */ + private void scanChests(Chunk chunk) { + // Count blocks in chests + for (BlockState bs : chunk.getTileEntities()) { + if (bs instanceof Container) { + Inventory inv = ((Container)bs).getSnapshotInventory(); + for (ItemStack i : inv) { + if (i != null && i.getType().isBlock()) { + for (int c = 0; c < i.getAmount(); c++) { + checkBlock(i.getType(), false); + } + } + } + } + } + } + + /** + * Scan the chunk chests and count the blocks + * @param chunk - the chunk to scan + * @return future that completes when the scan is done and supplies a boolean that will be true if the scan was successful, false if not + */ + private CompletableFuture scanChunk(@NonNull Chunk chunk) { + if (chunk == null) { + return CompletableFuture.completedFuture(false); + } + // Scan chests + if (addon.getSettings().isIncludeChests()) { + scanChests(chunk); + } + + // Count blocks in chunk + CompletableFuture result = new CompletableFuture<>(); + Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> scanAsync(result, chunk)); + return result; + } + + /** + * 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 + */ + public CompletableFuture scanNextChunk() { + if (chunksToCheck.isEmpty()) { + // This should not be needed, but just in case + return CompletableFuture.completedFuture(false); + } + // Retrieve and remove from the queue + Pair p = chunksToCheck.poll(); + // Set up the result + CompletableFuture result = new CompletableFuture<>(); + // Get chunks and scan + getWorldChunk(island.getWorld(), Environment.THE_END, p.x, p.z).thenAccept(endChunk -> + scanChunk(endChunk).thenAccept(b -> + getWorldChunk(island.getWorld(), Environment.NETHER, p.x, p.z).thenAccept(netherChunk -> + scanChunk(netherChunk).thenAccept(b2 -> + getWorldChunk(island.getWorld(), Environment.NORMAL, p.x, p.z).thenAccept(normalChunk -> + scanChunk(normalChunk).thenAccept(b3 -> + // Complete the result now that all chunks have been scanned + result.complete(!chunksToCheck.isEmpty())))) + ) + ) + ); + + return result; + } + + private Collection sortedReport(int total, Multiset MaterialCount) { + Collection r = new ArrayList<>(); + Iterable> entriesSortedByCount = Multisets.copyHighestCountFirst(MaterialCount).entrySet(); + for (Entry en : entriesSortedByCount) { + Material type = en.getElement(); + + int value = getValue(type); + + r.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; + } + + private void tidyUp() { + // Finalize calculations + results.rawBlockCount.addAndGet((long)(results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier())); + + // Set the death penalty + if (this.addon.getSettings().isSumTeamDeaths()) + { + for (UUID uuid : this.island.getMemberSet()) + { + this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid)); + } + } + else + { + // At this point, it may be that the island has become unowned. + this.results.deathHandicap.set(this.island.getOwner() == null ? 0 : + this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner())); + } + + long blockAndDeathPoints = this.results.rawBlockCount.get(); + + if (this.addon.getSettings().getDeathPenalty() > 0) + { + // Proper death penalty calculation. + blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty(); + } + this.results.level.set(calculateLevel(blockAndDeathPoints)); + + // Calculate how many points are required to get to the next level + long nextLevel = this.results.level.get(); + long blocks = blockAndDeathPoints; + while (nextLevel < this.results.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) { + nextLevel = calculateLevel(++blocks); + } + this.results.pointsToNextLevel.set(blocks - blockAndDeathPoints); + + // Report + results.report = getReport(); + // All done. + } } diff --git a/src/main/java/world/bentobox/level/calculators/Pipeliner.java b/src/main/java/world/bentobox/level/calculators/Pipeliner.java new file mode 100644 index 0000000..20f05e1 --- /dev/null +++ b/src/main/java/world/bentobox/level/calculators/Pipeliner.java @@ -0,0 +1,100 @@ +package world.bentobox.level.calculators; + +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitTask; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.level.Level; + +/** + * A pipeliner that will process one island at a time + * @author tastybento + * + */ +public class Pipeliner { + + private final Queue processQueue; + private final BukkitTask task; + private boolean inProcess; + private final Level addon; + + /** + * Construct the pipeliner + */ + public Pipeliner(Level addon) { + this.addon = addon; + processQueue = new ConcurrentLinkedQueue<>(); + // Loop continuously - check every tick if there is an island to scan + task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> { + if (!BentoBox.getInstance().isEnabled()) { + cancel(); + return; + } + // One island at a time + if (inProcess || processQueue.isEmpty()) return; + IslandLevelCalculator iD = processQueue.poll(); + // Ignore deleted or unonwed islands + if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned()) return; + // Start the process + inProcess = true; + // Start the scanning of a island with the first chunk + scanChunk(iD); + + }, 1L, 1L); + } + + private void cancel() { + task.cancel(); + } + + public int getIslandsInQueue() { + return processQueue.size(); + } + + /** + * Scans one chunk of an island and adds the results to a results object + * @param iD + */ + private void scanChunk(IslandLevelCalculator iD) { + if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned()) { + // Island is deleted, so finish early with nothing + inProcess = false; + iD.getR().complete(null); + return; + } + // Scan the next chunk + iD.scanNextChunk().thenAccept(r -> { + if (!Bukkit.isPrimaryThread()) { + addon.getPlugin().logError("scanChunk not on Primary Thread!"); + } + if (r) { + // scanNextChunk returns true if there are more chunks to scan + scanChunk(iD); + } else { + // Done + inProcess = false; + iD.getR().complete(iD.getResults()); + } + }); + + } + + + /** + * Adds an island to the scanning queue + * @param island - the island to scan + * + */ + public CompletableFuture addIsland(Island island) { + CompletableFuture r = new CompletableFuture<>(); + processQueue.add(new IslandLevelCalculator(addon, island, r)); + return r; + } + + +} diff --git a/src/main/java/world/bentobox/level/calculators/PlayerLevel.java b/src/main/java/world/bentobox/level/calculators/PlayerLevel.java deleted file mode 100644 index 87820f8..0000000 --- a/src/main/java/world/bentobox/level/calculators/PlayerLevel.java +++ /dev/null @@ -1,106 +0,0 @@ -package world.bentobox.level.calculators; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import org.bukkit.World; -import org.eclipse.jdt.annotation.Nullable; - -import world.bentobox.bentobox.api.events.addon.AddonEvent; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.level.Level; -import world.bentobox.level.calculators.CalcIslandLevel.Results; -import world.bentobox.level.event.IslandLevelCalculatedEvent; -import world.bentobox.level.event.IslandPreLevelEvent; - - -/** - * Gets the player's island level. For admin or players - * @author tastybento - * - */ -public class PlayerLevel { - - private final Level addon; - - private final Island island; - private final World world; - private final User asker; - private final UUID targetPlayer; - - private final long oldLevel; - - private CalcIslandLevel calc; - - - public PlayerLevel(final Level addon, final Island island, final UUID targetPlayer, @Nullable final User asker) { - this.addon = addon; - this.island = island; - this.world = island.getCenter().getWorld(); - this.asker = asker; - this.targetPlayer = targetPlayer; - this.oldLevel = addon.getIslandLevel(world, targetPlayer); - - // Fire pre-level calc event - IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island); - addon.getServer().getPluginManager().callEvent(e); - if (!e.isCancelled()) { - // Calculate if not cancelled - calc = new CalcIslandLevel(addon, island, this::fireIslandLevelCalcEvent); - } - } - - - private void fireIslandLevelCalcEvent() { - // Fire post calculation event - IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, calc.getResult()); - addon.getServer().getPluginManager().callEvent(ilce); - // This exposes these values to plugins via the event - Map keyValues = new HashMap<>(); - keyValues.put("eventName", "IslandLevelCalculatedEvent"); - keyValues.put("targetPlayer", targetPlayer); - keyValues.put("islandUUID", island.getUniqueId()); - keyValues.put("level", calc.getResult().getLevel()); - keyValues.put("pointsToNextLevel", calc.getResult().getPointsToNextLevel()); - keyValues.put("deathHandicap", calc.getResult().getDeathHandicap()); - keyValues.put("initialLevel", calc.getResult().getInitialLevel()); - new AddonEvent().builder().addon(addon).keyValues(keyValues).build(); - Results results = ilce.getResults(); - // Save the results - island.getMemberSet().forEach(m -> addon.setIslandLevel(world, m, results.getLevel())); - // Display result if event is not cancelled - if (!ilce.isCancelled() && asker != null) { - informPlayers(results); - } - } - - - private void informPlayers(Results results) { - // Tell the asker - asker.sendMessage("island.level.island-level-is", "[level]", String.valueOf(addon.getIslandLevel(world, targetPlayer))); - // Console - if (!asker.isPlayer()) { - results.getReport().forEach(asker::sendRawMessage); - return; - } - // Player - if (addon.getSettings().getDeathPenalty() != 0) { - asker.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() < CalcIslandLevel.MAX_AMOUNT) { - asker.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(results.getPointsToNextLevel())); - } - // Tell other team members - if (addon.getIslandLevel(world, targetPlayer) != oldLevel) { - island.getMemberSet().stream() - .filter(u -> !u.equals(asker.getUniqueId())) - .forEach(m -> User.getInstance(m).sendMessage("island.level.island-level-is", "[level]", String.valueOf(addon.getIslandLevel(world, targetPlayer)))); - } - - } - - -} diff --git a/src/main/java/world/bentobox/level/calculators/Results.java b/src/main/java/world/bentobox/level/calculators/Results.java index 28b70b9..faec036 100644 --- a/src/main/java/world/bentobox/level/calculators/Results.java +++ b/src/main/java/world/bentobox/level/calculators/Results.java @@ -1,78 +1,33 @@ package world.bentobox.level.calculators; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.bukkit.Material; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; -/** - * Results class - * - */ public class Results { - private int deathHandicap = 0; - private long initialLevel = 0; - private long level = 0; - private final Multiset mdCount = HashMultiset.create(); - private final Multiset ncCount = HashMultiset.create(); - private final Multiset ofCount = HashMultiset.create(); - private long pointsToNextLevel = 0; - private long rawBlockCount = 0; - private List report = new ArrayList<>(); - private long underWaterBlockCount = 0; - private final Multiset uwCount = HashMultiset.create(); + List report; + final Multiset mdCount = HashMultiset.create(); + final Multiset uwCount = HashMultiset.create(); + final Multiset ncCount = HashMultiset.create(); + final Multiset ofCount = HashMultiset.create(); + // AtomicLong and AtomicInteger must be used because they are changed by multiple concurrent threads + AtomicLong rawBlockCount = new AtomicLong(0); + AtomicLong underWaterBlockCount = new AtomicLong(0); + AtomicLong level = new AtomicLong(0); + AtomicInteger deathHandicap = new AtomicInteger(0); + AtomicLong pointsToNextLevel = new AtomicLong(0); + AtomicLong initialLevel = new AtomicLong(0); /** * @return the deathHandicap */ public int getDeathHandicap() { - return deathHandicap; - } - - public long getInitialLevel() { - return initialLevel; - } - /** - * @return the level - */ - public long getLevel() { - return level; - } - /** - * @return the mdCount - */ - public Multiset getMdCount() { - return mdCount; - } - /** - * @return the ncCount - */ - public Multiset getNcCount() { - return ncCount; - } - - /** - * @return the ofCount - */ - public Multiset getOfCount() { - return ofCount; - } - - /** - * @return the pointsToNextLevel - */ - public long getPointsToNextLevel() { - return pointsToNextLevel; - } - - /** - * @return the rawBlockCount - */ - public long getRawBlockCount() { - return rawBlockCount; + return deathHandicap.get(); } /** @@ -81,82 +36,32 @@ public class Results { public List getReport() { return report; } - - /** - * @return the underWaterBlockCount - */ - public long getUnderWaterBlockCount() { - return underWaterBlockCount; - } - - /** - * @return the uwCount - */ - public Multiset getUwCount() { - return uwCount; - } - - /** - * @param deathHandicap the deathHandicap to set - */ - public void setDeathHandicap(int deathHandicap) { - this.deathHandicap = deathHandicap; - } - - public void setInitialLevel(long initialLevel) { - this.initialLevel = initialLevel; - } - /** * Set level * @param level - level */ - public void setLevel(int level) { - this.level = level; - } - - /** - * @param level the level to set - */ public void setLevel(long level) { - this.level = level; + this.level.set(level); + } + /** + * @return the level + */ + public long getLevel() { + return level.get(); + } + /** + * @return the pointsToNextLevel + */ + public long getPointsToNextLevel() { + return pointsToNextLevel.get(); } - /** - * @param pointsToNextLevel the pointsToNextLevel to set - */ - public void setPointsToNextLevel(long pointsToNextLevel) { - this.pointsToNextLevel = pointsToNextLevel; + public long getInitialLevel() { + return initialLevel.get(); } - /** - * @param rawBlockCount the rawBlockCount to set - */ - public void setRawBlockCount(long rawBlockCount) { - this.rawBlockCount = rawBlockCount; - } - - /** - * @param report the report to set - */ - public void setReport(List report) { - this.report = report; - } - - /** - * @param underWaterBlockCount the underWaterBlockCount to set - */ - public void setUnderWaterBlockCount(long underWaterBlockCount) { - this.underWaterBlockCount = underWaterBlockCount; - } - - /** - * Add to death handicap - * @param deaths - number to add - */ - public void addToDeathHandicap(int deaths) { - this.deathHandicap += deaths; - + public void setInitialLevel(long initialLevel) { + this.initialLevel.set(initialLevel); } /* (non-Javadoc) @@ -164,9 +69,9 @@ public class Results { */ @Override public String toString() { - return "Results [report=" + report + ", mdCount=" + mdCount + ", uwCount=" + getUwCount() + ", ncCount=" + return "Results [report=" + report + ", mdCount=" + mdCount + ", uwCount=" + uwCount + ", ncCount=" + ncCount + ", ofCount=" + ofCount + ", rawBlockCount=" + rawBlockCount + ", underWaterBlockCount=" - + getUnderWaterBlockCount() + ", level=" + level + ", deathHandicap=" + deathHandicap + + underWaterBlockCount + ", level=" + level + ", deathHandicap=" + deathHandicap + ", pointsToNextLevel=" + pointsToNextLevel + ", initialLevel=" + initialLevel + "]"; } diff --git a/src/main/java/world/bentobox/level/commands/admin/AdminLevelCommand.java b/src/main/java/world/bentobox/level/commands/AdminLevelCommand.java similarity index 52% rename from src/main/java/world/bentobox/level/commands/admin/AdminLevelCommand.java rename to src/main/java/world/bentobox/level/commands/AdminLevelCommand.java index 4840a04..1beeba0 100644 --- a/src/main/java/world/bentobox/level/commands/admin/AdminLevelCommand.java +++ b/src/main/java/world/bentobox/level/commands/AdminLevelCommand.java @@ -1,23 +1,18 @@ -package world.bentobox.level.commands.admin; +package world.bentobox.level.commands; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.UUID; import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.util.Util; import world.bentobox.level.Level; -public class AdminLevelCommand extends CompositeCommand { - - private final Level addon; +public class AdminLevelCommand extends IslandLevelCommand { public AdminLevelCommand(Level addon, CompositeCommand parent) { - super(parent, "level"); - this.addon = addon; + super(addon, parent); } @Override @@ -28,25 +23,6 @@ public class AdminLevelCommand extends CompositeCommand { this.setDescription("admin.level.description"); } - @Override - public boolean execute(User user, String label, List args) { - if (args.size() == 1) { - // Asking for another player's level? - // Convert name to a UUID - final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); - if (playerUUID == null) { - user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); - return true; - } else { - addon.calculateIslandLevel(getWorld(), user, playerUUID); - } - return true; - } else { - showHelp(this, user); - return false; - } - } - @Override public Optional> tabComplete(User user, String alias, List args) { String lastArg = !args.isEmpty() ? args.get(args.size()-1) : ""; diff --git a/src/main/java/world/bentobox/level/commands/AdminLevelStatusCommand.java b/src/main/java/world/bentobox/level/commands/AdminLevelStatusCommand.java new file mode 100644 index 0000000..3c7f6b8 --- /dev/null +++ b/src/main/java/world/bentobox/level/commands/AdminLevelStatusCommand.java @@ -0,0 +1,30 @@ +package world.bentobox.level.commands; + +import java.util.List; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.level.Level; + +public class AdminLevelStatusCommand extends CompositeCommand { + + private final Level addon; + + public AdminLevelStatusCommand(Level addon, CompositeCommand parent) { + super(parent, "levelstatus"); + this.addon = addon; + } + + @Override + public void setup() { + this.setPermission("admin.levelstatus"); + this.setOnlyPlayer(false); + this.setDescription("admin.levelstatus.description"); + } + + @Override + public boolean execute(User user, String label, List args) { + user.sendRawMessage("Islands in queue: " + addon.getPipeliner().getIslandsInQueue()); + return true; + } +} diff --git a/src/main/java/world/bentobox/level/commands/admin/AdminTopCommand.java b/src/main/java/world/bentobox/level/commands/AdminTopCommand.java similarity index 78% rename from src/main/java/world/bentobox/level/commands/admin/AdminTopCommand.java rename to src/main/java/world/bentobox/level/commands/AdminTopCommand.java index b04aa0e..8387c5e 100644 --- a/src/main/java/world/bentobox/level/commands/admin/AdminTopCommand.java +++ b/src/main/java/world/bentobox/level/commands/AdminTopCommand.java @@ -1,4 +1,4 @@ -package world.bentobox.level.commands.admin; +package world.bentobox.level.commands; import java.util.List; import java.util.Map; @@ -13,10 +13,10 @@ public class AdminTopCommand extends CompositeCommand { private final Level levelPlugin; - public AdminTopCommand(Level levelPlugin, CompositeCommand parent) { + public AdminTopCommand(Level addon, CompositeCommand parent) { super(parent, "top", "topten"); - this.levelPlugin = levelPlugin; - new AdminTopRemoveCommand(levelPlugin, this); + this.levelPlugin = addon; + new AdminTopRemoveCommand(addon, this); } @Override @@ -28,8 +28,9 @@ public class AdminTopCommand extends CompositeCommand { @Override public boolean execute(User user, String label, List args) { + user.sendMessage("island.top.gui-title"); int rank = 0; - for (Map.Entry topTen : levelPlugin.getTopTen().getTopTenList(getWorld()).getTopTen().entrySet()) { + for (Map.Entry topTen : levelPlugin.getManager().getTopTen(getWorld(), 10).entrySet()) { Island island = getPlugin().getIslands().getIsland(getWorld(), topTen.getKey()); if (island != null) { rank++; diff --git a/src/main/java/world/bentobox/level/commands/admin/AdminTopRemoveCommand.java b/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java similarity index 76% rename from src/main/java/world/bentobox/level/commands/admin/AdminTopRemoveCommand.java rename to src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java index 4778903..e1e78c4 100644 --- a/src/main/java/world/bentobox/level/commands/admin/AdminTopRemoveCommand.java +++ b/src/main/java/world/bentobox/level/commands/AdminTopRemoveCommand.java @@ -1,6 +1,8 @@ -package world.bentobox.level.commands.admin; +package world.bentobox.level.commands; 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; @@ -44,12 +46,20 @@ public class AdminTopRemoveCommand extends CompositeCommand { user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); return false; } + return true; } + @Override public boolean execute(User user, String label, List args) { - addon.getTopTen().getTopTenList(getWorld()).remove(target.getUniqueId()); + addon.getManager().removeEntry(getWorld(), target.getUniqueId()); user.sendMessage("general.success"); return true; } + + @Override + public Optional> tabComplete(User user, String alias, List args) { + return Optional.of(addon.getManager().getTopTen(getWorld(), 10).keySet().stream().map(addon.getPlayers()::getName) + .filter(n -> !n.isEmpty()).collect(Collectors.toList())); + } } diff --git a/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java b/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java new file mode 100644 index 0000000..ba97930 --- /dev/null +++ b/src/main/java/world/bentobox/level/commands/IslandLevelCommand.java @@ -0,0 +1,108 @@ +package world.bentobox.level.commands; + +import java.util.List; +import java.util.UUID; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.level.Level; + +public class IslandLevelCommand extends CompositeCommand { + + private final Level addon; + + public IslandLevelCommand(Level addon, CompositeCommand parent) { + super(parent, "level"); + this.addon = addon; + } + + @Override + public void setup() { + this.setPermission("island.level"); + this.setParametersHelp("island.level.parameters"); + this.setDescription("island.level.description"); + } + + + @Override + public boolean execute(User user, String label, List args) { + if (!args.isEmpty()) { + // Asking for another player's level? + // Convert name to a UUID + final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); + if (playerUUID == null) { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return true; + } + // Ops, console and admin perms can request and calculate other player levels + if (!user.isPlayer() || user.isOp() || user.hasPermission(this.getPermissionPrefix() + "admin.level")) { + return scanIsland(user, playerUUID); + } + // Request for another player's island level + if (!user.getUniqueId().equals(playerUUID) ) { + user.sendMessage("island.level.island-level-is", "[level]", addon.getManager().getIslandLevelString(getWorld(), playerUUID)); + return true; + } + } + // Self request + // Check player cooldown + int coolDown = this.addon.getSettings().getLevelWait(); + + if (coolDown > 0) { + // Check cool down + if (checkCooldown(user)) return false; + // Set cool down + setCooldown(user.getUniqueId(), coolDown); + } + + // Self level request + return scanIsland(user, user.getUniqueId()); + + } + + + private boolean scanIsland(User user, UUID playerUUID) { + Island island = getIslands().getIsland(getWorld(), playerUUID); + if (island != null) { + user.sendMessage("island.level.calculating"); + int inQueue = addon.getPipeliner().getIslandsInQueue(); + if (inQueue > 1) { + user.sendMessage("island.level.in-queue", TextVariables.NUMBER, String.valueOf(inQueue + 1)); + } + // Get the old level + long oldLevel = addon.getManager().getIslandLevel(getWorld(), playerUUID); + addon.getManager().calculateLevel(playerUUID, island).thenAccept(results -> { + if (results == null) return; // island was deleted or become unowned + if (user.isPlayer()) { + user.sendMessage("island.level.island-level-is", "[level]", addon.getManager().getIslandLevelString(getWorld(), playerUUID)); + // Player + if (addon.getSettings().getDeathPenalty() != 0) { + 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) { + user.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(results.getPointsToNextLevel())); + } + // Tell other team members + if (results.getLevel() != oldLevel) { + island.getMemberSet().stream() + .filter(u -> !u.equals(user.getUniqueId())) + .forEach(m -> User.getInstance(m).sendMessage("island.level.island-level-is", "[level]", addon.getManager().getIslandLevelString(getWorld(), playerUUID))); + } + } else { + results.getReport().forEach(BentoBox.getInstance()::log); + } + }); + return true; + } else { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } + + } + + +} diff --git a/src/main/java/world/bentobox/level/commands/island/IslandTopCommand.java b/src/main/java/world/bentobox/level/commands/IslandTopCommand.java similarity index 68% rename from src/main/java/world/bentobox/level/commands/island/IslandTopCommand.java rename to src/main/java/world/bentobox/level/commands/IslandTopCommand.java index a80a3c1..7521b9c 100644 --- a/src/main/java/world/bentobox/level/commands/island/IslandTopCommand.java +++ b/src/main/java/world/bentobox/level/commands/IslandTopCommand.java @@ -1,4 +1,4 @@ -package world.bentobox.level.commands.island; +package world.bentobox.level.commands; import java.util.List; @@ -8,11 +8,11 @@ import world.bentobox.level.Level; public class IslandTopCommand extends CompositeCommand { - private final Level plugin; + private final Level addon; - public IslandTopCommand(Level plugin, CompositeCommand parent) { + public IslandTopCommand(Level addon, CompositeCommand parent) { super(parent, "top", "topten"); - this.plugin = plugin; + this.addon = addon; } @Override @@ -24,7 +24,7 @@ public class IslandTopCommand extends CompositeCommand { @Override public boolean execute(User user, String label, List list) { - plugin.getTopTen().getGUI(getWorld(), user, getPermissionPrefix()); + addon.getManager().getGUI(getWorld(), user); return true; } } diff --git a/src/main/java/world/bentobox/level/commands/island/IslandValueCommand.java b/src/main/java/world/bentobox/level/commands/IslandValueCommand.java similarity index 97% rename from src/main/java/world/bentobox/level/commands/island/IslandValueCommand.java rename to src/main/java/world/bentobox/level/commands/IslandValueCommand.java index be3a04f..ffdb0a7 100644 --- a/src/main/java/world/bentobox/level/commands/island/IslandValueCommand.java +++ b/src/main/java/world/bentobox/level/commands/IslandValueCommand.java @@ -1,14 +1,15 @@ -package world.bentobox.level.commands.island; +package world.bentobox.level.commands; + +import java.util.List; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.PlayerInventory; + import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.level.Level; -import java.util.List; - public class IslandValueCommand extends CompositeCommand { private final Level addon; diff --git a/src/main/java/world/bentobox/level/commands/island/IslandLevelCommand.java b/src/main/java/world/bentobox/level/commands/island/IslandLevelCommand.java deleted file mode 100644 index 58ada94..0000000 --- a/src/main/java/world/bentobox/level/commands/island/IslandLevelCommand.java +++ /dev/null @@ -1,73 +0,0 @@ -package world.bentobox.level.commands.island; - -import java.util.List; -import java.util.UUID; - -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.localization.TextVariables; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.level.Level; - -public class IslandLevelCommand extends CompositeCommand { - - private final Level levelPlugin; - - public IslandLevelCommand(Level levelPlugin, CompositeCommand parent) { - super(parent, "level"); - this.levelPlugin = levelPlugin; - } - - @Override - public void setup() { - this.setPermission("island.level"); - this.setParametersHelp("island.level.parameters"); - this.setDescription("island.level.description"); - this.setOnlyPlayer(true); - } - - @Override - public boolean execute(User user, String label, List args) { - if (!args.isEmpty()) { - // Asking for another player's level? - // Convert name to a UUID - final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); - if (playerUUID == null) { - user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); - return true; - } else if (user.getUniqueId().equals(playerUUID) ) { - return this.calculateLevel(user); - } else { - user.sendMessage("island.level.island-level-is", "[level]", String.valueOf(levelPlugin.getIslandLevel(getWorld(), playerUUID))); - return true; - } - } else { - return this.calculateLevel(user); - } - } - - - /** - * This method calls island level calculation if it is allowed by cooldown. - * @param user User which island level must be calculated. - * @return True if le - */ - private boolean calculateLevel(User user) - { - int coolDown = this.levelPlugin.getSettings().getLevelWait(); - - if (coolDown > 0 && this.checkCooldown(user, null)) - { - return false; - } - - // Self level request - this.levelPlugin.calculateIslandLevel(getWorld(), user, user.getUniqueId()); - - if (coolDown > 0) - { - this.setCooldown(user.getUniqueId(), null, coolDown); - } - - return true; - } -} diff --git a/src/main/java/world/bentobox/level/config/ConfigSettings.java b/src/main/java/world/bentobox/level/config/ConfigSettings.java index a886021..f0b19cc 100644 --- a/src/main/java/world/bentobox/level/config/ConfigSettings.java +++ b/src/main/java/world/bentobox/level/config/ConfigSettings.java @@ -1,37 +1,23 @@ package world.bentobox.level.config; -import java.util.Arrays; +import java.util.Collections; import java.util.List; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.configuration.ConfigComment; import world.bentobox.bentobox.api.configuration.ConfigEntry; import world.bentobox.bentobox.api.configuration.ConfigObject; import world.bentobox.bentobox.api.configuration.StoreAt; -import world.bentobox.level.Level; @StoreAt(filename="config.yml", path="addons/Level") @ConfigComment("Level Configuration [version]") @ConfigComment("") public class ConfigSettings implements ConfigObject { @ConfigComment("") - @ConfigComment("Game Mode Addons") - @ConfigComment("Level will hook into these game mode addons. Don't forget to set any world-specific") - @ConfigComment("block values below!") - @ConfigEntry(path = "game-modes") - private List gameModes = Arrays.asList("BSkyBlock","AcidIsland","CaveBlock"); - - @ConfigComment("") - @ConfigComment("Performance settings") - @ConfigComment("Level is very processor-intensive, so these settings may need to be tweaked to optimize for your server") - @ConfigComment("Delay between each task that loads chunks for calculating levels") - @ConfigComment("Increasing this will slow down level calculations but reduce average load") - @ConfigEntry(path = "task-delay") - private long taskDelay = 1; - - @ConfigComment("") - @ConfigComment("Number of chunks that will be processed per task") - @ConfigEntry(path = "chunks") - private int chunks = 10; + @ConfigComment("Disabled Game Mode Addons") + @ConfigComment("Level will NOT hook into these game mode addons.") + @ConfigEntry(path = "disabled-game-modes") + private List gameModes = Collections.emptyList(); @ConfigComment("") @ConfigComment("Calculate island level on login") @@ -54,6 +40,12 @@ public class ConfigSettings implements ConfigObject { @ConfigEntry(path = "end") private boolean end = false; + @ConfigComment("") + @ConfigComment("Include chest contents in level calculations.") + @ConfigComment("Will count blocks in chests or containers.") + @ConfigEntry(path = "include-chests") + private boolean includeChests = false; + @ConfigComment("") @ConfigComment("Underwater block multiplier") @ConfigComment("If blocks are below sea-level, they can have a higher value. e.g. 2x") @@ -116,48 +108,6 @@ public class ConfigSettings implements ConfigObject { this.gameModes = gameModes; } - - /** - * @return the taskDelay - */ - public long getTaskDelay() { - if (taskDelay < 1L) { - Level.getInstance().logError("task-delay must be at least 1"); - taskDelay = 1; - } - return taskDelay; - } - - - /** - * @param taskDelay the taskDelay to set - */ - public void setTaskDelay(long taskDelay) { - this.taskDelay = taskDelay; - } - - - /** - * @return the chunks - */ - public int getChunks() { - if (chunks < 1) { - Level.getInstance().logError("chunks must be at least 1"); - chunks = 1; - } - - return chunks; - } - - - /** - * @param chunks the chunks to set - */ - public void setChunks(int chunks) { - this.chunks = chunks; - } - - /** * @return the calcOnLogin */ @@ -228,7 +178,7 @@ public class ConfigSettings implements ConfigObject { public long getLevelCost() { if (levelCost < 1) { levelCost = 1; - Level.getInstance().logError("levelcost in config.yml cannot be less than 1. Setting to 1."); + BentoBox.getInstance().logError("levelcost in config.yml cannot be less than 1. Setting to 1."); } return levelCost; } @@ -263,7 +213,7 @@ public class ConfigSettings implements ConfigObject { */ public int getLevelWait() { if (levelWait < 0) { - Level.getInstance().logError("levelwait must be at least 0"); + BentoBox.getInstance().logError("levelwait must be at least 0"); levelWait = 0; } return levelWait; @@ -326,5 +276,24 @@ public class ConfigSettings implements ConfigObject { } + /** + * @return the includeChests + */ + public boolean isIncludeChests() { + return includeChests; + } + + + /** + * @param includeChests the includeChests to set + */ + public void setIncludeChests(boolean includeChests) { + this.includeChests = includeChests; + } + + + + + } diff --git a/src/main/java/world/bentobox/level/event/TopTenClick.java b/src/main/java/world/bentobox/level/event/TopTenClick.java deleted file mode 100644 index a04d352..0000000 --- a/src/main/java/world/bentobox/level/event/TopTenClick.java +++ /dev/null @@ -1,51 +0,0 @@ -package world.bentobox.level.event; - -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; - -/** - * This event is fired when a player clicks on a top ten head. - * - * @author tastybento - */ -class TopTenClick extends Event implements Cancellable { - - private boolean cancelled; - private static final HandlerList handlers = new HandlerList(); - private final String owner; - - - public TopTenClick(String owner) { - this.owner = owner; - } - - /** - * @return name of head owner that was clicked - */ - public String getOwner() { - return owner; - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - - } - - @Override - public HandlerList getHandlers() { - return getHandlerList(); - } - - private static HandlerList getHandlerList() { - return handlers; - } - - -} diff --git a/src/main/java/world/bentobox/level/event/IslandLevelCalculatedEvent.java b/src/main/java/world/bentobox/level/events/IslandLevelCalculatedEvent.java similarity index 94% rename from src/main/java/world/bentobox/level/event/IslandLevelCalculatedEvent.java rename to src/main/java/world/bentobox/level/events/IslandLevelCalculatedEvent.java index 8ae9e9e..196219f 100644 --- a/src/main/java/world/bentobox/level/event/IslandLevelCalculatedEvent.java +++ b/src/main/java/world/bentobox/level/events/IslandLevelCalculatedEvent.java @@ -1,11 +1,11 @@ -package world.bentobox.level.event; +package world.bentobox.level.events; import java.util.List; import java.util.UUID; import world.bentobox.bentobox.api.events.IslandBaseEvent; import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.level.calculators.CalcIslandLevel.Results; +import world.bentobox.level.calculators.Results; /** * This event is fired after the island level is calculated and before the results are saved. @@ -36,30 +36,30 @@ public class IslandLevelCalculatedEvent extends IslandBaseEvent { public Results getResults() { return results; } - + /** * @return death handicap value */ public int getDeathHandicap() { return results.getDeathHandicap(); } - + /** - * Get the island's initial level. It may be zero if it was never calculated + * Get the island's initial level. It may be zero if it was never calculated * or if a player was registered to the island after it was made. * @return initial level of island as calculated when the island was created. */ public long getInitialLevel() { return results.getInitialLevel(); } - + /** * @return the level calculated */ public long getLevel() { return results.getLevel(); } - + /** * Overwrite the level. This level will be used instead of the calculated level. @@ -68,14 +68,14 @@ public class IslandLevelCalculatedEvent extends IslandBaseEvent { public void setLevel(long level) { results.setLevel(level); } - + /** * @return number of points required to next level */ public long getPointsToNextLevel() { return results.getPointsToNextLevel(); } - + /** * @return a human readable report explaining how the calculation was made */ diff --git a/src/main/java/world/bentobox/level/event/IslandPreLevelEvent.java b/src/main/java/world/bentobox/level/events/IslandPreLevelEvent.java similarity index 95% rename from src/main/java/world/bentobox/level/event/IslandPreLevelEvent.java rename to src/main/java/world/bentobox/level/events/IslandPreLevelEvent.java index b48003b..453ab58 100644 --- a/src/main/java/world/bentobox/level/event/IslandPreLevelEvent.java +++ b/src/main/java/world/bentobox/level/events/IslandPreLevelEvent.java @@ -1,4 +1,4 @@ -package world.bentobox.level.event; +package world.bentobox.level.events; import java.util.UUID; diff --git a/src/main/java/world/bentobox/level/listeners/IslandTeamListeners.java b/src/main/java/world/bentobox/level/listeners/IslandActivitiesListeners.java similarity index 51% rename from src/main/java/world/bentobox/level/listeners/IslandTeamListeners.java rename to src/main/java/world/bentobox/level/listeners/IslandActivitiesListeners.java index 613357f..d0a3d85 100644 --- a/src/main/java/world/bentobox/level/listeners/IslandTeamListeners.java +++ b/src/main/java/world/bentobox/level/listeners/IslandActivitiesListeners.java @@ -1,7 +1,5 @@ package world.bentobox.level.listeners; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; import org.bukkit.World; @@ -20,107 +18,91 @@ import world.bentobox.bentobox.api.events.team.TeamEvent.TeamLeaveEvent; import world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.level.Level; -import world.bentobox.level.calculators.CalcIslandLevel; /** * Listens for new islands or ownership changes and sets the level to zero automatically * @author tastybento * */ -public class IslandTeamListeners implements Listener { +public class IslandActivitiesListeners implements Listener { private final Level addon; - private final Map cil; /** * @param addon - addon */ - public IslandTeamListeners(Level addon) { + public IslandActivitiesListeners(Level addon) { this.addon = addon; - cil = new HashMap<>(); + } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIsland(IslandCreatedEvent e) { - // Clear the island setting - addon.setInitialIslandLevel(e.getIsland(), 0L); - if (e.getIsland().getOwner() != null && e.getIsland().getWorld() != null) { - cil.putIfAbsent(e.getIsland(), new CalcIslandLevel(addon, e.getIsland(), () -> zeroLevel(e.getIsland()))); - } + zeroIsland(e.getIsland()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIsland(IslandResettedEvent e) { + zeroIsland(e.getIsland()); + } + + private void zeroIsland(final Island island) { // Clear the island setting - addon.setInitialIslandLevel(e.getIsland(), 0L); - if (e.getIsland().getOwner() != null && e.getIsland().getWorld() != null) { - cil.putIfAbsent(e.getIsland(), new CalcIslandLevel(addon, e.getIsland(), () -> zeroLevel(e.getIsland()))); + if (island.getOwner() != null && island.getWorld() != null) { + addon.getPipeliner().addIsland(island).thenAccept(results -> { + addon.getManager().setInitialIslandLevel(island, results.getLevel()); + }); } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onIslandDelete(IslandPreclearEvent e) { // Remove player from the top ten and level - final UUID owner = e.getIsland().getOwner(); - final World world = e.getIsland().getWorld(); - addon.setIslandLevel(world, owner, 0); - addon.getTopTen().removeEntry(world, owner); + UUID uuid = e.getIsland().getOwner(); + World world = e.getIsland().getWorld(); + remove(world, uuid); + } + + private void remove(World world, UUID uuid) { + if (uuid != null && world != null) { + addon.getManager().removeEntry(world, uuid); + } } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIslandOwner(TeamSetownerEvent e) { // Remove player from the top ten and level - addon.setIslandLevel(e.getIsland().getWorld(), e.getIsland().getOwner(), 0); - addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getIsland().getOwner()); + remove(e.getIsland().getWorld(), e.getIsland().getOwner()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(TeamJoinedEvent e) { // Remove player from the top ten and level - addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0); - addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID()); + remove(e.getIsland().getWorld(), e.getPlayerUUID()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(IslandUnregisteredEvent e) { // Remove player from the top ten and level - addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0); - addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID()); + remove(e.getIsland().getWorld(), e.getPlayerUUID()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(IslandRegisteredEvent e) { // Remove player from the top ten and level - addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0); - addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID()); + remove(e.getIsland().getWorld(), e.getPlayerUUID()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(TeamLeaveEvent e) { // Remove player from the top ten and level - addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0); - addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID()); + remove(e.getIsland().getWorld(), e.getPlayerUUID()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIsland(TeamKickEvent e) { // Remove player from the top ten and level - addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0); - addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID()); + remove(e.getIsland().getWorld(), e.getPlayerUUID()); } - private void zeroLevel(Island island) { - if (cil.containsKey(island)) { - long level = cil.get(island).getResult().getLevel(); - // Get deaths - int deaths = addon.getPlayers().getDeaths(island.getWorld(), island.getOwner()); - // Death penalty calculation. - if (addon.getSettings().getLevelCost() != 0) { - // Add the deaths because this makes the original island that much "bigger" - level += deaths * addon.getSettings().getDeathPenalty() / addon.getSettings().getLevelCost(); - } - addon.setInitialIslandLevel(island, level); - cil.remove(island); - } - } } diff --git a/src/main/java/world/bentobox/level/listeners/JoinLeaveListener.java b/src/main/java/world/bentobox/level/listeners/JoinLeaveListener.java index 8717eb5..37f9776 100644 --- a/src/main/java/world/bentobox/level/listeners/JoinLeaveListener.java +++ b/src/main/java/world/bentobox/level/listeners/JoinLeaveListener.java @@ -1,5 +1,7 @@ package world.bentobox.level.listeners; +import java.util.Objects; + import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -26,13 +28,16 @@ public class JoinLeaveListener implements Listener { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPlayerJoin(PlayerJoinEvent e) { // Load player into cache - addon.getLevelsData(e.getPlayer().getUniqueId()); + addon.getManager().getLevelsData(e.getPlayer().getUniqueId()); // If level calc on login is enabled, run through all the worlds and calculate the level if (addon.getSettings().isCalcOnLogin()) { addon.getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> addon.getSettings().getGameModes().contains(gm.getDescription().getName())) - .forEach(gm -> addon.calculateIslandLevel(gm.getOverWorld(), null, e.getPlayer().getUniqueId())); + .filter(gm -> !addon.getSettings().getGameModes().contains(gm.getDescription().getName())) + .map(gm -> gm.getIslands().getIsland(gm.getOverWorld(), e.getPlayer().getUniqueId())) + .filter(Objects::nonNull) + .forEach(island -> addon.getManager().calculateLevel(e.getPlayer().getUniqueId(), island)); } + } } diff --git a/src/main/java/world/bentobox/level/objects/LevelsData.java b/src/main/java/world/bentobox/level/objects/LevelsData.java index d4fdc0c..142c8fa 100644 --- a/src/main/java/world/bentobox/level/objects/LevelsData.java +++ b/src/main/java/world/bentobox/level/objects/LevelsData.java @@ -1,5 +1,6 @@ package world.bentobox.level.objects; +import java.util.Locale; import java.util.Map; import java.util.TreeMap; import java.util.UUID; @@ -28,8 +29,11 @@ public class LevelsData implements DataObject { */ @Expose private Map initialLevel = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - - public LevelsData() {} // For Bean loading + /** + * Map of world name to points to next level + */ + @Expose + private Map pointsToNextLevel = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); /** * Create a level entry for target player @@ -37,9 +41,8 @@ public class LevelsData implements DataObject { * @param level - level * @param world - world */ - public LevelsData(UUID targetPlayer, long level, World world) { + public LevelsData(UUID targetPlayer) { uniqueId = targetPlayer.toString(); - levels.put(world.getName(), level); } /* (non-Javadoc) @@ -81,8 +84,14 @@ public class LevelsData implements DataObject { this.levels = levels; } + /** + * Sets the island level to level - the initial level + * @param world - world where island is + * @param lv - level + */ public void setLevel(World world, Long lv) { - levels.put(world.getName(),lv); + String name = world.getName().toLowerCase(Locale.ENGLISH); + levels.put(name, lv - this.initialLevel.getOrDefault(name, 0L)); } /** @@ -91,7 +100,7 @@ public class LevelsData implements DataObject { * @param level - level */ public void setInitialLevel(World world, long level) { - this.initialLevel.put(world.getName(), level); + this.initialLevel.put(world.getName().toLowerCase(Locale.ENGLISH), level); } /** @@ -114,6 +123,51 @@ public class LevelsData implements DataObject { * @return initial island level or 0 by default */ public long getInitialLevel(World world) { - return initialLevel.getOrDefault(world.getName(), 0L); + return initialLevel.getOrDefault(world.getName().toLowerCase(Locale.ENGLISH), 0L); } + + /** + * Remove a world from a player's data + * @param world - world to remove + */ + public void remove(World world) { + levels.remove(world.getName().toLowerCase(Locale.ENGLISH)); + initialLevel.remove(world.getName().toLowerCase(Locale.ENGLISH)); + } + + /** + * @return the pointsToNextLevel + */ + public Map getPointsToNextLevel() { + return pointsToNextLevel; + } + + /** + * @param pointsToNextLevel the pointsToNextLevel to set + */ + public void setPointsToNextLevel(Map pointsToNextLevel) { + this.pointsToNextLevel = pointsToNextLevel; + } + + /** + * Sets the island points to next level. + * This is calculated the last time the level was calculated and will not change dynamically. + * @param world - world where island is + * @param points - points to next level + */ + public void setPointsToNextLevel(World world, Long points) { + pointsToNextLevel.put(world.getName().toLowerCase(Locale.ENGLISH), points); + } + + /** + * Get the points required to get to the next island level for this world. + * This is calculated when the island level is calculated and will not change dynamically. + * @param world - world + * @return points to next level or zero if unknown + */ + public long getPointsToNextLevel(World world) { + return pointsToNextLevel.getOrDefault(world.getName().toLowerCase(Locale.ENGLISH), 0L); + } + + } diff --git a/src/main/java/world/bentobox/level/objects/TopTenData.java b/src/main/java/world/bentobox/level/objects/TopTenData.java index 26f062c..e18e8bd 100644 --- a/src/main/java/world/bentobox/level/objects/TopTenData.java +++ b/src/main/java/world/bentobox/level/objects/TopTenData.java @@ -1,12 +1,11 @@ package world.bentobox.level.objects; -import java.util.Collections; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; -import org.eclipse.jdt.annotation.Nullable; +import org.bukkit.World; import com.google.gson.annotations.Expose; @@ -14,7 +13,7 @@ import world.bentobox.bentobox.database.objects.DataObject; import world.bentobox.bentobox.database.objects.Table; /** - * This class stores and sorts the top ten. + * This class stores the top ten. * @author tastybento * */ @@ -27,41 +26,8 @@ public class TopTenData implements DataObject { @Expose private Map topTen = new LinkedHashMap<>(); - public Map getTopTen() { - // Remove any entries that have level values less than 1 - //topTen.values().removeIf(l -> l < 1); - // make copy - Map snapTopTen = Collections.unmodifiableMap(topTen); - return snapTopTen.entrySet().stream() - .filter(l -> l.getValue() > 0) - .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(10) - .collect(Collectors.toMap( - Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); - } - - /** - * Get the level for the rank - * @param rank - rank - * @return level value or 0 if none. - */ - public long getTopTenLevel(int rank) { - Map tt = getTopTen(); - return rank <= tt.size() ? (long)tt.values().toArray()[(rank-1)] : 0; - } - - /** - * Get the UUID of the rank - * @param rank - rank - * @return UUID or null - */ - @Nullable - public UUID getTopTenUUID(int rank) { - Map tt = getTopTen(); - return rank <= tt.size() ? (UUID)tt.keySet().toArray()[(rank-1)] : null; - } - - public void setTopTen(Map topTen) { - this.topTen = topTen; + public TopTenData(World k) { + uniqueId = k.getName().toLowerCase(Locale.ENGLISH); } @Override @@ -73,35 +39,20 @@ public class TopTenData implements DataObject { @Override public void setUniqueId(String uniqueId) { // This is the world name - make it always lowercase - this.uniqueId = uniqueId.toLowerCase(); + this.uniqueId = uniqueId.toLowerCase(Locale.ENGLISH); + } + /** + * @return the topTen + */ + public Map getTopTen() { + return topTen; + } + /** + * @param topTen the topTen to set + */ + public void setTopTen(Map topTen) { + this.topTen = topTen; } - /** - * Add level for this island owner or team leader, sort the top ten and limit to ten entries - * @param uuid - UUID of owner or team leader - * @param level - island level - */ - public void addLevel(UUID uuid, Long level) { - this.topTen.put(uuid, level); - } - - /** - * Get the level for this UUID, or zero if the UUID is not found - * @param uuid - UUID to check - * @return island level - */ - public long getLevel(UUID uuid) { - if (topTen.containsKey(uuid)) - return topTen.get(uuid); - return 0L; - } - - /** - * Removes ownerUUID from the top ten - * @param ownerUUID - UUID to remove - */ - public void remove(UUID ownerUUID) { - this.topTen.remove(ownerUUID); - } } diff --git a/src/main/java/world/bentobox/level/requests/LevelRequestHandler.java b/src/main/java/world/bentobox/level/requests/LevelRequestHandler.java index 2798e67..dc257fe 100644 --- a/src/main/java/world/bentobox/level/requests/LevelRequestHandler.java +++ b/src/main/java/world/bentobox/level/requests/LevelRequestHandler.java @@ -38,6 +38,6 @@ public class LevelRequestHandler extends AddonRequestHandler { return 0L; } - return addon.getIslandLevel(Bukkit.getWorld((String) map.get("world-name")), (UUID) map.get("player")); + return addon.getManager().getIslandLevel(Bukkit.getWorld((String) map.get("world-name")), (UUID) map.get("player")); } } diff --git a/src/main/java/world/bentobox/level/requests/TopTenRequestHandler.java b/src/main/java/world/bentobox/level/requests/TopTenRequestHandler.java index ea2f524..898d2be 100644 --- a/src/main/java/world/bentobox/level/requests/TopTenRequestHandler.java +++ b/src/main/java/world/bentobox/level/requests/TopTenRequestHandler.java @@ -54,6 +54,6 @@ public class TopTenRequestHandler extends AddonRequestHandler { } // No null check required - return addon.getTopTen().getTopTenList(Bukkit.getWorld((String) map.get(WORLD_NAME))).getTopTen(); + return addon.getManager().getTopTen(Bukkit.getWorld((String) map.get(WORLD_NAME)), 10); } } diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index 40a2ba6..85eac40 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -4,83 +4,24 @@ version: ${version}${build.number} icon: DIAMOND api-version: 1.14 -softdepend: AcidIsland, BSkyBlock, CaveBlock, AOneBlock, SkyGrid - authors: tastybento permissions: - bskyblock.intopten: + '[gamemode].intopten': description: Player is in the top ten. default: true - bskyblock.island.level: + '[gamemode].island.level': description: Player can use level command default: true - bskyblock.island.top: + '[gamemode].island.top': description: Player can use top ten command default: true - bskyblock.island.value: + '[gamemode].island.value': description: Player can use value command default: true - bskyblock.admin.level: + '[gamemode].admin.level': description: Player can use admin level command default: true - bskyblock.admin.topten: - description: Player can use admin top ten command - default: true - - acidisland.intopten: - description: Player is in the top ten. - default: true - acidisland.island.level: - description: Player can use level command - default: true - acidisland.island.top: - description: Player can use top ten command - default: true - acidisland.island.value: - description: Player can use value command - default: true - acidisland.admin.level: - description: Player can use admin level command - default: true - acidisland.admin.topten: - description: Player can use admin top ten command - default: true - - caveblock.intopten: - description: Player is in the top ten. - default: true - caveblock.island.level: - description: Player can use level command - default: true - caveblock.island.top: - description: Player can use top ten command - default: true - caveblock.island.value: - description: Player can use value command - default: true - caveblock.admin.level: - description: Player can use admin level command - default: true - caveblock.admin.topten: - description: Player can use admin top ten command - default: true - - aoneblock.intopten: - description: Player is in the top ten. - default: true - aoneblock.island.level: - description: Player can use level command - default: true - aoneblock.island.top: - description: Player can use top ten command - default: true - aoneblock.island.value: - description: Player can use value command - default: true - aoneblock.admin.level: - description: Player can use admin level command - default: true - aoneblock.admin.topten: + '[gamemode].admin.topten': description: Player can use admin top ten command default: true diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e93fd6a..d0d988f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,58 +1,48 @@ -# Config file for Level add-on Version ${version} - -# Game Mode Addons -# Level will hook into these game mode addons. Don't forget to set any world-specific -# block values below! -game-modes: -- AcidIsland -- BSkyBlock -- CaveBlock -#- SkyGrid -#- AOneBlock - -# Performance settings -# Level is very processor-intensive, so these settings may need to be tweaked to optimize for your server -# Delay between each task that loads chunks for calculating levels -# Increasing this will slow down level calculations but reduce average load -task-delay: 1 - -# Number of chunks that will be processed per task -chunks: 10 - +# Level Configuration ${version} +# +# +# Disabled Game Mode Addons +# Level will NOT hook into these game mode addons. +disabled-game-modes: +- AOneBlock +# # Calculate island level on login # This silently calculates the player's island level when they login # This applies to all islands the player has on the server, e.g., BSkyBlock, AcidIsland login: false - +# # Include nether island in level calculations. # Warning: Enabling this mid-game will give players with an island a jump in # island level. New islands will be correctly zeroed. nether: false - -# Include end island in level calculations +# +# Include end island in level calculations. # Warning: Enabling this mid-game will give players with an island a jump in # island level. New islands will be correctly zeroed. end: false - +# +# Include chest contents in level calculations. +# Will count blocks in chests or containers. +include-chests: false +# # Underwater block multiplier # If blocks are below sea-level, they can have a higher value. e.g. 2x # Promotes under-water development if there is a sea. Value can be fractional. underwater: 1.0 - +# # Value of one island level. Default 100. Minimum value is 1. 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 # for example, an alternative non-linear option could be: 3 * sqrt(blocks / level_cost) level-calc: blocks / level_cost - - +# # Cooldown between level requests in seconds levelwait: 60 - +# # Death penalty # How many block values a player will lose per death. # Default value of 100 means that for every death, the player will lose 1 level (if levelcost is 100) @@ -60,9 +50,8 @@ levelwait: 60 deathpenalty: 100 # Sum team deaths - if true, all the teams deaths are summed # If false, only the leader's deaths counts -sumteamdeaths: false # For other death related settings, see the GameModeAddon's config.yml settings. - +sumteamdeaths: false # Shorthand island level # Shows large level values rounded down, e.g., 10,345 -> 10k shorthand: false diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index dcae349..d447cd0 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -9,7 +9,7 @@ admin: description: "calculate the island level for player" top: description: "show the top ten list" - unknown-world: "&cUnknown world!" + unknown-world: "&c Unknown world!" display: "&f[rank]. &a[name] &7- &b[level]" remove: description: "remove player from Top Ten" @@ -19,22 +19,23 @@ island: level: parameters: "[player]" description: "calculate your island level or show the level of [player]" - calculating: "&aCalculating level..." - island-level-is: "&aIsland level is &b[level]" - required-points-to-next-level: "&a[points] points required until the next level" + calculating: "&a Calculating level..." + in-queue: "&a You are number [number] in the queue" + island-level-is: "&a Island level is &b[level]" + required-points-to-next-level: "&a [points] points required until the next level" deaths: "&c([number] deaths)" - cooldown: "&cYou must wait &b[time] &cseconds until you can do that again" + cooldown: "&c You must wait &b[time] &c seconds until you can do that again" top: description: "show the Top Ten" - gui-title: "&aTop Ten" + gui-title: "&a Top Ten" gui-heading: "&6[name]: &B[rank]" - island-level: "&BLevel [level]" - warp-to: "&AWarping to [name]'s island" + island-level: "&b Level [level]" + warp-to: "&A Warping to [name]'s island" value: description: "shows the value of any block" - success: "&7The value of this block is: &e[value]" - success-underwater: "&7The value of this block below sea-level: &e[value]" - empty-hand: "&cThere are no blocks in your hand" - no-value: "&cThat item has no value." \ No newline at end of file + success: "&7 The value of this block is: &e[value]" + success-underwater: "&7 The value of this block below sea-level: &e[value]" + empty-hand: "&c There are no blocks in your hand" + no-value: "&c That item has no value." \ No newline at end of file diff --git a/src/test/java/world/bentobox/level/LevelPresenterTest.java b/src/test/java/world/bentobox/level/LevelPresenterTest.java deleted file mode 100644 index 681c372..0000000 --- a/src/test/java/world/bentobox/level/LevelPresenterTest.java +++ /dev/null @@ -1,115 +0,0 @@ -package world.bentobox.level; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.managers.IslandWorldManager; -import world.bentobox.bentobox.managers.IslandsManager; -import world.bentobox.level.calculators.PlayerLevel; -import world.bentobox.level.config.BlockConfig; -import world.bentobox.level.config.ConfigSettings; - -/** - * @author tastybento - * - */ -@RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, LevelPresenter.class}) -public class LevelPresenterTest { - - @Mock - private BentoBox plugin; - @Mock - private Level addon; - @Mock - private PlayerLevel pl; - @Mock - private ConfigSettings settings; - @Mock - private BlockConfig blockConfig; - - @Before - public void setUp() throws Exception { - IslandWorldManager iwm = mock(IslandWorldManager.class); - when(plugin.getIWM()).thenReturn(iwm); - when(iwm.getPermissionPrefix(Mockito.any())).thenReturn("world"); - IslandsManager im = mock(IslandsManager.class); - when(plugin.getIslands()).thenReturn(im); - // Has island - when(im.hasIsland(Mockito.any(), Mockito.any(User.class))).thenReturn(true); - // In team - when(im.inTeam(Mockito.any(), Mockito.any())).thenReturn(true); - // team leader - when(im.getOwner(Mockito.any(), Mockito.any())).thenReturn(UUID.randomUUID()); - - // Player level - PowerMockito.whenNew(PlayerLevel.class).withAnyArguments().thenReturn(pl); - - // Settings - when(addon.getSettings()).thenReturn(settings); - when(addon.getBlockConfig()).thenReturn(blockConfig); - } - - /** - * Test method for {@link LevelPresenter#LevelPresenter(Level, world.bentobox.bentobox.BentoBox)}. - */ - @Test - public void testLevelPresenter() { - new LevelPresenter(addon, plugin); - } - - /** - * Test method for {@link LevelPresenter#calculateIslandLevel(org.bukkit.World, world.bentobox.bentobox.api.user.User, java.util.UUID)}. - */ - @Test - public void testCalculateIslandLevel() { - LevelPresenter lp = new LevelPresenter(addon, plugin); - World world = mock(World.class); - User sender = mock(User.class); - UUID targetPlayer = UUID.randomUUID(); - lp.calculateIslandLevel(world, sender, targetPlayer); - - Mockito.verify(sender).sendMessage("island.level.calculating"); - } - - /** - * Test method for {@link LevelPresenter#getLevelString(long)}. - */ - @Test - public void testGetLevelStringLong() { - LevelPresenter lp = new LevelPresenter(addon, plugin); - assertEquals("123456789", lp.getLevelString(123456789L)); - - } - - /** - * Test method for {@link LevelPresenter#getLevelString(long)}. - */ - @Test - public void testGetLevelStringLongShorthand() { - when(settings.isShorthand()).thenReturn(true); - LevelPresenter lp = new LevelPresenter(addon, plugin); - assertEquals("123.5M", lp.getLevelString(123456789L)); - assertEquals("1.2k", lp.getLevelString(1234L)); - assertEquals("123.5G", lp.getLevelString(123456789352L)); - assertEquals("1.2T", lp.getLevelString(1234567893524L)); - assertEquals("12345.7T", lp.getLevelString(12345678345345349L)); - - } -} diff --git a/src/test/java/world/bentobox/level/LevelTest.java b/src/test/java/world/bentobox/level/LevelTest.java index d65af4c..ad73dfe 100644 --- a/src/test/java/world/bentobox/level/LevelTest.java +++ b/src/test/java/world/bentobox/level/LevelTest.java @@ -1,13 +1,9 @@ package world.bentobox.level; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -67,7 +63,7 @@ import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.managers.PlaceholdersManager; import world.bentobox.level.config.BlockConfig; import world.bentobox.level.config.ConfigSettings; -import world.bentobox.level.listeners.IslandTeamListeners; +import world.bentobox.level.listeners.IslandActivitiesListeners; import world.bentobox.level.listeners.JoinLeaveListener; /** @@ -97,6 +93,9 @@ public class LevelTest { @Mock private BukkitScheduler scheduler; + @Mock + private Settings pluginSettings; + private Level addon; @Mock @@ -115,8 +114,6 @@ public class LevelTest { private PluginManager pim; @Mock private BlockConfig blockConfig; - @Mock - private Settings pluginSettings; @BeforeClass public static void beforeClass() throws IOException { @@ -152,6 +149,12 @@ public class LevelTest { // Set up plugin Whitebox.setInternalState(BentoBox.class, "instance", plugin); when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); + + // The database type has to be created one line before the thenReturn() to work! + DatabaseType value = DatabaseType.JSON; + when(plugin.getSettings()).thenReturn(pluginSettings); + when(pluginSettings.getDatabaseType()).thenReturn(value); + //when(plugin.isEnabled()).thenReturn(true); // Command manager CommandsManager cm = mock(CommandsManager.class); @@ -172,6 +175,7 @@ public class LevelTest { when(plugin.getIWM()).thenReturn(iwm); + // Player has island to begin with when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island); when(plugin.getIslands()).thenReturn(im); @@ -201,6 +205,7 @@ public class LevelTest { when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode)); AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test").authors("tasty").build(); when(gameMode.getDescription()).thenReturn(desc2); + when(gameMode.getOverWorld()).thenReturn(world); // Player command @NonNull @@ -214,10 +219,6 @@ public class LevelTest { when(plugin.getFlagsManager()).thenReturn(fm); when(fm.getFlags()).thenReturn(Collections.emptyList()); - // The database type has to be created one line before the thenReturn() to work! - DatabaseType value = DatabaseType.JSON; - when(plugin.getSettings()).thenReturn(pluginSettings); - when(pluginSettings.getDatabaseType()).thenReturn(value); // Bukkit PowerMockito.mockStatic(Bukkit.class); @@ -275,47 +276,20 @@ public class LevelTest { verify(plugin).logWarning("[Level] Level Addon: No such world in blockconfig.yml : acidisland_world"); verify(plugin).log("[Level] Level hooking into BSkyBlock"); verify(cmd, times(3)).getAddon(); // Three commands - verify(adminCmd, times(2)).getAddon(); // Two commands + verify(adminCmd, times(3)).getAddon(); // Three commands // Placeholders verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_island_level"), any()); verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_visited_island_level"), any()); + verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_points_to_next_level"), any()); for (int i = 1; i < 11; i++) { verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_name_" + i), any()); verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_value_" + i), any()); } // Commands - verify(am).registerListener(eq(addon), any(IslandTeamListeners.class)); + verify(am).registerListener(eq(addon), any(IslandActivitiesListeners.class)); verify(am).registerListener(eq(addon), any(JoinLeaveListener.class)); } - /** - * Test method for {@link world.bentobox.level.Level#getIslandLevel(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testGetIslandLevelUnknown() { - addon.onEnable(); - assertEquals(0L, addon.getIslandLevel(world, UUID.randomUUID())); - } - - /** - * Test method for {@link world.bentobox.level.Level#getIslandLevel(org.bukkit.World, java.util.UUID)}. - */ - @Test - public void testGetIslandLevelNullTarget() { - addon.onEnable(); - assertEquals(0L, addon.getIslandLevel(world, UUID.randomUUID())); - - } - - /** - * Test method for {@link world.bentobox.level.Level#getLevelsData(java.util.UUID)}. - */ - @Test - public void testGetLevelsDataUnknown() { - addon.onEnable(); - assertNull(addon.getLevelsData(UUID.randomUUID())); - } - /** * Test method for {@link world.bentobox.level.Level#getSettings()}. */ @@ -326,55 +300,5 @@ public class LevelTest { assertEquals(100, s.getLevelCost()); } - /** - * Test method for {@link world.bentobox.level.Level#getTopTen()}. - */ - @Test - public void testGetTopTen() { - addon.onEnable(); - assertNotNull(addon.getTopTen()); - } - - /** - * Test method for {@link world.bentobox.level.Level#getLevelPresenter()}. - */ - @Test - public void testGetLevelPresenter() { - addon.onEnable(); - assertNotNull(addon.getLevelPresenter()); - } - - /** - * Test method for {@link world.bentobox.level.Level#setIslandLevel(org.bukkit.World, java.util.UUID, long)}. - */ - @Test - public void testSetIslandLevel() { - addon.onEnable(); - addon.setIslandLevel(world, uuid, 345L); - assertEquals(345L, addon.getIslandLevel(world, uuid)); - verify(plugin, never()).logError(anyString()); - } - - /** - * Test method for {@link world.bentobox.level.Level#getInitialIslandLevel(world.bentobox.bentobox.database.objects.Island)}. - */ - @Test - public void testGetInitialIslandLevel() { - addon.onEnable(); - addon.setInitialIslandLevel(island, 40); - verify(plugin, never()).logError(anyString()); - assertEquals(40, addon.getInitialIslandLevel(island)); - - } - - /** - * Test method for {@link world.bentobox.level.Level#getHandler()}. - */ - @Test - public void testGetHandler() { - addon.onEnable(); - assertNotNull(addon.getHandler()); - } - } diff --git a/src/test/java/world/bentobox/level/LevelsManagerTest.java b/src/test/java/world/bentobox/level/LevelsManagerTest.java new file mode 100644 index 0000000..fc4ac27 --- /dev/null +++ b/src/test/java/world/bentobox/level/LevelsManagerTest.java @@ -0,0 +1,420 @@ +package world.bentobox.level; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.PluginManager; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import com.google.common.collect.ImmutableSet; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.AbstractDatabaseHandler; +import world.bentobox.bentobox.database.DatabaseSetup; +import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.PlayersManager; +import world.bentobox.level.calculators.Pipeliner; +import world.bentobox.level.calculators.Results; +import world.bentobox.level.config.ConfigSettings; +import world.bentobox.level.objects.LevelsData; +import world.bentobox.level.objects.TopTenData; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Bukkit.class, BentoBox.class, DatabaseSetup.class, PanelBuilder.class}) +public class LevelsManagerTest { + + @Mock + private static AbstractDatabaseHandler handler; + @Mock + Level addon; + @Mock + private BentoBox plugin; + @Mock + private Settings pluginSettings; + + + // Class under test + private LevelsManager lm; + @Mock + private Island island; + @Mock + private Pipeliner pipeliner; + private CompletableFuture cf; + private UUID uuid; + @Mock + private World world; + @Mock + private Player player; + @Mock + private ConfigSettings settings; + @Mock + private User user; + @Mock + private PlayersManager pm; + @Mock + private Inventory inv; + @Mock + private IslandWorldManager iwm; + @Mock + private PluginManager pim; + @Mock + private LevelsData levelsData; + + + + @SuppressWarnings("unchecked") + @BeforeClass + public static void beforeClass() { + // This has to be done beforeClass otherwise the tests will interfere with each other + handler = mock(AbstractDatabaseHandler.class); + // Database + PowerMockito.mockStatic(DatabaseSetup.class); + DatabaseSetup dbSetup = mock(DatabaseSetup.class); + when(DatabaseSetup.getDatabase()).thenReturn(dbSetup); + when(dbSetup.getHandler(any())).thenReturn(handler); + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + when(addon.getPlugin()).thenReturn(plugin); + // Set up plugin + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + + // Bukkit + PowerMockito.mockStatic(Bukkit.class); + when(Bukkit.getWorld(anyString())).thenReturn(world); + when(Bukkit.getPluginManager()).thenReturn(pim); + when(Bukkit.getPlayer(any(UUID.class))).thenReturn(player); + + // The database type has to be created one line before the thenReturn() to work! + DatabaseType value = DatabaseType.JSON; + when(plugin.getSettings()).thenReturn(pluginSettings); + when(pluginSettings.getDatabaseType()).thenReturn(value); + + // Pipeliner + when(addon.getPipeliner()).thenReturn(pipeliner); + cf = new CompletableFuture<>(); + when(pipeliner.addIsland(any())).thenReturn(cf); + + // Island + uuid = UUID.randomUUID(); + ImmutableSet iset = ImmutableSet.of(uuid); + when(island.getMemberSet()).thenReturn(iset); + when(island.getOwner()).thenReturn(uuid); + when(island.getWorld()).thenReturn(world); + + + // Player + when(player.getUniqueId()).thenReturn(uuid); + when(player.hasPermission(anyString())).thenReturn(true); + + // World + when(world.getName()).thenReturn("bskyblock-world"); + + // Settings + when(addon.getSettings()).thenReturn(settings); + + // User + when(user.getTranslation(anyString())).thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + when(user.getTranslation(eq("island.top.gui-heading"), eq("[name]"), anyString(), eq("[rank]"), anyString())).thenReturn("gui-heading"); + when(user.getTranslation(eq("island.top.island-level"),eq("[level]"), anyString())).thenReturn("island-level"); + when(user.getPlayer()).thenReturn(player); + + // Player Manager + when(addon.getPlayers()).thenReturn(pm); + when(pm.getName(any())).thenReturn("player1", + "player2", + "player3", + "player4", + "player5", + "player6", + "player7", + "player8", + "player9", + "player10" + ); + // Mock item factory (for itemstacks) + ItemFactory itemFactory = mock(ItemFactory.class); + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + ItemMeta itemMeta = mock(ItemMeta.class); + when(itemFactory.getItemMeta(any())).thenReturn(itemMeta); + + // Has perms + when(player.hasPermission(anyString())).thenReturn(true); + // Fill the top ten + TopTenData ttd = new TopTenData(world); + ttd.setUniqueId("world"); + List topTen = new ArrayList<>(); + for (long i = -5; i < 5; i ++) { + ttd.getTopTen().put(UUID.randomUUID(), i); + } + // Include a known UUID + ttd.getTopTen().put(uuid, 456789L); + topTen.add(ttd); + when(handler.loadObjects()).thenReturn(topTen); + when(handler.objectExists(anyString())).thenReturn(true); + when(levelsData.getLevel(any())).thenReturn(-5L, -4L, -3L, -2L, -1L, 0L, 1L, 2L, 3L, 4L, 5L, 45678L); + when(levelsData.getUniqueId()).thenReturn(uuid.toString()); + when(handler.loadObject(anyString())).thenReturn(levelsData ); + + + // Inventory GUI + when(Bukkit.createInventory(any(), anyInt(), anyString())).thenReturn(inv); + + // IWM + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.getPermissionPrefix(any())).thenReturn("bskyblock."); + + lm = new LevelsManager(addon); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + deleteAll(new File("database")); + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + } + + private static void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}. + */ + @Test + public void testCalculateLevel() { + Results results = new Results(); + results.setLevel(10000); + results.setInitialLevel(3); + lm.calculateLevel(uuid, island); + cf.complete(results); + + assertTrue(lm.getLevelsData(uuid).getLevel(world) == 10000); + //Map tt = lm.getTopTen(world, 10); + //assertEquals(1, tt.size()); + //assertTrue(tt.get(uuid) == 10000); + + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}. + */ + @Test + public void testGetInitialLevel() { + assertEquals(0,lm.getInitialLevel(island)); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testGetIslandLevel() { + assertEquals(-5, lm.getIslandLevel(world, uuid)); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testGetPointsToNextString() { + assertEquals("0", lm.getPointsToNextString(world, UUID.randomUUID())); + assertEquals("0", lm.getPointsToNextString(world, uuid)); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testGetIslandLevelString() { + assertEquals("-5", lm.getIslandLevelString(world, uuid)); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}. + */ + @Test + public void testGetLevelsData() { + assertEquals(levelsData, lm.getLevelsData(uuid)); + + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#formatLevel(long)}. + */ + @Test + public void testFormatLevel() { + assertEquals("123456789", lm.formatLevel(123456789L)); + when(settings.isShorthand()).thenReturn(true); + assertEquals("123.5M", lm.formatLevel(123456789L)); + assertEquals("1.2k", lm.formatLevel(1234L)); + assertEquals("123.5G", lm.formatLevel(123456789352L)); + assertEquals("1.2T", lm.formatLevel(1234567893524L)); + assertEquals("12345.7T", lm.formatLevel(12345678345345349L)); + + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. + */ + @Test + public void testGetTopTenEmpty() { + Map tt = lm.getTopTen(world, 10); + assertTrue(tt.isEmpty()); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}. + */ + @Test + public void testGetTopTen() { + testLoadTopTens(); + Map tt = lm.getTopTen(world, 10); + assertFalse(tt.isEmpty()); + assertEquals(5, tt.size()); + assertEquals(1, lm.getTopTen(world, 1).size()); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testHasTopTenPerm() { + assertTrue(lm.hasTopTenPerm(world, uuid)); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#loadTopTens()}. + */ + @Test + public void testLoadTopTens() { + lm.loadTopTens(); + PowerMockito.verifyStatic(Bukkit.class); // 1 + Bukkit.getWorld(eq("world")); + verify(addon).log(eq("Loaded TopTen for bskyblock-world")); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#loadTopTens()}. + */ + @Test + public void testLoadTopTensNullWorlds() { + when(Bukkit.getWorld(anyString())).thenReturn(null); + lm.loadTopTens(); + PowerMockito.verifyStatic(Bukkit.class); // 1 + Bukkit.getWorld(eq("world")); + verify(addon).logError(eq("TopTen world 'world' is not known on server. You might want to delete this table. Skipping...")); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}. + */ + @Test + public void testRemoveEntry() { + testLoadTopTens(); + Map tt = lm.getTopTen(world, 10); + assertTrue(tt.containsKey(uuid)); + lm.removeEntry(world, uuid); + tt = lm.getTopTen(world, 10); + assertFalse(tt.containsKey(uuid)); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#save()}. + */ + @Test + public void testSave() { + lm.save(); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}. + */ + @Test + public void testSetInitialIslandLevel() { + lm.setInitialIslandLevel(island, 10); + assertEquals(10, lm.getInitialLevel(island)); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}. + */ + @Test + public void testSetIslandLevel() { + lm.setIslandLevel(world, uuid, 1234); + assertEquals(1234, lm.getIslandLevel(world, uuid)); + } + + /** + * Test method for {@link world.bentobox.level.LevelsManager#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()); + } + */ + } + +} diff --git a/src/test/java/world/bentobox/level/TopTenTest.java b/src/test/java/world/bentobox/level/TopTenTest.java deleted file mode 100644 index 1e09d54..0000000 --- a/src/test/java/world/bentobox/level/TopTenTest.java +++ /dev/null @@ -1,278 +0,0 @@ -package world.bentobox.level; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.inventory.meta.ItemMeta; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.stubbing.Answer; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.reflect.Whitebox; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.panels.builders.PanelBuilder; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.AbstractDatabaseHandler; -import world.bentobox.bentobox.database.DatabaseSetup; -import world.bentobox.bentobox.managers.IslandWorldManager; -import world.bentobox.bentobox.managers.IslandsManager; -import world.bentobox.bentobox.managers.PlayersManager; -import world.bentobox.level.config.BlockConfig; -import world.bentobox.level.objects.TopTenData; - -@RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, BentoBox.class, DatabaseSetup.class, PanelBuilder.class}) -public class TopTenTest { - - @Mock - private Level addon; - @Mock - private World world; - @Mock - private BentoBox plugin; - @Mock - private static AbstractDatabaseHandler handler; - @Mock - private IslandsManager im; - @Mock - private Player player; - @Mock - private IslandWorldManager iwm; - @Mock - private User user; - @Mock - private PlayersManager pm; - @Mock - private Inventory inv; - @Mock - private LevelPresenter lp; - @Mock - private BlockConfig settings; - - - @SuppressWarnings("unchecked") - @BeforeClass - public static void beforeClass() { - // This has to be done beforeClass otherwise the tests will interfere with each other - handler = mock(AbstractDatabaseHandler.class); - // Database - PowerMockito.mockStatic(DatabaseSetup.class); - DatabaseSetup dbSetup = mock(DatabaseSetup.class); - when(DatabaseSetup.getDatabase()).thenReturn(dbSetup); - when(dbSetup.getHandler(any())).thenReturn(handler); - } - - - @Before - public void setUp() throws Exception { - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - when(addon.getPlugin()).thenReturn(plugin); - - PowerMockito.mockStatic(Bukkit.class); - when(Bukkit.getWorld(anyString())).thenReturn(world); - Server server = mock(Server.class); - when(server.getPlayer(any(UUID.class))).thenReturn(player); - when(Bukkit.getServer()).thenReturn(server); - // Has perms - when(player.hasPermission(anyString())).thenReturn(true); - // Fill the top ten - TopTenData ttd = new TopTenData(); - ttd.setUniqueId("world"); - List topTen = new ArrayList<>(); - for (long i = -100; i < 100; i ++) { - ttd.addLevel(UUID.randomUUID(), i); - topTen.add(ttd); - } - when(handler.loadObjects()).thenReturn(topTen); - - // Islands - when(addon.getIslands()).thenReturn(im); - // World - when(world.getName()).thenReturn("world"); - // IWM - when(plugin.getIWM()).thenReturn(iwm); - - // User - when(user.getTranslation(anyString())).thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); - when(user.getTranslation(eq("island.top.gui-heading"), eq("[name]"), anyString(), eq("[rank]"), anyString())).thenReturn("gui-heading"); - when(user.getTranslation(eq("island.top.island-level"),eq("[level]"), anyString())).thenReturn("island-level"); - when(user.getPlayer()).thenReturn(player); - - // Player Manager - when(addon.getPlayers()).thenReturn(pm); - when(pm.getName(any())).thenReturn("player1", - "player2", - "player3", - "player4", - "player5", - "player6", - "player7", - "player8", - "player9", - "player10" - ); - // Mock item factory (for itemstacks) - ItemFactory itemFactory = mock(ItemFactory.class); - when(Bukkit.getItemFactory()).thenReturn(itemFactory); - ItemMeta itemMeta = mock(ItemMeta.class); - when(itemFactory.getItemMeta(any())).thenReturn(itemMeta); - - // Inventory GUI - when(Bukkit.createInventory(any(), anyInt(), anyString())).thenReturn(inv); - - // Level presenter - when(addon.getLevelPresenter()).thenReturn(lp); - when(lp.getLevelString(anyLong())).thenAnswer((Answer) invocation -> String.valueOf(invocation.getArgument(0, Long.class))); - } - - @Test - public void testTopTen() { - new TopTen(addon); - PowerMockito.verifyStatic(Bukkit.class, times(200)); // 1 - Bukkit.getWorld(eq("world")); - } - - @Test - public void testTopTenNullWorld() { - when(Bukkit.getWorld(anyString())).thenReturn(null); - new TopTen(addon); - verify(addon, times(200)).logError("TopTen world world is not known on server. Skipping..."); - } - - @Test - public void testAddEntryNotOwner() throws Exception { - // Database - when(handler.loadObjects()).thenReturn(new ArrayList<>()); - TopTen tt = new TopTen(addon); - UUID ownerUUID = UUID.randomUUID(); - tt.addEntry(world, ownerUUID, 200L); - assertEquals(0, tt.getTopTenList(world).getTopTen().size()); - } - - @Test - public void testAddEntryIsOwner() throws Exception { - when(im.isOwner(any(), any())).thenReturn(true); - when(handler.loadObjects()).thenReturn(new ArrayList<>()); - TopTen tt = new TopTen(addon); - UUID ownerUUID = UUID.randomUUID(); - tt.addEntry(world, ownerUUID, 200L); - assertEquals(200L, (long) tt.getTopTenList(world).getTopTen().get(ownerUUID)); - } - - @Test - public void testAddEntryIsOwnerNoPermission() throws Exception { - when(player.hasPermission(anyString())).thenReturn(false); - when(im.isOwner(any(), any())).thenReturn(true); - when(handler.loadObjects()).thenReturn(new ArrayList<>()); - TopTen tt = new TopTen(addon); - UUID ownerUUID = UUID.randomUUID(); - tt.addEntry(world, ownerUUID, 200L); - assertNull(tt - .getTopTenList(world) - .getTopTen() - .get(ownerUUID)); - } - - @Test - public void testGetGUIFullTopTen() { - TopTen tt = new TopTen(addon); - tt.getGUI(world, user, "bskyblock"); - 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 - public void testGetGUINoPerms() { - when(player.hasPermission(anyString())).thenReturn(false); - TopTen tt = new TopTen(addon); - tt.getGUI(world, user, "bskyblock"); - 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, Mockito.never()).setItem(eq(i), any()); - } - - } - - @Test - public void testGetGUINoTopTen() throws Exception { - when(handler.loadObjects()).thenReturn(new ArrayList<>()); - TopTen tt = new TopTen(addon); - tt.getGUI(world, user, "bskyblock"); - 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, Mockito.never()).setItem(eq(i), any()); - } - - } - - @Test - public void testGetTopTenList() { - TopTen tt = new TopTen(addon); - TopTenData ttdList = tt.getTopTenList(world); - assertEquals(plugin, ttdList.getPlugin()); - } - - @Test - public void testGetTopTenListNewWorld() { - TopTen tt = new TopTen(addon); - TopTenData ttdList = tt.getTopTenList(mock(World.class)); - assertEquals(plugin, ttdList.getPlugin()); - } - - @Test - public void testRemoveEntry() throws Exception { - // Add entry - when(im.isOwner(any(), any())).thenReturn(true); - when(handler.loadObjects()).thenReturn(new ArrayList<>()); - TopTen tt = new TopTen(addon); - UUID ownerUUID = UUID.randomUUID(); - tt.addEntry(world, ownerUUID, 200L); - assertEquals(200L, (long) tt.getTopTenList(world).getTopTen().get(ownerUUID)); - // Remove it - tt.removeEntry(world, ownerUUID); - assertNull(tt.getTopTenList(world).getTopTen().get(ownerUUID)); - } - - @Test - public void testSaveTopTen() throws Exception { - TopTen tt = new TopTen(addon); - tt.saveTopTen(); - verify(handler).saveObject(any()); - } - -} 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 bebad5c..3055bac 100644 --- a/src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java +++ b/src/test/java/world/bentobox/level/commands/admin/AdminTopRemoveCommandTest.java @@ -36,7 +36,8 @@ import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.managers.LocalesManager; import world.bentobox.bentobox.managers.PlayersManager; import world.bentobox.level.Level; -import world.bentobox.level.TopTen; +import world.bentobox.level.LevelsManager; +import world.bentobox.level.commands.AdminTopRemoveCommand; import world.bentobox.level.objects.TopTenData; /** @@ -73,9 +74,9 @@ public class AdminTopRemoveCommandTest { private AdminTopRemoveCommand atrc; @Mock - private TopTen tt; - @Mock private TopTenData ttd; + @Mock + private LevelsManager manager; @Before public void setUp() { @@ -105,8 +106,7 @@ public class AdminTopRemoveCommandTest { when(plugin.getPlayers()).thenReturn(pm); when(pm.getUser(anyString())).thenReturn(user); // topTen - when(addon.getTopTen()).thenReturn(tt); - when(tt.getTopTenList(any())).thenReturn(ttd); + when(addon.getManager()).thenReturn(manager); // User uuid = UUID.randomUUID(); when(user.getUniqueId()).thenReturn(uuid); @@ -175,7 +175,7 @@ public class AdminTopRemoveCommandTest { public void testExecuteUserStringListOfString() { testCanExecuteKnown(); assertTrue(atrc.execute(user, "delete", Collections.singletonList("tastybento"))); - verify(ttd).remove(eq(uuid)); + verify(manager).removeEntry(any(World.class), eq(uuid)); verify(user).sendMessage(eq("general.success")); } diff --git a/src/test/java/world/bentobox/level/objects/TopTenDataTest.java b/src/test/java/world/bentobox/level/objects/TopTenDataTest.java deleted file mode 100644 index d347354..0000000 --- a/src/test/java/world/bentobox/level/objects/TopTenDataTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package world.bentobox.level.objects; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; - -import org.junit.Before; -import org.junit.Test; - -/** - * @author tastybento - * - */ -public class TopTenDataTest { - - private final Map topTen = new LinkedHashMap<>(); - private TopTenData ttd; - private final UUID uuid = UUID.randomUUID(); - - @Before - public void setUp() { - // Create a top ten map - for (long i = 0; i < 100; i++) { - topTen.put(UUID.randomUUID(), i); - } - // Add the top player - topTen.put(uuid, 100L); - // Add negative values - for (long i = 0; i < 100; i++) { - topTen.put(UUID.randomUUID(), - i); - } - ttd = new TopTenData(); - } - - /** - * Test method for {@link world.bentobox.level.objects.TopTenData#getTopTen()}. - */ - @Test - public void testGetTopTen() { - assertTrue(ttd.getTopTen().isEmpty()); - } - - /** - * Test method for {@link world.bentobox.level.objects.TopTenData#setTopTen(java.util.Map)}. - */ - @Test - public void testSetAndGetTopTen() { - ttd.setTopTen(topTen); - // Ten only - assertEquals(10, ttd.getTopTen().size()); - // Check order - long i = 100; - for (long l : ttd.getTopTen().values()) { - - assertEquals(i--, l); - } - } - - /** - * Test method for {@link world.bentobox.level.objects.TopTenData#setTopTen(java.util.Map)}. - */ - @Test - public void testSetAndGetTopTenShort() { - Map topTen2 = new LinkedHashMap<>(); - // Create a top ten map - for (long i = 0; i < 3; i++) { - topTen2.put(UUID.randomUUID(), i); - } - // Add the top player - topTen2.put(uuid, 3L); - ttd.setTopTen(topTen2); - // Three only - assertEquals(3, ttd.getTopTen().size()); - // Check order - long i = 3; - for (long l : ttd.getTopTen().values()) { - System.out.println(l); - assertEquals(i--, l); - } - } - - /** - * Test method for {@link world.bentobox.level.objects.TopTenData#getUniqueId()}. - */ - @Test - public void testGetUniqueId() { - assertTrue(ttd.getUniqueId().isEmpty()); - } - - /** - * Test method for {@link world.bentobox.level.objects.TopTenData#setUniqueId(java.lang.String)}. - */ - @Test - public void testSetUniqueId() { - ttd.setUniqueId("unique"); - assertEquals("unique", ttd.getUniqueId()); - } - - /** - * Test method for {@link world.bentobox.level.objects.TopTenData#addLevel(java.util.UUID, java.lang.Long)}. - */ - @Test - public void testAddAndGetLevel() { - topTen.forEach(ttd::addLevel); - topTen.keySet().forEach(k -> assertEquals((long) topTen.get(k), ttd.getLevel(k))); - } - - /** - * Test method for {@link world.bentobox.level.objects.TopTenData#remove(java.util.UUID)}. - */ - @Test - public void testRemove() { - ttd.remove(uuid); - // Check order - long i = 99; - for (long l : ttd.getTopTen().values()) { - assertEquals(i--, l); - } - } - -}