diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 1639b05..f7a1734 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -24,6 +24,7 @@ import world.bentobox.level.calculators.Pipeliner; import world.bentobox.level.commands.AdminLevelCommand; import world.bentobox.level.commands.AdminLevelStatusCommand; import world.bentobox.level.commands.AdminSetInitialLevelCommand; +import world.bentobox.level.commands.AdminStatsCommand; import world.bentobox.level.commands.AdminTopCommand; import world.bentobox.level.commands.IslandLevelCommand; import world.bentobox.level.commands.IslandTopCommand; @@ -39,411 +40,411 @@ import world.bentobox.level.requests.TopTenRequestHandler; import world.bentobox.visit.VisitAddon; import world.bentobox.warps.Warp; - /** * @author tastybento * */ public class Level extends Addon { - // The 10 in top ten - public static final int TEN = 10; + // The 10 in top ten + public static final int TEN = 10; - // Settings - private ConfigSettings settings; - private Config configObject = new Config<>(this, ConfigSettings.class); - private BlockConfig blockConfig; - private Pipeliner pipeliner; - private LevelsManager manager; - private boolean stackersEnabled; - private boolean advChestEnabled; - private boolean roseStackersEnabled; - private boolean ultimateStackerEnabled; - private final List registeredGameModes = new ArrayList<>(); + // Settings + private ConfigSettings settings; + private Config configObject = new Config<>(this, ConfigSettings.class); + private BlockConfig blockConfig; + private Pipeliner pipeliner; + private LevelsManager manager; + private boolean stackersEnabled; + private boolean advChestEnabled; + private boolean roseStackersEnabled; + private boolean ultimateStackerEnabled; + private final List registeredGameModes = new ArrayList<>(); - /** - * Local variable that stores if warpHook is present. - */ - private Warp warpHook; + /** + * Local variable that stores if warpHook is present. + */ + private Warp warpHook; - /** - * Local variable that stores if visitHook is present. - */ - private VisitAddon visitHook; + /** + * Local variable that stores if visitHook is present. + */ + private VisitAddon visitHook; + @Override + public void onLoad() { + // Save the default config from config.yml + saveDefaultConfig(); + if (loadSettings()) { + // Disable + logError("Level settings could not load! Addon disabled."); + setState(State.DISABLED); + } else { + configObject.saveConfigObject(settings); + } - @Override - public void onLoad() { - // Save the default config from config.yml - saveDefaultConfig(); - if (loadSettings()) { - // Disable - logError("Level settings could not load! Addon disabled."); - setState(State.DISABLED); - } else { - configObject.saveConfigObject(settings); - } + // Save existing panels. + this.saveResource("panels/top_panel.yml", false); + this.saveResource("panels/detail_panel.yml", false); + this.saveResource("panels/value_panel.yml", false); + } - // Save existing panels. - this.saveResource("panels/top_panel.yml", false); - this.saveResource("panels/detail_panel.yml", false); - this.saveResource("panels/value_panel.yml", false); - } + private boolean loadSettings() { + // Load settings again to get worlds + settings = configObject.loadConfigObject(); - private boolean loadSettings() { - // Load settings again to get worlds - settings = configObject.loadConfigObject(); + return settings == null; + } - return settings == null; - } + @Override + public void onEnable() { + loadBlockSettings(); + // Start pipeline + pipeliner = new Pipeliner(this); + // Start Manager + manager = new LevelsManager(this); + // Register listeners + this.registerListener(new IslandActivitiesListeners(this)); + this.registerListener(new JoinLeaveListener(this)); + this.registerListener(new MigrationListener(this)); - @Override - public void onEnable() { - loadBlockSettings(); - // Start pipeline - pipeliner = new Pipeliner(this); - // Start Manager - manager = new LevelsManager(this); - // Register listeners - this.registerListener(new IslandActivitiesListeners(this)); - this.registerListener(new JoinLeaveListener(this)); - this.registerListener(new MigrationListener(this)); + // Register commands for GameModes + registeredGameModes.clear(); + getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> { + log("Level hooking into " + gm.getDescription().getName()); + registerCommands(gm); + new PlaceholderManager(this).registerPlaceholders(gm); + registeredGameModes.add(gm); + }); + // Register request handlers + registerRequestHandler(new LevelRequestHandler(this)); + registerRequestHandler(new TopTenRequestHandler(this)); - // Register commands for GameModes - registeredGameModes.clear(); - getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) - .forEach(gm -> { - log("Level hooking into " + gm.getDescription().getName()); - registerCommands(gm); - new PlaceholderManager(this).registerPlaceholders(gm); - registeredGameModes.add(gm); - }); - // Register request handlers - registerRequestHandler(new LevelRequestHandler(this)); - registerRequestHandler(new TopTenRequestHandler(this)); + // Check if WildStackers is enabled on the server + // I only added support for counting blocks into the island level + // Someone else can PR if they want spawners added to the Leveling system :) + if (!settings.getDisabledPluginHooks().contains("WildStacker")) { + stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker"); + if (stackersEnabled) { + log("Hooked into WildStackers."); + } + } - // Check if WildStackers is enabled on the server - // I only added support for counting blocks into the island level - // Someone else can PR if they want spawners added to the Leveling system :) - if ( !settings.getDisabledPluginHooks().contains("WildStacker") ) { - stackersEnabled = Bukkit.getPluginManager().isPluginEnabled("WildStacker"); - if (stackersEnabled) { - log("Hooked into WildStackers."); - } - } + // Check if AdvancedChests is enabled on the server + if (!settings.getDisabledPluginHooks().contains("AdvancedChests")) { + Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests"); + advChestEnabled = advChest != null; + if (advChestEnabled) { + // Check version + if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) { + log("Hooked into AdvancedChests."); + } else { + logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + + " - requires version 23.0 or later"); + advChestEnabled = false; + } + } + } - // Check if AdvancedChests is enabled on the server - if ( !settings.getDisabledPluginHooks().contains("AdvancedChests") ) { - Plugin advChest = Bukkit.getPluginManager().getPlugin("AdvancedChests"); - advChestEnabled = advChest != null; - if (advChestEnabled) { - // Check version - if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) { - log("Hooked into AdvancedChests."); - } else { - logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + " - requires version 23.0 or later"); - advChestEnabled = false; - } - } - } + // Check if RoseStackers is enabled + if (!settings.getDisabledPluginHooks().contains("RoseStacker")) { + roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker"); + if (roseStackersEnabled) { + log("Hooked into RoseStackers."); + } + } - // Check if RoseStackers is enabled - if ( !settings.getDisabledPluginHooks().contains("RoseStacker") ) { - roseStackersEnabled = Bukkit.getPluginManager().isPluginEnabled("RoseStacker"); - if (roseStackersEnabled) { - log("Hooked into RoseStackers."); - } - } + // Check if UltimateStacker is enabled + if (!settings.getDisabledPluginHooks().contains("UltimateStacker")) { + ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker"); + if (ultimateStackerEnabled) { + log("Hooked into UltimateStacker."); + } + } + } - // Check if UltimateStacker is enabled - if ( !settings.getDisabledPluginHooks().contains("UltimateStacker") ) { - ultimateStackerEnabled = Bukkit.getPluginManager().isPluginEnabled("UltimateStacker"); - if (ultimateStackerEnabled) { - log("Hooked into UltimateStacker."); - } - } - } + @Override + public void allLoaded() { + super.allLoaded(); - @Override - public void allLoaded() - { - super.allLoaded(); + if (this.isEnabled()) { + this.hookExtensions(); + } + } - if (this.isEnabled()) - { - this.hookExtensions(); - } - } + /** + * This method tries to hook into addons and plugins + */ + private void hookExtensions() { + // Try to find Visit addon and if it does not exist, display a warning + this.getAddonByName("Visit").ifPresentOrElse(addon -> { + this.visitHook = (VisitAddon) addon; + this.log("Level Addon hooked into Visit addon."); + }, () -> this.visitHook = null); + // Try to find Warps addon and if it does not exist, display a warning + this.getAddonByName("Warps").ifPresentOrElse(addon -> { + this.warpHook = (Warp) addon; + this.log("Level Addon hooked into Warps addon."); + }, () -> this.warpHook = null); + } - /** - * This method tries to hook into addons and plugins - */ - private void hookExtensions() - { - // Try to find Visit addon and if it does not exist, display a warning - this.getAddonByName("Visit").ifPresentOrElse(addon -> - { - this.visitHook = (VisitAddon) addon; - this.log("Level Addon hooked into Visit addon."); - }, () -> this.visitHook = null); + /** + * Compares versions + * + * @param version1 version 1 + * @param version2 version 2 + * @return {@code <0 if version 1 is older than version 2, =0 if the same, >0 if version 1 is newer than version 2} + */ + public static int compareVersions(String version1, String version2) { + int comparisonResult = 0; - // Try to find Warps addon and if it does not exist, display a warning - this.getAddonByName("Warps").ifPresentOrElse(addon -> - { - this.warpHook = (Warp) addon; - this.log("Level Addon hooked into Warps addon."); - }, () -> this.warpHook = null); - } + String[] version1Splits = version1.split("\\."); + String[] version2Splits = version2.split("\\."); + int maxLengthOfVersionSplits = Math.max(version1Splits.length, version2Splits.length); + for (int i = 0; i < maxLengthOfVersionSplits; i++) { + Integer v1 = i < version1Splits.length ? Integer.parseInt(version1Splits[i]) : 0; + Integer v2 = i < version2Splits.length ? Integer.parseInt(version2Splits[i]) : 0; + int compare = v1.compareTo(v2); + if (compare != 0) { + comparisonResult = compare; + break; + } + } + return comparisonResult; + } - /** - * Compares versions - * @param version1 version 1 - * @param version2 version 2 - * @return {@code <0 if version 1 is older than version 2, =0 if the same, >0 if version 1 is newer than version 2} - */ - public static int compareVersions(String version1, String version2) { - int comparisonResult = 0; + private void registerCommands(GameModeAddon gm) { + gm.getAdminCommand().ifPresent(adminCommand -> { + new AdminLevelCommand(this, adminCommand); + new AdminTopCommand(this, adminCommand); + new AdminLevelStatusCommand(this, adminCommand); + if (getSettings().isZeroNewIslandLevels()) { + new AdminSetInitialLevelCommand(this, adminCommand); + } + new AdminStatsCommand(this, adminCommand); + }); + gm.getPlayerCommand().ifPresent(playerCmd -> { + new IslandLevelCommand(this, playerCmd); + new IslandTopCommand(this, playerCmd); + new IslandValueCommand(this, playerCmd); + }); + } - String[] version1Splits = version1.split("\\."); - String[] version2Splits = version2.split("\\."); - int maxLengthOfVersionSplits = Math.max(version1Splits.length, version2Splits.length); + @Override + public void onDisable() { + // Stop the pipeline + this.getPipeliner().stop(); + } - for (int i = 0; i < maxLengthOfVersionSplits; i++){ - Integer v1 = i < version1Splits.length ? Integer.parseInt(version1Splits[i]) : 0; - Integer v2 = i < version2Splits.length ? Integer.parseInt(version2Splits[i]) : 0; - int compare = v1.compareTo(v2); - if (compare != 0) { - comparisonResult = compare; - break; - } - } - return comparisonResult; - } + private void loadBlockSettings() { + // Save the default blockconfig.yml + this.saveResource("blockconfig.yml", false); - private void registerCommands(GameModeAddon gm) { - gm.getAdminCommand().ifPresent(adminCommand -> { - new AdminLevelCommand(this, adminCommand); - new AdminTopCommand(this, adminCommand); - new AdminLevelStatusCommand(this, adminCommand); - if (getSettings().isZeroNewIslandLevels()) { - new AdminSetInitialLevelCommand(this, adminCommand); - } - }); - gm.getPlayerCommand().ifPresent(playerCmd -> { - new IslandLevelCommand(this, playerCmd); - new IslandTopCommand(this, playerCmd); - new IslandValueCommand(this, playerCmd); - }); - } + YamlConfiguration blockValues = new YamlConfiguration(); + try { + File file = new File(this.getDataFolder(), "blockconfig.yml"); + blockValues.load(file); + // Load the block config class + blockConfig = new BlockConfig(this, blockValues, file); + } catch (IOException | InvalidConfigurationException e) { + // Disable + logError("Level blockconfig.yml settings could not load! Addon disabled."); + setState(State.DISABLED); + } - @Override - public void onDisable() { - // Stop the pipeline - this.getPipeliner().stop(); - } + } - private void loadBlockSettings() { - // Save the default blockconfig.yml - this.saveResource("blockconfig.yml", false); + /** + * @return the blockConfig + */ + public BlockConfig getBlockConfig() { + return blockConfig; + } - YamlConfiguration blockValues = new YamlConfiguration(); - try { - File file = new File(this.getDataFolder(), "blockconfig.yml"); - blockValues.load(file); - // Load the block config class - blockConfig = new BlockConfig(this, blockValues, file); - } catch (IOException | InvalidConfigurationException e) { - // Disable - logError("Level blockconfig.yml settings could not load! Addon disabled."); - setState(State.DISABLED); - } + /** + * @return the settings + */ + public ConfigSettings getSettings() { + return settings; + } - } + /** + * @return the pipeliner + */ + public Pipeliner getPipeliner() { + return pipeliner; + } + /** + * @return the manager + */ + public LevelsManager getManager() { + return manager; + } - /** - * @return the blockConfig - */ - public BlockConfig getBlockConfig() { - return blockConfig; - } + /** + * Set the config settings - used for tests only + * + * @param configSettings - config settings + */ + void setSettings(ConfigSettings configSettings) { + this.settings = configSettings; - /** - * @return the settings - */ - public ConfigSettings getSettings() { - return settings; - } + } - /** - * @return the pipeliner - */ - public Pipeliner getPipeliner() { - return pipeliner; - } + /** + * @return the stackersEnabled + */ + public boolean isStackersEnabled() { + return stackersEnabled; + } - /** - * @return the manager - */ - public LevelsManager getManager() { - return manager; - } + /** + * @return the advChestEnabled + */ + public boolean isAdvChestEnabled() { + return advChestEnabled; + } - /** - * Set the config settings - used for tests only - * @param configSettings - config settings - */ - void setSettings(ConfigSettings configSettings) { - this.settings = configSettings; + /** + * Get level from cache for a player. + * + * @param targetPlayer - target player UUID + * @return Level of player or zero if player is unknown or UUID is null + */ + public long getIslandLevel(World world, @Nullable UUID targetPlayer) { + return getManager().getIslandLevel(world, targetPlayer); + } - } + /** + * Sets the player's level to a value + * + * @param world - world + * @param targetPlayer - target player + * @param level - level + */ + public void setIslandLevel(World world, UUID targetPlayer, long level) { + getManager().setIslandLevel(world, targetPlayer, level); + } - /** - * @return the stackersEnabled - */ - public boolean isStackersEnabled() { - return stackersEnabled; - } + /** + * Zeros the initial island level + * + * @param island - island + * @param level - initial calculated island level + */ + public void setInitialIslandLevel(@NonNull Island island, long level) { + getManager().setInitialIslandLevel(island, level); + } - /** - * @return the advChestEnabled - */ - public boolean isAdvChestEnabled() { - return advChestEnabled; - } + /** + * Get the initial island level + * + * @param island - island + * @return level or 0 by default + */ + public long getInitialIslandLevel(@NonNull Island island) { + return getManager().getInitialLevel(island); + } - /** - * Get level from cache for a player. - * @param targetPlayer - target player UUID - * @return Level of player or zero if player is unknown or UUID is null - */ - public long getIslandLevel(World world, @Nullable UUID targetPlayer) { - return getManager().getIslandLevel(world, targetPlayer); - } + /** + * Calculates a user's island + * + * @param world - the world where this island is + * @param user - not used! See depecration message + * @param playerUUID - the target island member's UUID + * @deprecated Do not use this anymore. Use + * getManager().calculateLevel(playerUUID, island) + */ + @Deprecated(since = "2.3.0", forRemoval = true) + public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) { + Island island = getIslands().getIsland(world, playerUUID); + if (island != null) + getManager().calculateLevel(playerUUID, island); + } - /** - * Sets the player's level to a value - * @param world - world - * @param targetPlayer - target player - * @param level - level - */ - public void setIslandLevel(World world, UUID targetPlayer, long level) { - getManager().setIslandLevel(world, targetPlayer, level); - } + /** + * Provide the levels data for the target player + * + * @param targetPlayer - UUID of target player + * @return LevelsData object or null if not found. Only island levels are set! + * @deprecated Do not use this anymore. Use {@link #getIslandLevel(World, UUID)} + */ + @Deprecated(since = "2.3.0", forRemoval = true) + public LevelsData getLevelsData(UUID targetPlayer) { + LevelsData ld = new LevelsData(targetPlayer); + getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> { + if (getSettings().isZeroNewIslandLevels()) { + Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer); + if (island != null) { + ld.setInitialLevel(gm.getOverWorld(), this.getInitialIslandLevel(island)); + } + } + ld.setLevel(gm.getOverWorld(), this.getIslandLevel(gm.getOverWorld(), targetPlayer)); + }); + return ld; + } - /** - * Zeros the initial island level - * @param island - island - * @param level - initial calculated island level - */ - public void setInitialIslandLevel(@NonNull Island island, long level) { - getManager().setInitialIslandLevel(island, level); - } + /** + * @return the registeredGameModes + */ + public List getRegisteredGameModes() { + return registeredGameModes; + } - /** - * Get the initial island level - * @param island - island - * @return level or 0 by default - */ - public long getInitialIslandLevel(@NonNull Island island) { - return getManager().getInitialLevel(island); - } + /** + * Check if Level addon is active in game mode + * + * @param gm Game Mode Addon + * @return true if active, false if not + */ + public boolean isRegisteredGameMode(GameModeAddon gm) { + return registeredGameModes.contains(gm); + } - /** - * Calculates a user's island - * @param world - the world where this island is - * @param user - not used! See depecration message - * @param playerUUID - the target island member's UUID - * @deprecated Do not use this anymore. Use getManager().calculateLevel(playerUUID, island) - */ - @Deprecated(since="2.3.0", forRemoval=true) - public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) { - Island island = getIslands().getIsland(world, playerUUID); - if (island != null) getManager().calculateLevel(playerUUID, island); - } + /** + * Checks if Level addon is active in world + * + * @param world world + * @return true if active, false if not + */ + public boolean isRegisteredGameModeWorld(World world) { + return registeredGameModes.stream().map(GameModeAddon::getOverWorld).anyMatch(w -> Util.sameWorld(world, w)); + } - /** - * Provide the levels data for the target player - * @param targetPlayer - UUID of target player - * @return LevelsData object or null if not found. Only island levels are set! - * @deprecated Do not use this anymore. Use {@link #getIslandLevel(World, UUID)} - */ - @Deprecated(since="2.3.0", forRemoval=true) - public LevelsData getLevelsData(UUID targetPlayer) { - LevelsData ld = new LevelsData(targetPlayer); - getPlugin().getAddonsManager().getGameModeAddons().stream() - .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) - .forEach(gm -> { - if (getSettings().isZeroNewIslandLevels()) { - Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer); - if (island != null) { - ld.setInitialLevel(gm.getOverWorld(), this.getInitialIslandLevel(island)); - } - } - ld.setLevel(gm.getOverWorld(), this.getIslandLevel(gm.getOverWorld(), targetPlayer)); - }); - return ld; - } + /** + * @return the roseStackersEnabled + */ + public boolean isRoseStackersEnabled() { + return roseStackersEnabled; + } - /** - * @return the registeredGameModes - */ - public List getRegisteredGameModes() { - return registeredGameModes; - } + /** + * @return the ultimateStackerEnabled + */ + public boolean isUltimateStackerEnabled() { + return ultimateStackerEnabled; + } - /** - * Check if Level addon is active in game mode - * @param gm Game Mode Addon - * @return true if active, false if not - */ - public boolean isRegisteredGameMode(GameModeAddon gm) { - return registeredGameModes.contains(gm); - } + /** + * Method Level#getVisitHook returns the visitHook of this object. + * + * @return {@code Visit} of this object, {@code null} otherwise. + */ + public VisitAddon getVisitHook() { + return this.visitHook; + } - /** - * Checks if Level addon is active in world - * @param world world - * @return true if active, false if not - */ - public boolean isRegisteredGameModeWorld(World world) { - return registeredGameModes.stream().map(GameModeAddon::getOverWorld).anyMatch(w -> Util.sameWorld(world, w)); - } - - /** - * @return the roseStackersEnabled - */ - public boolean isRoseStackersEnabled() { - return roseStackersEnabled; - } - - /** - * @return the ultimateStackerEnabled - */ - public boolean isUltimateStackerEnabled() { - return ultimateStackerEnabled; - } - - /** - * Method Level#getVisitHook returns the visitHook of this object. - * - * @return {@code Visit} of this object, {@code null} otherwise. - */ - public VisitAddon getVisitHook() - { - return this.visitHook; - } - - /** - * Method Level#getWarpHook returns the warpHook of this object. - * - * @return {@code Warp} of this object, {@code null} otherwise. - */ - public Warp getWarpHook() - { - return this.warpHook; - } + /** + * Method Level#getWarpHook returns the warpHook of this object. + * + * @return {@code Warp} of this object, {@code null} otherwise. + */ + public Warp getWarpHook() { + return this.warpHook; + } } diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index e40ba49..087ab0d 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -31,417 +31,453 @@ import world.bentobox.level.objects.IslandLevels; 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<>(); + private static final String INTOPTEN = "intopten"; + private static final TreeMap LEVELS; + private static final BigInteger THOUSAND = BigInteger.valueOf(1000); + static { + LEVELS = new TreeMap<>(); - LEVELS.put(THOUSAND, "k"); - LEVELS.put(THOUSAND.pow(2), "M"); - LEVELS.put(THOUSAND.pow(3), "G"); - LEVELS.put(THOUSAND.pow(4), "T"); - } - private final Level addon; + LEVELS.put(THOUSAND, "k"); + LEVELS.put(THOUSAND.pow(2), "M"); + LEVELS.put(THOUSAND.pow(3), "G"); + LEVELS.put(THOUSAND.pow(4), "T"); + } + private final Level addon; - // Database handler for level data - private final Database handler; - // A cache of island levels. - private final Map levelsCache; - // Top ten lists - private final Map topTenLists; + // Database handler for level data + private final Database handler; + // A cache of island levels. + private final Map levelsCache; + // Top ten lists + private final Map topTenLists; + public LevelsManager(Level addon) { + this.addon = addon; + // Get the BentoBox database + // Set up the database handler to store and retrieve data + // Note that these are saved by the BentoBox database + handler = new Database<>(addon, IslandLevels.class); + // Initialize the cache + levelsCache = new HashMap<>(); + // Initialize top ten lists + topTenLists = new ConcurrentHashMap<>(); + } - public LevelsManager(Level addon) { - this.addon = addon; - // Get the BentoBox database - // Set up the database handler to store and retrieve data - // Note that these are saved by the BentoBox database - handler = new Database<>(addon, IslandLevels.class); - // Initialize the cache - levelsCache = new HashMap<>(); - // Initialize top ten lists - topTenLists = new ConcurrentHashMap<>(); - } + public void migrate() { + Database oldDb = new Database<>(addon, LevelsData.class); + oldDb.loadObjects().forEach(ld -> { + try { + UUID owner = UUID.fromString(ld.getUniqueId()); + // Step through each world + ld.getLevels().keySet().stream() + // World + .map(Bukkit::getWorld).filter(Objects::nonNull) + // Island + .map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull).forEach(i -> { + // Make new database entry + World w = i.getWorld(); + IslandLevels il = new IslandLevels(i.getUniqueId()); + il.setInitialLevel(ld.getInitialLevel(w)); + il.setLevel(ld.getLevel(w)); + il.setMdCount(ld.getMdCount(w)); + il.setPointsToNextLevel(ld.getPointsToNextLevel(w)); + il.setUwCount(ld.getUwCount(w)); + // Save it + handler.saveObjectAsync(il); + }); + // Now delete the old database entry + oldDb.deleteID(ld.getUniqueId()); + } catch (Exception e) { + addon.logError("Could not migrate level data database! " + e.getMessage()); + e.printStackTrace(); + return; + } + }); + } - public void migrate() { - Database oldDb = new Database<>(addon, LevelsData.class); - oldDb.loadObjects().forEach(ld -> { - try { - UUID owner = UUID.fromString(ld.getUniqueId()); - // Step through each world - ld.getLevels().keySet().stream() - // World - .map(Bukkit::getWorld).filter(Objects::nonNull) - // Island - .map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull) - .forEach(i -> { - // Make new database entry - World w = i.getWorld(); - IslandLevels il = new IslandLevels(i.getUniqueId()); - il.setInitialLevel(ld.getInitialLevel(w)); - il.setLevel(ld.getLevel(w)); - il.setMdCount(ld.getMdCount(w)); - il.setPointsToNextLevel(ld.getPointsToNextLevel(w)); - il.setUwCount(ld.getUwCount(w)); - // Save it - handler.saveObjectAsync(il); - }); - // Now delete the old database entry - oldDb.deleteID(ld.getUniqueId()); - } catch (Exception e) { - addon.logError("Could not migrate level data database! " + e.getMessage()); - e.printStackTrace(); - return; - } - }); - } + /** + * Add a score to the top players list + * + * @param world - world + * @param targetPlayer - target player + * @param lv - island level + */ + private void addToTopTen(@NonNull World world, @NonNull UUID targetPlayer, long lv) { + // Get top ten + Map topTen = topTenLists.computeIfAbsent(world, k -> new TopTenData(world)).getTopTen(); + // Remove this player from the top list no matter what (we'll put them back + // later if required) + topTen.remove(targetPlayer); - /** - * Add a score to the top players list - * @param world - world - * @param targetPlayer - target player - * @param lv - island level - */ - private void addToTopTen(@NonNull World world, @NonNull UUID targetPlayer, long lv) { - // Get top ten - Map topTen = topTenLists.computeIfAbsent(world, k -> new TopTenData(world)).getTopTen(); - // Remove this player from the top list no matter what (we'll put them back later if required) - topTen.remove(targetPlayer); + // Get the island + Island island = addon.getIslands().getIsland(world, targetPlayer); + if (island != null && island.getOwner() != null && hasTopTenPerm(world, island.getOwner())) { + // Insert the owner into the top ten + topTen.put(island.getOwner(), lv); + } + } - // Get the island - Island island = addon.getIslands().getIsland(world, targetPlayer); - if (island != null && island.getOwner() != null && hasTopTenPerm(world, island.getOwner())) { - // Insert the owner into the top ten - topTen.put(island.getOwner(), lv); - } - } + /** + * Add an island to a top ten + * + * @param island - island to add + * @param lv - level + * @return true if successful, false if not added + */ + private boolean addToTopTen(Island island, long lv) { + if (island != null && island.getOwner() != null && hasTopTenPerm(island.getWorld(), island.getOwner())) { + topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())).getTopTen() + .put(island.getOwner(), lv); + return true; + } + return false; + } - /** - * Add an island to a top ten - * @param island - island to add - * @param lv - level - * @return true if successful, false if not added - */ - private boolean addToTopTen(Island island, long lv) { - if (island != null && island.getOwner() != null && hasTopTenPerm(island.getWorld(), island.getOwner())) { - topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())) - .getTopTen().put(island.getOwner(), lv); - return true; - } - return false; - } + /** + * Calculate the island level, set all island member's levels to the result and + * try to add the owner to the top ten + * + * @param targetPlayer - uuid of targeted player - owner or team member + * @param island - island to calculate + * @return completable future with the results of the calculation + */ + public CompletableFuture calculateLevel(UUID targetPlayer, Island island) { + CompletableFuture result = new CompletableFuture<>(); + // Fire pre-level calc event + IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island); + Bukkit.getPluginManager().callEvent(e); + if (e.isCancelled()) { + return CompletableFuture.completedFuture(null); + } + // Add island to the pipeline + addon.getPipeliner().addIsland(island).thenAccept(r -> { + // Results are irrelevant because the island is unowned or deleted, or + // IslandLevelCalcEvent is cancelled + if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) { + result.complete(null); + } + // Save result + setIslandResults(island.getWorld(), island.getOwner(), r); + // Save the island scan details + result.complete(r); + }); + return result; + } - /** - * Calculate the island level, set all island member's levels to the result and try to add the owner to the top ten - * @param targetPlayer - uuid of targeted player - owner or team member - * @param island - island to calculate - * @return completable future with the results of the calculation - */ - public CompletableFuture calculateLevel(UUID targetPlayer, Island island) { - CompletableFuture result = new CompletableFuture<>(); - // Fire pre-level calc event - IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island); - Bukkit.getPluginManager().callEvent(e); - if (e.isCancelled()) { - return CompletableFuture.completedFuture(null); - } - // Add island to the pipeline - addon.getPipeliner().addIsland(island).thenAccept(r -> { - // Results are irrelevant because the island is unowned or deleted, or IslandLevelCalcEvent is cancelled - if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) { - result.complete(null); - } - // Save result - setIslandResults(island.getWorld(), island.getOwner(), r); - // Save the island scan details - result.complete(r); - }); - return result; - } + /** + * Fires the IslandLevelCalculatedEvent and returns true if it is canceled + * + * @param targetPlayer - target player + * @param island - island + * @param results - results set + * @return true if canceled + */ + private boolean fireIslandLevelCalcEvent(UUID targetPlayer, Island island, Results results) { + // Fire post calculation event + IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results); + Bukkit.getPluginManager().callEvent(ilce); + if (ilce.isCancelled()) + return true; + // Set the values if they were altered + results.setLevel((Long) ilce.getKeyValues().getOrDefault("level", results.getLevel())); + results.setInitialLevel((Long) ilce.getKeyValues().getOrDefault("initialLevel", results.getInitialLevel())); + results.setDeathHandicap((int) ilce.getKeyValues().getOrDefault("deathHandicap", results.getDeathHandicap())); + results.setPointsToNextLevel( + (Long) ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel())); + results.setTotalPoints((Long) ilce.getKeyValues().getOrDefault("totalPoints", results.getTotalPoints())); + return ((Boolean) ilce.getKeyValues().getOrDefault("isCancelled", false)); + } - /** - * Fires the IslandLevelCalculatedEvent and returns true if it is canceled - * @param targetPlayer - target player - * @param island - island - * @param results - results set - * @return true if canceled - */ - private boolean fireIslandLevelCalcEvent(UUID targetPlayer, Island island, Results results) { - // Fire post calculation event - IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results); - Bukkit.getPluginManager().callEvent(ilce); - if (ilce.isCancelled()) return true; - // Set the values if they were altered - results.setLevel((Long)ilce.getKeyValues().getOrDefault("level", results.getLevel())); - results.setInitialLevel((Long)ilce.getKeyValues().getOrDefault("initialLevel", results.getInitialLevel())); - results.setDeathHandicap((int)ilce.getKeyValues().getOrDefault("deathHandicap", results.getDeathHandicap())); - results.setPointsToNextLevel((Long)ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel())); - results.setTotalPoints((Long)ilce.getKeyValues().getOrDefault("totalPoints", results.getTotalPoints())); - return ((Boolean)ilce.getKeyValues().getOrDefault("isCancelled", false)); - } + /** + * Get the string representation of the level. May be converted to shorthand + * notation, e.g., 104556 = 10.5k + * + * @param lvl - long value to represent + * @return string of the level. + */ + public String formatLevel(@Nullable Long lvl) { + if (lvl == null) + return ""; + String level = String.valueOf(lvl); + // Asking for the level of another player + if (addon.getSettings().isShorthand()) { + BigInteger levelValue = BigInteger.valueOf(lvl); - /** - * Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k - * @param lvl - long value to represent - * @return string of the level. - */ - public String formatLevel(@Nullable Long lvl) { - if (lvl == null) return ""; - String level = String.valueOf(lvl); - // Asking for the level of another player - if(addon.getSettings().isShorthand()) { - BigInteger levelValue = BigInteger.valueOf(lvl); + Map.Entry stage = LEVELS.floorEntry(levelValue); - Map.Entry stage = LEVELS.floorEntry(levelValue); + if (stage != null) { // level > 1000 + // 1 052 -> 1.0k + // 1 527 314 -> 1.5M + // 3 874 130 021 -> 3.8G + // 4 002 317 889 -> 4.0T + level = new DecimalFormat("#.#").format( + levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue() / 1000.0) + stage.getValue(); + } + } + return level; + } - if (stage != null) { // level > 1000 - // 1 052 -> 1.0k - // 1 527 314 -> 1.5M - // 3 874 130 021 -> 3.8G - // 4 002 317 889 -> 4.0T - level = new DecimalFormat("#.#").format(levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue()/1000.0) + stage.getValue(); - } - } - return level; - } + /** + * Get the initial level of the island. Used to zero island levels + * + * @param island - island + * @return initial level of island + */ + public long getInitialLevel(Island island) { + return getLevelsData(island).getInitialLevel(); + } - /** - * Get the initial level of the island. Used to zero island levels - * @param island - island - * @return initial level of island - */ - public long getInitialLevel(Island island) { - return getLevelsData(island).getInitialLevel(); - } + /** + * Get level of island from cache for a player. + * + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return Level of the player's island or zero if player is unknown or UUID is + * null + */ + public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) { + if (targetPlayer == null) + return 0L; + // Get the island + Island island = addon.getIslands().getIsland(world, targetPlayer); + return island == null ? 0L : getLevelsData(island).getLevel(); + } - /** - * Get level of island from cache for a player. - * @param world - world where the island is - * @param targetPlayer - target player UUID - * @return Level of the player's island or zero if player is unknown or UUID is null - */ - public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) { - if (targetPlayer == null) return 0L; - // Get the island - Island island = addon.getIslands().getIsland(world, targetPlayer); - return island == null ? 0L : getLevelsData(island).getLevel(); - } + /** + * Get the maximum level ever given to this island + * + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return Max level of the player's island or zero if player is unknown or UUID + * is null + */ + public long getIslandMaxLevel(@NonNull World world, @Nullable UUID targetPlayer) { + if (targetPlayer == null) + return 0L; + // Get the island + Island island = addon.getIslands().getIsland(world, targetPlayer); + return island == null ? 0L : getLevelsData(island).getMaxLevel(); + } - /** - * Get the maximum level ever given to this island - * @param world - world where the island is - * @param targetPlayer - target player UUID - * @return Max level of the player's island or zero if player is unknown or UUID is null - */ - public long getIslandMaxLevel(@NonNull World world, @Nullable UUID targetPlayer) { - if (targetPlayer == null) return 0L; - // Get the island - Island island = addon.getIslands().getIsland(world, targetPlayer); - return island == null ? 0L : getLevelsData(island).getMaxLevel(); - } + /** + * Returns a formatted string of the target player's island level + * + * @param world - world where the island is + * @param targetPlayer - target player's UUID + * @return Formatted level of player or zero if player is unknown or UUID is + * null + */ + public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { + return formatLevel(getIslandLevel(world, targetPlayer)); + } - /** - * Returns a formatted string of the target player's island level - * @param world - world where the island is - * @param targetPlayer - target player's UUID - * @return Formatted level of player or zero if player is unknown or UUID is null - */ - public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) { - return formatLevel(getIslandLevel(world, targetPlayer)); - } + /** + * Load a level data for the island from the cache or database. + * + * @param island - UUID of island + * @return IslandLevels object + */ + @NonNull + public IslandLevels getLevelsData(@NonNull Island island) { + String id = island.getUniqueId(); + if (levelsCache.containsKey(id)) { + return levelsCache.get(id); + } + // Get from database if not in cache + if (handler.objectExists(id)) { + IslandLevels ld = handler.loadObject(id); + if (ld != null) { + levelsCache.put(id, ld); + } else { + handler.deleteID(id); + levelsCache.put(id, new IslandLevels(id)); + } + } else { + levelsCache.put(id, new IslandLevels(id)); + } + // Return cached value + return levelsCache.get(id); + } - /** - * Load a level data for the island from the cache or database. - * @param island - UUID of island - * @return IslandLevels object - */ - @NonNull - public IslandLevels getLevelsData(@NonNull Island island) { - String id = island.getUniqueId(); - if (levelsCache.containsKey(id)) { - return levelsCache.get(id); - } - // Get from database if not in cache - if (handler.objectExists(id)) { - IslandLevels ld = handler.loadObject(id); - if (ld != null) { - levelsCache.put(id, ld); - } else { - handler.deleteID(id); - levelsCache.put(id, new IslandLevels(id)); - } - } else { - levelsCache.put(id, new IslandLevels(id)); - } - // Return cached value - return levelsCache.get(id); - } + /** + * Get the number of points required until the next level since the last level + * calc + * + * @param world - world where the island is + * @param targetPlayer - target player UUID + * @return string with the number required or blank if the player is unknown + */ + public String getPointsToNextString(@NonNull World world, @Nullable UUID targetPlayer) { + if (targetPlayer == null) + return ""; + Island island = addon.getIslands().getIsland(world, targetPlayer); + return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel()); + } - /** - * Get the number of points required until the next level since the last level calc - * @param world - world where the island is - * @param targetPlayer - target player UUID - * @return string with the number required or blank if the player is unknown - */ - public String getPointsToNextString(@NonNull World world, @Nullable UUID targetPlayer) { - if (targetPlayer == null) return ""; - Island island = addon.getIslands().getIsland(world, targetPlayer); - return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel()); - } + /** + * Get the top ten for this world. Returns offline players or players with the + * intopten permission. + * + * @param world - world requested + * @param size - size of the top ten + * @return sorted top ten map + */ + @NonNull + public Map getTopTen(@NonNull World world, int size) { + createAndCleanRankings(world); + // Return the sorted map + return Collections.unmodifiableMap(topTenLists.get(world).getTopTen().entrySet().stream() + .filter(e -> addon.getIslands().hasIsland(world, e.getKey())).filter(l -> l.getValue() > 0) + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(size) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new))); + } - /** - * Get the top ten for this world. Returns offline players or players with the intopten permission. - * @param world - world requested - * @param size - size of the top ten - * @return sorted top ten map - */ - @NonNull - public Map getTopTen(@NonNull World world, int size) { - createAndCleanRankings(world); - // Return the sorted map - return Collections.unmodifiableMap(topTenLists.get(world).getTopTen().entrySet().stream() - .filter(e -> addon.getIslands().isOwner(world, e.getKey())) - .filter(l -> l.getValue() > 0) - .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(size) - .collect(Collectors.toMap( - Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new))); - } + void createAndCleanRankings(@NonNull World world) { + topTenLists.computeIfAbsent(world, TopTenData::new); + // Remove player from top ten if they are online and do not have the perm + topTenLists.get(world).getTopTen().keySet().removeIf(u -> !hasTopTenPerm(world, u)); + } - void createAndCleanRankings(@NonNull World world) { - topTenLists.computeIfAbsent(world, TopTenData::new); - // Remove player from top ten if they are online and do not have the perm - topTenLists.get(world).getTopTen().keySet().removeIf(u -> !hasTopTenPerm(world, u)); - } + /** + * @return the topTenLists + */ + public Map getTopTenLists() { + return topTenLists; + } - /** - * @return the topTenLists - */ - protected Map getTopTenLists() { - return topTenLists; - } + /** + * Get the rank of the player in the rankings + * + * @param world - world + * @param uuid - player UUID + * @return rank placing - note - placing of 1 means top ranked + */ + public int getRank(@NonNull World world, UUID uuid) { + createAndCleanRankings(world); + Stream> stream = topTenLists.get(world).getTopTen().entrySet().stream() + .filter(e -> addon.getIslands().isOwner(world, e.getKey())).filter(l -> l.getValue() > 0) + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); + return (int) (stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).count() + 1); + } - /** - * Get the rank of the player in the rankings - * @param world - world - * @param uuid - player UUID - * @return rank placing - note - placing of 1 means top ranked - */ - public int getRank(@NonNull World world, UUID uuid) { - createAndCleanRankings(world); - Stream> stream = topTenLists.get(world).getTopTen().entrySet().stream() - .filter(e -> addon.getIslands().isOwner(world, e.getKey())) - .filter(l -> l.getValue() > 0) - .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); - return (int) (stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).count() + 1); - } + /** + * Checks if player has the correct top ten perm to have their level saved + * + * @param world + * @param targetPlayer + * @return true if player has the perm or the player is offline + */ + boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) { + String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world); + return Bukkit.getPlayer(targetPlayer) == null + || Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN); + } - /** - * Checks if player has the correct top ten perm to have their level saved - * @param world - * @param targetPlayer - * @return true if player has the perm or the player is offline - */ - boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) { - String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world); - return Bukkit.getPlayer(targetPlayer) == null || Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN); - } + /** + * Loads all the top tens from the database + */ + public void loadTopTens() { + topTenLists.clear(); + Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> { + addon.log("Generating rankings"); + handler.loadObjects().forEach(il -> { + if (il.getLevel() > 0) { + addon.getIslands().getIslandById(il.getUniqueId()) + .ifPresent(i -> this.addToTopTen(i, il.getLevel())); + } + }); + topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName())); + }); + } - /** - * Loads all the top tens from the database - */ - public void loadTopTens() { - topTenLists.clear(); - Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> { - addon.log("Generating rankings"); - handler.loadObjects().forEach(il -> { - if (il.getLevel() > 0) { - addon.getIslands().getIslandById(il.getUniqueId()).ifPresent(i -> this.addToTopTen(i, il.getLevel())); - } - }); - topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName())); - }); - } + /** + * Removes a player from a world's top ten and removes world from player's level + * data + * + * @param world - world + * @param uuid - the player's uuid + */ + public void removeEntry(World world, UUID uuid) { + if (topTenLists.containsKey(world)) { + topTenLists.get(world).getTopTen().remove(uuid); + } - /** - * Removes a player from a world's top ten and removes world from player's level data - * @param world - world - * @param uuid - the player's uuid - */ - public void removeEntry(World world, UUID uuid) { - if (topTenLists.containsKey(world)) { - topTenLists.get(world).getTopTen().remove(uuid); - } + } - } + /** + * Set an initial island level + * + * @param island - the island to set. Must have a non-null world + * @param lv - initial island level + */ + public void setInitialIslandLevel(@NonNull Island island, long lv) { + if (island.getWorld() == null) + return; + levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv); + handler.saveObjectAsync(levelsCache.get(island.getUniqueId())); + } - /** - * Set an initial island level - * @param island - the island to set. Must have a non-null world - * @param lv - initial island level - */ - public void setInitialIslandLevel(@NonNull Island island, long lv) { - if (island.getWorld() == null) return; - levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv); - handler.saveObjectAsync(levelsCache.get(island.getUniqueId())); - } + /** + * Set the island level for the owner of the island that targetPlayer is a + * member + * + * @param world - world + * @param targetPlayer - player, may be a team member + * @param lv - level + */ + public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) { + // Get the island + Island island = addon.getIslands().getIsland(world, targetPlayer); + if (island != null) { + String id = island.getUniqueId(); + IslandLevels il = levelsCache.computeIfAbsent(id, IslandLevels::new); + // Remove the initial level + if (addon.getSettings().isZeroNewIslandLevels()) { + il.setLevel(lv - il.getInitialLevel()); + } else { + il.setLevel(lv); + } + handler.saveObjectAsync(levelsCache.get(id)); + // Update TopTen + addToTopTen(world, targetPlayer, levelsCache.get(id).getLevel()); + } - /** - * Set the island level for the owner of the island that targetPlayer is a member - * @param world - world - * @param targetPlayer - player, may be a team member - * @param lv - level - */ - public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) { - // Get the island - Island island = addon.getIslands().getIsland(world, targetPlayer); - if (island != null) { - String id = island.getUniqueId(); - IslandLevels il = levelsCache.computeIfAbsent(id, IslandLevels::new); - // Remove the initial level - if (addon.getSettings().isZeroNewIslandLevels()) { - il.setLevel(lv - il.getInitialLevel()); - } else { - il.setLevel(lv); - } - handler.saveObjectAsync(levelsCache.get(id)); - // Update TopTen - addToTopTen(world, targetPlayer, levelsCache.get(id).getLevel()); - } + } - } + /** + * Set the island level for the owner of the island that targetPlayer is a + * member + * + * @param world - world + * @param owner - owner of the island + * @param r - results of the calculation + */ + private void setIslandResults(World world, @NonNull UUID owner, Results r) { + // Get the island + Island island = addon.getIslands().getIsland(world, owner); + if (island == null) + return; + IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new); + ld.setLevel(r.getLevel()); + ld.setUwCount(Maps.asMap(r.getUwCount().elementSet(), elem -> r.getUwCount().count(elem))); + ld.setMdCount(Maps.asMap(r.getMdCount().elementSet(), elem -> r.getMdCount().count(elem))); + ld.setPointsToNextLevel(r.getPointsToNextLevel()); + ld.setTotalPoints(r.getTotalPoints()); + levelsCache.put(island.getUniqueId(), ld); + handler.saveObjectAsync(ld); + // Update TopTen + addToTopTen(world, owner, ld.getLevel()); + } - /** - * Set the island level for the owner of the island that targetPlayer is a member - * @param world - world - * @param owner - owner of the island - * @param r - results of the calculation - */ - private void setIslandResults(World world, @NonNull UUID owner, Results r) { - // Get the island - Island island = addon.getIslands().getIsland(world, owner); - if (island == null) return; - IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new); - ld.setLevel(r.getLevel()); - ld.setUwCount(Maps.asMap(r.getUwCount().elementSet(), elem -> r.getUwCount().count(elem))); - ld.setMdCount(Maps.asMap(r.getMdCount().elementSet(), elem -> r.getMdCount().count(elem))); - ld.setPointsToNextLevel(r.getPointsToNextLevel()); - ld.setTotalPoints(r.getTotalPoints()); - levelsCache.put(island.getUniqueId(), ld); - handler.saveObjectAsync(ld); - // Update TopTen - addToTopTen(world, owner, ld.getLevel()); - } - - /** - * Removes island from cache when it is deleted - * @param uniqueId - id of island - */ - public void deleteIsland(String uniqueId) { - levelsCache.remove(uniqueId); - handler.deleteID(uniqueId); - } + /** + * Removes island from cache when it is deleted + * + * @param uniqueId - id of island + */ + public void deleteIsland(String uniqueId) { + levelsCache.remove(uniqueId); + handler.deleteID(uniqueId); + } } diff --git a/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java b/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java new file mode 100644 index 0000000..3399a1f --- /dev/null +++ b/src/main/java/world/bentobox/level/commands/AdminStatsCommand.java @@ -0,0 +1,94 @@ +package world.bentobox.level.commands; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.bukkit.World; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.level.Level; +import world.bentobox.level.objects.TopTenData; + +public class AdminStatsCommand extends CompositeCommand { + + private final Level level; + + public AdminStatsCommand(Level addon, CompositeCommand parent) { + super(parent, "stats"); + this.level = addon; + new AdminTopRemoveCommand(addon, this); + } + + @Override + public void setup() { + this.setPermission("admin.stats"); + this.setOnlyPlayer(false); + this.setDescription("admin.stats.description"); + } + + @Override + public boolean execute(User user, String label, List args) { + user.sendMessage("admin.stats.title"); + for (Entry en : level.getManager().getTopTenLists().entrySet()) { + user.sendMessage("admin.stats.world", TextVariables.NAME, + level.getPlugin().getIWM().getWorldName(en.getKey())); + Map topTen = en.getValue().getTopTen(); + if (topTen.isEmpty()) { + user.sendMessage("admin.stats.no-data"); + return false; + } + + // Calculating basic statistics + long sum = 0, max = Long.MIN_VALUE, min = Long.MAX_VALUE; + Map levelFrequency = new HashMap<>(); + + for (Long level : topTen.values()) { + sum += level; + max = Math.max(max, level); + min = Math.min(min, level); + levelFrequency.merge(level, 1, Integer::sum); + } + + double average = sum / (double) topTen.size(); + List sortedLevels = topTen.values().stream().sorted().collect(Collectors.toList()); + long median = sortedLevels.get(sortedLevels.size() / 2); + Long mode = Collections.max(levelFrequency.entrySet(), Map.Entry.comparingByValue()).getKey(); + + // Logging basic statistics + user.sendMessage("admin.stats.average-level", TextVariables.NUMBER, String.valueOf(average)); + user.sendMessage("admin.stats.median-level", TextVariables.NUMBER, String.valueOf(median)); + user.sendMessage("admin.stats.mode-level", TextVariables.NUMBER, String.valueOf(mode)); + user.sendMessage("admin.stats.highest-level", TextVariables.NUMBER, String.valueOf(max)); + user.sendMessage("admin.stats.lowest-level", TextVariables.NUMBER, String.valueOf(min)); + + // Grouping data for distribution analysis + Map rangeMap = new TreeMap<>(); + for (Long level : topTen.values()) { + String range = getRange(level); + rangeMap.merge(range, 1, Integer::sum); + } + + // Logging distribution + user.sendMessage("admin.stats.distribution"); + for (Map.Entry entry : rangeMap.entrySet()) { + user.sendMessage( + entry.getKey() + ": " + entry.getValue() + " " + user.getTranslation("admin.stats.islands")); + } + } + return true; + } + + private static String getRange(long level) { + long rangeStart = level / 100 * 100; + long rangeEnd = rangeStart + 99; + return rangeStart + "-" + rangeEnd; + } +} diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 6456818..f71b52d 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -22,8 +22,18 @@ admin: remove: description: "remove player from Top Ten" parameters: "" - - + stats: + description: "show stats on islands on this server" + title: "Server Island Stats" + world: "&a [name]" + no-data: "&c No data to process." + average-level: "Average Island Level: [number]" + median-level: "Median Island Level: [number]" + mode-level: "Mode Island Level: [number]" + highest-level: "Highest Island Level: [number]" + lowest-level: "Lowest Island Level: [number]" + distribution: "Island Level Distribution:" + islands: "islands" island: level: parameters: "[player]"