diff --git a/pom.xml b/pom.xml index c76d008..88003e2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,13 +6,24 @@ world.bentobox level - 1.5.0 + ${revision} Level Level is an add-on for BentoBox, an expandable Minecraft Bukkit plugin for island-type games like SkyBlock or AcidIsland. https://github.com/BentoBoxWorld/Level 2017 + + + tastybento + tastybento@bentobox.world + -8 + + Lead Developer + + + + scm:git:https://github.com/BentoBoxWorld/Level.git scm:git:git@github.com:BentoBoxWorld/Level.git @@ -41,12 +52,59 @@ - UTF-8 - UTF-8 - 1.8 - 1.7.4 + UTF-8 + UTF-8 + 1.8 + + 2.0.2 + + 1.13.2-R0.1-SNAPSHOT + 1.6.0-SNAPSHOT + + ${build.version}-SNAPSHOT + + -LOCAL + + 1.6.0 + + + + + + ci + + + env.BUILD_NUMBER + + + + + -b${env.BUILD_NUMBER} + + + + + + + + master + + + env.GIT_BRANCH + origin/master + + + + + ${build.version} + + + + + + spigot-repo @@ -62,40 +120,50 @@ - - - org.spigotmc - spigot-api - 1.14.1-R0.1-SNAPSHOT - provided - - - org.mockito - mockito-all - 1.10.19 - test - - - org.powermock - powermock-module-junit4 - ${powermock.version} - test - - - org.powermock - powermock-api-mockito - ${powermock.version} - test - - - world.bentobox - bentobox - 1.5.0 - provided - - + + + + org.spigotmc + spigot-api + ${spigot.version} + provided + + + + org.mockito + mockito-core + 3.0.0 + test + + + org.powermock + powermock-module-junit4 + ${powermock.version} + test + + + org.powermock + powermock-api-mockito2 + ${powermock.version} + test + + + world.bentobox + bentobox + ${bentobox.version} + provided + + + + + + + + + ${project.name}-${revision}${build.number} + clean package diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index 127c3f0..181faf5 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.UUID; import org.bukkit.World; +import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.user.User; @@ -46,7 +47,7 @@ public class Level extends Addon { private TopTen topTen; // Level calculator - private LevelPresenter levelCalc; + private LevelPresenter levelPresenter; /** * Calculates a user's island @@ -54,8 +55,8 @@ public class Level extends Addon { * @param user - the user who is asking, or null if none * @param playerUUID - the target island member's UUID */ - public void calculateIslandLevel(World world, User user, UUID playerUUID) { - levelCalc.calculateIslandLevel(world, user, playerUUID); + public void calculateIslandLevel(World world, @Nullable User user, UUID playerUUID) { + levelPresenter.calculateIslandLevel(world, user, playerUUID); } /** @@ -85,7 +86,7 @@ public class Level extends Addon { /** * @return the settings */ - public final Settings getSettings() { + public Settings getSettings() { return settings; } @@ -93,6 +94,13 @@ public class Level extends Addon { return topTen; } + /** + * @return the levelPresenter + */ + public LevelPresenter getLevelPresenter() { + return levelPresenter; + } + @Override public void onDisable(){ // Save the cache @@ -115,7 +123,7 @@ public class Level extends Addon { // Initialize the cache levelsCache = new HashMap<>(); // Load the calculator - levelCalc = new LevelPresenter(this, this.getPlugin()); + levelPresenter = new LevelPresenter(this, this.getPlugin()); // Start the top ten and register it for clicks topTen = new TopTen(this); registerListener(topTen); @@ -208,7 +216,7 @@ public class Level extends Addon { public long getInitialIslandLevel(Island island) { return levelsCache.containsKey(island.getOwner()) ? levelsCache.get(island.getOwner()).getInitialLevel(island.getWorld()) : 0L; } - + public Database getHandler() { return handler; } diff --git a/src/main/java/world/bentobox/level/LevelPresenter.java b/src/main/java/world/bentobox/level/LevelPresenter.java index 8edc82d..c4a5bd3 100644 --- a/src/main/java/world/bentobox/level/LevelPresenter.java +++ b/src/main/java/world/bentobox/level/LevelPresenter.java @@ -1,7 +1,11 @@ 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; @@ -10,7 +14,18 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.level.calculators.PlayerLevel; -class LevelPresenter { +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; @@ -41,16 +56,18 @@ class LevelPresenter { targetPlayer = plugin.getIslands().getOwner(world, targetPlayer); inTeam = true; } else { - sender.sendMessage("general.errors.player-has-no-island"); + if (sender != null) sender.sendMessage("general.errors.player-has-no-island"); return; } } // Player asking for their own island calc - if (inTeam || !sender.isPlayer() || sender.getUniqueId().equals(targetPlayer) || sender.isOp() || sender.hasPermission(permPrefix + "mod.info")) { + if (sender == null || inTeam || !sender.isPlayer() || sender.getUniqueId().equals(targetPlayer) || sender.isOp() || sender.hasPermission(permPrefix + "mod.info")) { // Newer better system - uses chunks - if (!onLevelWaitTime(sender) || levelWait <= 0 || sender.isOp() || sender.hasPermission(permPrefix + "mod.info")) { - sender.sendMessage("island.level.calculating"); - setLevelWaitTime(sender); + 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 @@ -58,11 +75,36 @@ class LevelPresenter { } } else { - // Asking for the level of another player - sender.sendMessage("island.level.island-level-is","[level]", String.valueOf(addon.getIslandLevel(world, targetPlayer))); + 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 * diff --git a/src/main/java/world/bentobox/level/TopTen.java b/src/main/java/world/bentobox/level/TopTen.java index 24b1576..157375b 100644 --- a/src/main/java/world/bentobox/level/TopTen.java +++ b/src/main/java/world/bentobox/level/TopTen.java @@ -28,11 +28,11 @@ import world.bentobox.level.objects.TopTenData; * */ public class TopTen implements Listener { - private final Level addon; + private 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; + private Database handler; public TopTen(Level addon) { this.addon = addon; @@ -42,6 +42,21 @@ public class TopTen implements Listener { 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); + } 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 * @@ -58,8 +73,8 @@ public class TopTen implements Listener { topTenList.get(world).setUniqueId(world.getName()); // Try and see if the player is online - Player player = addon.getServer().getPlayer(ownerUUID); - if (player != null && !player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(world) + ".intopten")) { + Player player = Bukkit.getServer().getPlayer(ownerUUID); + if (player != null && !player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(world) + "intopten")) { topTenList.get(world).remove(ownerUUID); return; } @@ -86,7 +101,7 @@ public class TopTen implements Listener { Map.Entry m = it.next(); UUID topTenUUID = m.getKey(); // Remove from TopTen if the player is online and has the permission - Player entry = addon.getServer().getPlayer(topTenUUID); + Player entry = Bukkit.getServer().getPlayer(topTenUUID); boolean show = true; if (entry != null) { if (!entry.hasPermission(permPrefix + "intopten")) { @@ -110,12 +125,12 @@ public class TopTen implements Listener { * @param asker - the asker of the top ten * @return PanelItem */ - private PanelItem getHead(int rank, Long level, UUID playerUUID, User asker, World world) { + private PanelItem getHead(int rank, long level, UUID playerUUID, User asker, World world) { final String name = addon.getPlayers().getName(playerUUID); List description = new ArrayList<>(); if (name != null) { description.add(asker.getTranslation("island.top.gui-heading", "[name]", name, "[rank]", String.valueOf(rank))); - description.add(asker.getTranslation("island.top.island-level","[level]", String.valueOf(level))); + 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)) { @@ -131,16 +146,14 @@ public class TopTen implements Listener { return builder.build(); } - public TopTenData getTopTenList(World world) { - return topTenList.get(world); - } - /** - * Loads all the top tens from the database + * Get the top ten list for this world + * @param world - world + * @return top ten data object */ - private void loadTopTen() { - topTenList = new HashMap<>(); - handler.loadObjects().forEach(tt -> topTenList.put(Bukkit.getWorld(tt.getUniqueId()), tt)); + public TopTenData getTopTenList(World world) { + topTenList.putIfAbsent(world, new TopTenData()); + return topTenList.get(world); } /** diff --git a/src/main/java/world/bentobox/level/calculators/CalcIslandLevel.java b/src/main/java/world/bentobox/level/calculators/CalcIslandLevel.java index a18ecef..a1b060b 100644 --- a/src/main/java/world/bentobox/level/calculators/CalcIslandLevel.java +++ b/src/main/java/world/bentobox/level/calculators/CalcIslandLevel.java @@ -12,7 +12,10 @@ import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.ChunkSnapshot; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.World; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Slab; import org.bukkit.scheduler.BukkitTask; import com.google.common.collect.HashMultiset; @@ -28,8 +31,8 @@ import world.bentobox.level.Level; public class CalcIslandLevel { - private static final int MAX_CHUNKS = 200; - private static final long SPEED = 1; + private final int MAX_CHUNKS; + private final long SPEED; private static final String LINE_BREAK = "=================================="; private boolean checking; private final BukkitTask task; @@ -38,12 +41,13 @@ public class CalcIslandLevel { private final Set> chunksToScan; private final Island island; - private final World world; private final Results result; private final Runnable onExit; // Copy the limits hash map private final HashMap limitCount; + private final World world; + private final List worlds; /** @@ -56,7 +60,17 @@ public class CalcIslandLevel { public CalcIslandLevel(final Level addon, final Island island, final Runnable onExit) { this.addon = addon; this.island = island; - this.world = island.getCenter().getWorld(); + this.world = island.getWorld(); + this.worlds = new ArrayList<>(); + this.worlds.add(world); + if (addon.getSettings().isNether()) { + World netherWorld = addon.getPlugin().getIWM().getNetherWorld(world); + if (netherWorld != null) this.worlds.add(netherWorld); + } + if (addon.getSettings().isEnd()) { + World endWorld = addon.getPlugin().getIWM().getEndWorld(world); + if (endWorld != null) this.worlds.add(endWorld); + } this.limitCount = new HashMap<>(addon.getSettings().getBlockLimits()); this.onExit = onExit; @@ -66,6 +80,9 @@ public class CalcIslandLevel { // Set the initial island handicap result.initialLevel = addon.getInitialIslandLevel(island); + SPEED = addon.getSettings().getUpdateTickDelay(); + MAX_CHUNKS = addon.getSettings().getChunksPerTick(); + // Get chunks to scan chunksToScan = getChunksToScan(island); @@ -85,12 +102,14 @@ public class CalcIslandLevel { // Add chunk snapshots to the list while (it.hasNext() && chunkSnapshot.size() < MAX_CHUNKS) { Pair pair = it.next(); - if (!world.isChunkLoaded(pair.x, pair.z)) { - world.loadChunk(pair.x, pair.z); - chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot()); - world.unloadChunk(pair.x, pair.z); - } else { - chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot()); + for (World world : worlds) { + if (!world.isChunkLoaded(pair.x, pair.z)) { + world.loadChunk(pair.x, pair.z); + chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot()); + world.unloadChunk(pair.x, pair.z); + } else { + chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot()); + } } it.remove(); } @@ -114,6 +133,10 @@ public class CalcIslandLevel { } private void scanChunk(ChunkSnapshot chunk) { + 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) { @@ -125,27 +148,31 @@ public class CalcIslandLevel { continue; } - for (int y = 0; y < island.getCenter().getWorld().getMaxHeight(); y++) { - Material blockData = chunk.getBlockType(x, y, z); - int seaHeight = addon.getPlugin().getIWM().getSeaHeight(world); + 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; - // Air is free - if (!blockData.equals(Material.AIR)) { - checkBlock(blockData, belowSeaLevel); + // 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, belowSeaLevel); + } } + checkBlock(blockData, belowSeaLevel); } } } } - private void checkBlock(Material md, boolean belowSeaLevel) { - int count = limitCount(md); + private void checkBlock(BlockData bd, boolean belowSeaLevel) { + int count = limitCount(bd.getMaterial()); if (belowSeaLevel) { result.underWaterBlockCount += count; - result.uwCount.add(md); + result.uwCount.add(bd.getMaterial()); } else { result.rawBlockCount += count; - result.mdCount.add(md); + result.mdCount.add(bd.getMaterial()); } } @@ -194,8 +221,7 @@ public class CalcIslandLevel { 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) { - Pair pair = new Pair<>(world.getBlockAt(x, 0, z).getChunk().getX(), world.getBlockAt(x, 0, z).getChunk().getZ()); - chunkSnapshot.add(pair); + chunkSnapshot.add(new Pair<>(x >> 4, z >> 4)); } } return chunkSnapshot; @@ -222,9 +248,6 @@ public class CalcIslandLevel { this.addon.getPlayers().getDeaths(this.world, this.island.getOwner()); } - // Just lazy check for min death count. - this.result.deathHandicap = Math.min(this.result.deathHandicap, this.addon.getSettings().getMaxDeaths()); - long blockAndDeathPoints = this.result.rawBlockCount; if (this.addon.getSettings().getDeathPenalty() > 0) @@ -308,10 +331,11 @@ public class CalcIslandLevel { if (addon.getSettings().getBlockValues().containsKey(type)) { // Specific value = addon.getSettings().getBlockValues().get(type); + + r.add(type.toString() + ":" + + String.format("%,d", en.getCount()) + " blocks x " + value + " = " + (value * en.getCount())); + total += (value * en.getCount()); } - 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); diff --git a/src/main/java/world/bentobox/level/calculators/PlayerLevel.java b/src/main/java/world/bentobox/level/calculators/PlayerLevel.java index dee9e5a..68a71e4 100644 --- a/src/main/java/world/bentobox/level/calculators/PlayerLevel.java +++ b/src/main/java/world/bentobox/level/calculators/PlayerLevel.java @@ -5,6 +5,7 @@ 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; @@ -34,7 +35,7 @@ public class PlayerLevel { private CalcIslandLevel calc; - public PlayerLevel(final Level addon, final Island island, final UUID targetPlayer, final User asker) { + 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(); @@ -70,7 +71,7 @@ public class PlayerLevel { // Save the results island.getMemberSet().forEach(m -> addon.setIslandLevel(world, m, results.getLevel())); // Display result if event is not cancelled - if (!ilce.isCancelled()) { + if (!ilce.isCancelled() && asker != null) { informPlayers(results); } } diff --git a/src/main/java/world/bentobox/level/commands/admin/AdminLevelCommand.java b/src/main/java/world/bentobox/level/commands/admin/AdminLevelCommand.java index a532acf..4840a04 100644 --- a/src/main/java/world/bentobox/level/commands/admin/AdminLevelCommand.java +++ b/src/main/java/world/bentobox/level/commands/admin/AdminLevelCommand.java @@ -6,17 +6,18 @@ 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 levelPlugin; + private final Level addon; - public AdminLevelCommand(Level levelPlugin, CompositeCommand parent) { + public AdminLevelCommand(Level addon, CompositeCommand parent) { super(parent, "level"); - this.levelPlugin = levelPlugin; + this.addon = addon; } @Override @@ -34,10 +35,10 @@ public class AdminLevelCommand extends CompositeCommand { // Convert name to a UUID final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); if (playerUUID == null) { - user.sendMessage("general.errors.unknown-player"); + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); return true; } else { - levelPlugin.calculateIslandLevel(getWorld(), user, playerUUID); + addon.calculateIslandLevel(getWorld(), user, playerUUID); } return true; } else { @@ -45,7 +46,7 @@ public class AdminLevelCommand extends CompositeCommand { 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/island/IslandLevelCommand.java b/src/main/java/world/bentobox/level/commands/island/IslandLevelCommand.java index 48314f9..58ada94 100644 --- a/src/main/java/world/bentobox/level/commands/island/IslandLevelCommand.java +++ b/src/main/java/world/bentobox/level/commands/island/IslandLevelCommand.java @@ -4,6 +4,7 @@ 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; @@ -31,7 +32,7 @@ public class IslandLevelCommand extends CompositeCommand { // Convert name to a UUID final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0)); if (playerUUID == null) { - user.sendMessage("general.errors.unknown-player"); + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); return true; } else if (user.getUniqueId().equals(playerUUID) ) { return this.calculateLevel(user); diff --git a/src/main/java/world/bentobox/level/config/Settings.java b/src/main/java/world/bentobox/level/config/Settings.java index e0f49dd..0c63609 100644 --- a/src/main/java/world/bentobox/level/config/Settings.java +++ b/src/main/java/world/bentobox/level/config/Settings.java @@ -1,6 +1,5 @@ package world.bentobox.level.config; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -14,6 +13,7 @@ import world.bentobox.level.Level; public class Settings { + private Level level; private boolean sumTeamDeaths; private Map blockLimits = new HashMap<>(); private Map blockValues = new HashMap<>(); @@ -22,27 +22,49 @@ public class Settings { private int deathpenalty; private long levelCost; private int levelWait; - private int maxDeaths; - private boolean islandResetDeathReset; - private boolean teamJoinDeathReset; - private List gameModes = new ArrayList<>(); + + /** + * Stores number of chunks that can be updated in single tick. + */ + private int chunksPerTick; + + /** + * Stores number of tick delay between each chunk loading. + */ + private long updateTickDelay; + + private List gameModes; + public Settings(Level level) { - + this.level = level; level.saveDefaultConfig(); // GameModes gameModes = level.getConfig().getStringList("game-modes"); + // Level calculation chunk load speed + this.setUpdateTickDelay(level.getConfig().getLong("updatetickdelay", 1)); + + if (this.getUpdateTickDelay() <= 0) + { + this.setUpdateTickDelay(1); + } + + // Level calculation chunk count per update + this.setChunksPerTick(level.getConfig().getInt("chunkspertick", 200)); + + if (this.getChunksPerTick() <= 0) + { + this.setChunksPerTick(200); + } + setLevelWait(level.getConfig().getInt("levelwait", 60)); if (getLevelWait() < 0) { setLevelWait(0); } setDeathpenalty(level.getConfig().getInt("deathpenalty", 0)); setSumTeamDeaths(level.getConfig().getBoolean("sumteamdeaths")); - setMaxDeaths(level.getConfig().getInt("maxdeaths", 10)); - setIslandResetDeathReset(level.getConfig().getBoolean("islandresetdeathreset", true)); - setTeamJoinDeathReset(level.getConfig().getBoolean("teamjoindeathreset", true)); setUnderWaterMultiplier(level.getConfig().getDouble("underwater", 1D)); setLevelCost(level.getConfig().getInt("levelcost", 100)); if (getLevelCost() < 1) { @@ -91,7 +113,7 @@ public class Settings { worldBlockValues.put(bWorld, values); } } else { - level.getLogger().severe(() -> "Level Addon: No such world : " + world); + level.getLogger().severe(() -> "Level Addon: No such world in config.yml : " + world); } } } @@ -182,44 +204,6 @@ public class Settings { private void setLevelWait(int levelWait) { this.levelWait = levelWait; } - /** - * TODO: Use max deaths - * @return the maxDeaths - */ - public final int getMaxDeaths() { - return maxDeaths; - } - /** - * @param maxDeaths the maxDeaths to set - */ - private void setMaxDeaths(int maxDeaths) { - this.maxDeaths = maxDeaths; - } - /** - * @return the islandResetDeathReset - */ - public final boolean isIslandResetDeathReset() { - return islandResetDeathReset; - } - /** - * @param islandResetDeathReset the islandResetDeathReset to set - */ - private void setIslandResetDeathReset(boolean islandResetDeathReset) { - this.islandResetDeathReset = islandResetDeathReset; - } - /** - * @return the teamJoinDeathReset - */ - public final boolean isTeamJoinDeathReset() { - return teamJoinDeathReset; - } - /** - * @param teamJoinDeathReset the teamJoinDeathReset to set - */ - private void setTeamJoinDeathReset(boolean teamJoinDeathReset) { - this.teamJoinDeathReset = teamJoinDeathReset; - } - /** * @return the worldBlockValues */ @@ -234,4 +218,73 @@ public class Settings { return gameModes; } + /** + * @return if the nether island should be included in the level calc or not + */ + public boolean isNether() { + return level.getConfig().getBoolean("nether"); + } + + /** + * @return if the end island should be included in the level calc or not + */ + public boolean isEnd() { + return level.getConfig().getBoolean("end"); + } + + /** + * @return true if level should be calculated on login + */ + public boolean isLogin() { + return level.getConfig().getBoolean("login"); + } + + /** + * @return true if levels should be shown in shorthand notation, e.g., 10,234 -> 10k + */ + public boolean isShortHand() { + return level.getConfig().getBoolean("shorthand"); + } + + + /** + * This method returns the number of chunks that can be processed at single tick. + * @return the value of chunksPerTick. + */ + public int getChunksPerTick() + { + return this.chunksPerTick; + } + + + /** + * This method sets the chunksPerTick value. + * @param chunksPerTick the chunksPerTick new value. + * + */ + public void setChunksPerTick(int chunksPerTick) + { + this.chunksPerTick = chunksPerTick; + } + + + /** + * This method returns the delay between each update call. + * @return the value of updateTickDelay. + */ + public long getUpdateTickDelay() + { + return this.updateTickDelay; + } + + + /** + * This method sets the updateTickDelay value. + * @param updateTickDelay the updateTickDelay new value. + * + */ + public void setUpdateTickDelay(long updateTickDelay) + { + this.updateTickDelay = updateTickDelay; + } } diff --git a/src/main/java/world/bentobox/level/listeners/IslandTeamListeners.java b/src/main/java/world/bentobox/level/listeners/IslandTeamListeners.java index dfe923e..047c0f6 100644 --- a/src/main/java/world/bentobox/level/listeners/IslandTeamListeners.java +++ b/src/main/java/world/bentobox/level/listeners/IslandTeamListeners.java @@ -100,6 +100,13 @@ public class IslandTeamListeners implements Listener { 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 217c0cd..3323ae0 100644 --- a/src/main/java/world/bentobox/level/listeners/JoinLeaveListener.java +++ b/src/main/java/world/bentobox/level/listeners/JoinLeaveListener.java @@ -28,6 +28,14 @@ public class JoinLeaveListener implements Listener { public void onPlayerJoin(PlayerJoinEvent e) { // Load player into cache addon.getLevelsData(e.getPlayer().getUniqueId()); + // If level calc on login is enabled, run through all the worlds and calculate the level + if (addon.getSettings().isLogin()) { + addon.getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> addon.getSettings().getGameModes().contains(gm.getDescription().getName())) + .forEach(gm -> { + addon.calculateIslandLevel(gm.getOverWorld(), null, e.getPlayer().getUniqueId()); + }); + } } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) diff --git a/src/main/java/world/bentobox/level/objects/LevelsData.java b/src/main/java/world/bentobox/level/objects/LevelsData.java index 45c4876..4e251a1 100644 --- a/src/main/java/world/bentobox/level/objects/LevelsData.java +++ b/src/main/java/world/bentobox/level/objects/LevelsData.java @@ -1,7 +1,7 @@ package world.bentobox.level.objects; -import java.util.HashMap; import java.util.Map; +import java.util.TreeMap; import java.util.UUID; import org.bukkit.World; @@ -20,12 +20,12 @@ public class LevelsData implements DataObject { * Map of world name and island level */ @Expose - private Map levels = new HashMap<>(); + private Map levels = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); /** * Map of world name to island initial level */ @Expose - private Map initialLevel = new HashMap<>(); + private Map initialLevel = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); public LevelsData() {} // For Bean loading @@ -89,7 +89,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(), level); } /** diff --git a/src/main/java/world/bentobox/level/objects/TopTenData.java b/src/main/java/world/bentobox/level/objects/TopTenData.java index 4ad4648..832c90f 100644 --- a/src/main/java/world/bentobox/level/objects/TopTenData.java +++ b/src/main/java/world/bentobox/level/objects/TopTenData.java @@ -24,6 +24,8 @@ public class TopTenData implements DataObject { private Map topTen = new LinkedHashMap<>(); public Map getTopTen() { + // Remove any entries that have level values less than 1 + topTen.values().removeIf(l -> l < 1); return topTen.entrySet().stream() .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(10) .collect(Collectors.toMap( @@ -36,12 +38,14 @@ public class TopTenData implements DataObject { @Override public String getUniqueId() { + // This is the world name return uniqueId; } @Override public void setUniqueId(String uniqueId) { - this.uniqueId = uniqueId; + // This is the world name - make it always lowercase + this.uniqueId = uniqueId.toLowerCase(); } /** diff --git a/src/main/java/world/bentobox/level/placeholders/LevelPlaceholder.java b/src/main/java/world/bentobox/level/placeholders/LevelPlaceholder.java index da61bf0..4bf27ee 100644 --- a/src/main/java/world/bentobox/level/placeholders/LevelPlaceholder.java +++ b/src/main/java/world/bentobox/level/placeholders/LevelPlaceholder.java @@ -1,6 +1,3 @@ -/** - * - */ package world.bentobox.level.placeholders; import world.bentobox.bentobox.api.addons.GameModeAddon; @@ -32,7 +29,7 @@ public class LevelPlaceholder implements PlaceholderReplacer { */ @Override public String onReplace(User user) { - return String.valueOf(addon.getIslandLevel(gm.getOverWorld(), user.getUniqueId())); + return addon.getLevelPresenter().getLevelString(addon.getIslandLevel(gm.getOverWorld(), user.getUniqueId())); } } diff --git a/src/main/java/world/bentobox/level/placeholders/TopTenNamePlaceholder.java b/src/main/java/world/bentobox/level/placeholders/TopTenNamePlaceholder.java index 593b18d..b0e7c67 100644 --- a/src/main/java/world/bentobox/level/placeholders/TopTenNamePlaceholder.java +++ b/src/main/java/world/bentobox/level/placeholders/TopTenNamePlaceholder.java @@ -1,6 +1,3 @@ -/** - * - */ package world.bentobox.level.placeholders; import java.util.Collection; diff --git a/src/main/java/world/bentobox/level/placeholders/TopTenPlaceholder.java b/src/main/java/world/bentobox/level/placeholders/TopTenPlaceholder.java index 27849e4..0da18d3 100644 --- a/src/main/java/world/bentobox/level/placeholders/TopTenPlaceholder.java +++ b/src/main/java/world/bentobox/level/placeholders/TopTenPlaceholder.java @@ -1,6 +1,3 @@ -/** - * - */ package world.bentobox.level.placeholders; import java.util.Collection; diff --git a/src/main/java/world/bentobox/level/requests/TopTenRequestHandler.java b/src/main/java/world/bentobox/level/requests/TopTenRequestHandler.java index d947717..bc32b52 100644 --- a/src/main/java/world/bentobox/level/requests/TopTenRequestHandler.java +++ b/src/main/java/world/bentobox/level/requests/TopTenRequestHandler.java @@ -16,26 +16,26 @@ import world.bentobox.level.objects.TopTenData; */ public class TopTenRequestHandler extends AddonRequestHandler { - /** - * The level addon field. - */ - private Level addon; + /** + * The level addon field. + */ + private Level addon; - /** - * This constructor creates a new TopTenRequestHandler instance. - * - * @param addon of type Level - */ - public TopTenRequestHandler(Level addon) { - super("top-ten-level"); - this.addon = addon; - } + /** + * This constructor creates a new TopTenRequestHandler instance. + * + * @param addon of type Level + */ + public TopTenRequestHandler(Level addon) { + super("top-ten-level"); + this.addon = addon; + } - /** - * @see AddonRequestHandler#handle(Map) - */ - @Override - public Object handle(Map map) { + /** + * @see {@link AddonRequestHandler#handle(Map)} + */ + @Override + public Object handle(Map map) { /* What we need in the map: @@ -47,14 +47,14 @@ public class TopTenRequestHandler extends AddonRequestHandler { - the map of top ten player UUIDs and their island levels. Can be less then 10. */ - if (map == null || map.isEmpty() - || map.get("world-name") == null || !(map.get("world-name") instanceof String) - || Bukkit.getWorld((String) map.get("world-name")) == null) { - return Collections.emptyMap(); - } + if (map == null || map.isEmpty() + || map.get("world-name") == null || !(map.get("world-name") instanceof String) + || Bukkit.getWorld((String) map.get("world-name")) == null) { + return Collections.emptyMap(); + } - // Null-point check. - TopTenData data = addon.getTopTen().getTopTenList(Bukkit.getWorld((String) map.get("world-name"))); - return data != null ? data.getTopTen() : Collections.emptyMap(); - } + // Null-point check. + TopTenData data = addon.getTopTen().getTopTenList(Bukkit.getWorld((String) map.get("world-name"))); + return data != null ? data.getTopTen() : Collections.emptyMap(); + } } diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index be3b9a9..6035359 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -1,6 +1,6 @@ name: Level main: world.bentobox.level.Level -version: ${version} +version: ${version}${build.number} softdepend: AcidIsland, BSkyBlock diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 834ab05..0891dc9 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -14,6 +14,21 @@ game-modes: # Players with the permission askyblock.island.multiplier.# will have their blocks # multiplied in value by that amount. +# 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 +# Warning: Enabling this mid-game will give players with an island a jump in +# island level. New islands will be correctly zeroed. +end: 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. @@ -25,6 +40,14 @@ levelcost: 100 # Cooldown between level requests in seconds levelwait: 60 +# Delay between each task that loads chunks for calculating island level. +# Increasing this will increase time to calculate island level. +updatetickdelay: 1 + +# Number of chunks that will be processed at the same tick. +# Decreasing this will increase time to calculate island level. +chunkspertick: 200 + # 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) @@ -33,15 +56,11 @@ deathpenalty: 100 # Sum team deaths - if true, all the teams deaths are summed # If false, only the leader's deaths counts sumteamdeaths: false -# Max deaths -# If player dies more than this, it doesn't count anymore -# Stops players from getting into an impossible situation -maxdeaths: 10 -# Reset deaths on island reset -islandresetdeathreset: true -# Reset deaths on team join -teamjoindeathreset: true +# For other death related settings, see the GameModeAddon's config.yml settings. +# Shorthand island level +# Shows large level values rounded down, e.g., 10,345 -> 10k +shorthand: false # This section lists the limits for any particular block. Blocks over this amount # are not counted. @@ -651,7 +670,7 @@ blocks: # If a block is not listed, the default value will be used # Prefix with world name worlds: - AcidIsland_world: + acidisland_world: SAND: 0 SANDSTONE: 0 ICE: 0 diff --git a/src/main/resources/locales/fr-FR.yml b/src/main/resources/locales/fr-FR.yml new file mode 100644 index 0000000..740436c --- /dev/null +++ b/src/main/resources/locales/fr-FR.yml @@ -0,0 +1,41 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +meta: + authors: + - plagoutte + +admin: + level: + parameters: "" + description: "calcule le niveau d'île d'un joueur" + top: + description: "affiche le top 10 des îles" + unknown-world: "&cMonde inconnu." + display: "&f[rank]. &a[name] &7- &b[level]" + +island: + level: + parameters: "[joueur]" + description: "calcule le niveau de votre île ou affiche le niveau d'un [joueur]" + calculating: "&aCalcul du niveau en cours..." + island-level-is: "&aLe niveau d'île est &b[level]" + required-points-to-next-level: "&a[points] points avant le prochain niveau" + deaths: "&c([number] morts)" + cooldown: "&cVous devez attendre &b[time] &csecondes avant de pouvoir re-faire cette action" + + top: + description: "affiche le top 10" + gui-title: "&aTop 10" + gui-heading: "&6[name]: &B[rank]" + island-level: "&BNiveau [level]" + warp-to: "&ATéléportation vers l'île de [name]" + + value: + description: "affiche la valeur d'un bloc" + success: "§7Valeur de ce bloc : §e[value]" + success-underwater: "§7Valeur de ce bloc en dessous du niveau de la mer : §e[value]" + empty-hand: "§cIl n'y a aucun bloc dans votre main" + no-value: "§cCet objet n'a pas de valeur." diff --git a/src/main/resources/locales/lv-LV.yml b/src/main/resources/locales/lv-LV.yml new file mode 100644 index 0000000..08b3621 --- /dev/null +++ b/src/main/resources/locales/lv-LV.yml @@ -0,0 +1,37 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +admin: + level: + parameters: "" + description: "aprēķina spēlētāja salas līmeni" + top: + description: "rādīt labākās 10 salas" + unknown-world: "&cNezināma pasaule!" + display: "&f[rank]. &a[name] &7- &b[level]" + +island: + level: + parameters: "[player]" + description: "aprēķina tavas salas līmeni, vai parāda spēlētāja [player] līmeni" + calculating: "&aAprēķina līmeni..." + island-level-is: "&aSalas līmenis ir &b[level]" + required-points-to-next-level: "&aNepieciešami [points] punkti, lai sasniegtu nākošo līmeni" + deaths: "&c([number] nāves)" + cooldown: "&cTev ir jāuzgaida &b[time]&c sekundes, lai vēlreiz aprēķinātu salas līmeni!" + + top: + description: "rādīt labākos 10" + gui-title: "&aLabākie 10" + gui-heading: "&6[name]: &B[rank]" + island-level: "&BLīmenis [level]" + warp-to: "&APārvietoties uz [name] salu." + + value: + description: "rādīt vērtību jebkuram blokam" + success: "&7Vērtība šim blokam ir: &e[value]" + success-underwater: "&7Vērtība šim blokam zem jūras līmeņa: &e[value]" + empty-hand: "&cTev nav bloks rokās." + no-value: "&cŠim blokam/priekšmetam nav vērtības." diff --git a/src/main/resources/locales/tr-TR.yml b/src/main/resources/locales/tr-TR.yml new file mode 100644 index 0000000..1089c4f --- /dev/null +++ b/src/main/resources/locales/tr-TR.yml @@ -0,0 +1,37 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +admin: + level: + parameters: "" + description: "Bir oyuncunun ada seviyesini hesapla" + top: + description: "Ilk 10 adayı sırala" + unknown-world: "&cBilinmeyen kelime" + display: "&f[rank]. &a[name] &7- &b[level]" + +island: + level: + parameters: "[player]" + description: "&7Kendi ada seviyeni hesapla veya başka oyuncunun ada seviyesini öğren" + calculating: "&aLevel hesaplanıyor..." + island-level-is: "&7Ada seviyesi &b[level]" + required-points-to-next-level: "&7Adayı yükseltmek için &a[points] &7Puan gerekiyor" + deaths: "&c(Ölümler: [number])" + cooldown: "&7Bunu tekrar yapmak için &b[time] &7beklemelisin" + + top: + description: "Ilk 10 adayı sırala" + gui-title: "&aIlk 10 Ada" + gui-heading: "&6Sıralama: &3[rank]" + island-level: "&7Seviye: &a[level]" + warp-to: "&a[name] &7oyuncusunun adasına ışınlanıyor" + + value: + description: "Herhangi bir bloğun değerini gösterir" + success: "&7Bu bloğun değeri: &e[value]" + success-underwater: "&7Deniz seviyesinin altındaki bu bloğun değeri: &e[value]" + empty-hand: "&cElinde hiç blok yok" + no-value: "&cBu eşyanın bir değeri yok." diff --git a/src/test/java/world/bentobox/level/LevelPresenterTest.java b/src/test/java/world/bentobox/level/LevelPresenterTest.java index 51293e5..01e33e6 100644 --- a/src/test/java/world/bentobox/level/LevelPresenterTest.java +++ b/src/test/java/world/bentobox/level/LevelPresenterTest.java @@ -1,5 +1,6 @@ package world.bentobox.level; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -10,6 +11,7 @@ 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; @@ -20,6 +22,7 @@ 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.Settings; /** * @author tastybento @@ -29,14 +32,17 @@ import world.bentobox.level.calculators.PlayerLevel; @PrepareForTest({Bukkit.class, LevelPresenter.class}) public class LevelPresenterTest { + @Mock private BentoBox plugin; + @Mock private Level addon; + @Mock private PlayerLevel pl; + @Mock + private Settings settings; @Before public void setUp() throws Exception { - plugin = mock(BentoBox.class); - addon = mock(Level.class); IslandWorldManager iwm = mock(IslandWorldManager.class); when(plugin.getIWM()).thenReturn(iwm); when(iwm.getPermissionPrefix(Mockito.any())).thenReturn("world"); @@ -48,10 +54,12 @@ public class LevelPresenterTest { when(im.inTeam(Mockito.any(), Mockito.any())).thenReturn(true); // team leader when(im.getOwner(Mockito.any(), Mockito.any())).thenReturn(UUID.randomUUID()); - - pl = mock(PlayerLevel.class); + + // Player level PowerMockito.whenNew(PlayerLevel.class).withAnyArguments().thenReturn(pl); + // Settings + when(addon.getSettings()).thenReturn(settings); } /** @@ -72,10 +80,32 @@ public class LevelPresenterTest { User sender = mock(User.class); UUID targetPlayer = UUID.randomUUID(); lp.calculateIslandLevel(world, sender, targetPlayer); - + Mockito.verify(sender).sendMessage("island.level.calculating"); - // Verify PlayerLevel was called - Mockito.verify(pl); } + /** + * 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/TopTenTest.java b/src/test/java/world/bentobox/level/TopTenTest.java new file mode 100644 index 0000000..8dd3807 --- /dev/null +++ b/src/test/java/world/bentobox/level/TopTenTest.java @@ -0,0 +1,280 @@ +package world.bentobox.level; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +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.Settings; +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; + private List topTen; + @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 Settings 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"); + 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); + assertTrue(tt.getTopTenList(world).getTopTen().get(ownerUUID) == 200L); + } + + @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); + assertTrue(tt.getTopTenList(world).getTopTen().get(ownerUUID) == 200L); + // 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/objects/TopTenDataTest.java b/src/test/java/world/bentobox/level/objects/TopTenDataTest.java new file mode 100644 index 0000000..a0da168 --- /dev/null +++ b/src/test/java/world/bentobox/level/objects/TopTenDataTest.java @@ -0,0 +1,114 @@ +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.After; +import org.junit.Before; +import org.junit.Test; + +/** + * @author tastybento + * + */ +public class TopTenDataTest { + + private Map topTen = new LinkedHashMap<>(); + private TopTenData ttd; + private UUID uuid = UUID.randomUUID(); + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // 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(); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * 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#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 -> { + assertTrue(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); + } + } + +}