Complete rewrite to enable pipelining.

Adds ability to scan chest contents.
This commit is contained in:
tastybento 2020-06-21 17:54:51 -07:00
parent c19ae41cbb
commit 08b7c99c3f
37 changed files with 1987 additions and 2499 deletions

View File

@ -65,7 +65,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>2.1.0</build.version>
<build.version>2.2.0</build.version>
</properties>
<!-- Profiles will allow to automatically change build version. -->

View File

@ -2,151 +2,53 @@ package world.bentobox.level;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.configuration.Config;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.calculators.CalcIslandLevel;
import world.bentobox.level.commands.admin.AdminLevelCommand;
import world.bentobox.level.commands.admin.AdminTopCommand;
import world.bentobox.level.commands.island.IslandLevelCommand;
import world.bentobox.level.commands.island.IslandTopCommand;
import world.bentobox.level.commands.island.IslandValueCommand;
import world.bentobox.level.calculators.IslandLevelCalculator;
import world.bentobox.level.calculators.Pipeliner;
import world.bentobox.level.commands.AdminLevelCommand;
import world.bentobox.level.commands.AdminLevelStatusCommand;
import world.bentobox.level.commands.AdminTopCommand;
import world.bentobox.level.commands.IslandLevelCommand;
import world.bentobox.level.commands.IslandTopCommand;
import world.bentobox.level.commands.IslandValueCommand;
import world.bentobox.level.config.BlockConfig;
import world.bentobox.level.config.ConfigSettings;
import world.bentobox.level.listeners.IslandTeamListeners;
import world.bentobox.level.listeners.IslandActivitiesListeners;
import world.bentobox.level.listeners.JoinLeaveListener;
import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.requests.LevelRequestHandler;
import world.bentobox.level.requests.TopTenRequestHandler;
/**
* Addon to BSkyBlock/AcidIsland that enables island level scoring and top ten functionality
* @author tastybento
*
*/
public class Level extends Addon {
private static Addon addon;
// Settings
private ConfigSettings settings;
private Config<ConfigSettings> configObject = new Config<>(this, ConfigSettings.class);
/**
* @param settings the settings to set
*/
public void setSettings(ConfigSettings settings) {
this.settings = settings;
}
// Database handler for level data
private Database<LevelsData> handler;
// A cache of island levels.
private Map<UUID, LevelsData> levelsCache;
// The Top Ten object
private TopTen topTen;
// Level calculator
private LevelPresenter levelPresenter;
private BlockConfig blockConfig;
/**
* Calculates a user's island
* @param world - the world where this island is
* @param user - the user who is asking, or null if none
* @param playerUUID - the target island member's UUID
*/
public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) {
levelPresenter.calculateIslandLevel(world, user, playerUUID);
}
/**
* Get level from cache for a player.
* @param targetPlayer - target player UUID
* @return Level of player or zero if player is unknown or UUID is null
*/
public long getIslandLevel(World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) return 0L;
LevelsData ld = getLevelsData(targetPlayer);
return ld == null ? 0L : ld.getLevel(world);
}
/**
* Load a player from the cache or database
* @param targetPlayer - UUID of target player
* @return LevelsData object or null if not found
*/
@Nullable
public LevelsData getLevelsData(@NonNull UUID targetPlayer) {
// Get from database if not in cache
if (!levelsCache.containsKey(targetPlayer) && handler.objectExists(targetPlayer.toString())) {
LevelsData ld = handler.loadObject(targetPlayer.toString());
if (ld != null) {
levelsCache.put(targetPlayer, ld);
} else {
handler.deleteID(targetPlayer.toString());
}
}
// Return cached value or null
return levelsCache.get(targetPlayer);
}
/**
* @return the settings
*/
public ConfigSettings getSettings() {
return settings;
}
public TopTen getTopTen() {
return topTen;
}
/**
* @return the levelPresenter
*/
public LevelPresenter getLevelPresenter() {
return levelPresenter;
}
@Override
public void onDisable(){
// Save the cache
if (levelsCache != null) {
save();
}
if (topTen != null) {
topTen.saveTopTen();
}
}
private Pipeliner pipeliner;
private LevelsManager manager;
@Override
public void onLoad() {
// Save the default config from config.yml
saveDefaultConfig();
// Load settings from config.yml. This will check if there are any issues with it too.
if (loadSettings()) {
// Disable
logError("Level settings could not load! Addon disabled.");
setState(State.DISABLED);
} else {
configObject.saveConfigObject(settings);
}
}
@ -154,41 +56,105 @@ public class Level extends Addon {
private boolean loadSettings() {
// Load settings again to get worlds
settings = configObject.loadConfigObject();
if (settings == null) {
// Disable
logError("Level settings could not load! Addon disabled.");
setState(State.DISABLED);
return false;
}
// Check for legacy blocks and limits etc.
if (getConfig().isConfigurationSection("blocks")
|| getConfig().isConfigurationSection("limits")
|| getConfig().isConfigurationSection("worlds")) {
logWarning("Converting old config.yml format - shifting blocks, limits and worlds to blockconfig.yml");
File blockConfigFile = new File(this.getDataFolder(), "blockconfig.yml");
if (blockConfigFile.exists()) {
logError("blockconfig.yml already exists! Saving config as blockconfig.yml.2");
blockConfigFile = new File(this.getDataFolder(), "blockconfig.yml.2");
}
YamlConfiguration blockConfig = new YamlConfiguration();
copyConfigSection(blockConfig, "limits");
copyConfigSection(blockConfig, "blocks");
copyConfigSection(blockConfig, "worlds");
try {
blockConfig.save(blockConfigFile);
} catch (IOException e) {
logError("Could not save! " + e.getMessage());
}
}
return true;
return settings == null;
}
private void copyConfigSection(YamlConfiguration blockConfig, String sectionName) {
if (!getConfig().isConfigurationSection(sectionName)) return;
ConfigurationSection section = getConfig().getConfigurationSection(sectionName);
for (String k:section.getKeys(true)) {
blockConfig.set(sectionName + "." + k, section.get(k));
@Override
public void onEnable() {
loadBlockSettings();
// Start pipeline
pipeliner = new Pipeliner(this);
// Start Manager
manager = new LevelsManager(this);
manager.loadTopTens();
// Register listeners
this.registerListener(new IslandActivitiesListeners(this));
this.registerListener(new JoinLeaveListener(this));
// Register commands for GameModes
getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName()))
.forEach(gm -> {
log("Level hooking into " + gm.getDescription().getName());
registerCommands(gm);
registerPlaceholders(gm);
});
// Register request handlers
registerRequestHandler(new LevelRequestHandler(this));
registerRequestHandler(new TopTenRequestHandler(this));
// Check if WildStackers is enabled on the server
// I only added support for counting blocks into the island level
// Someone else can PR if they want spawners added to the Leveling system :)
IslandLevelCalculator.stackersEnabled = Bukkit.getPluginManager().getPlugin("WildStacker") != null;
}
private void registerPlaceholders(GameModeAddon gm) {
if (getPlugin().getPlaceholdersManager() == null) return;
// Island Level
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_island_level",
user -> getManager().getIslandLevelString(gm.getOverWorld(), user.getUniqueId()));
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_points_to_next_level",
user -> getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId()));
// Visited Island Level
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_visited_island_level", user -> getVisitedIslandLevel(gm, user));
// Register Top Ten Placeholders
for (int i = 1; i < 11; i++) {
final int rank = i;
// Name
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_top_name_" + i, u -> getRankName(gm.getOverWorld(), rank));
// Level
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_top_value_" + i, u -> getRankLevel(gm.getOverWorld(), rank));
}
}
private String getRankName(World world, int rank) {
if (rank < 1) rank = 1;
if (rank > 10) rank = 10;
return getPlayers().getName(getManager().getTopTen(world, 10).keySet().stream().skip(rank - 1).limit(1).findFirst().orElse(null));
}
private String getRankLevel(World world, int rank) {
if (rank < 1) rank = 1;
if (rank > 10) rank = 10;
return getManager().formatLevel(getManager().getTopTen(world, 10).values().stream().skip(rank - 1).limit(1).findFirst().orElse(null));
}
private String getVisitedIslandLevel(GameModeAddon gm, User user) {
if (!gm.inWorld(user.getLocation())) return "";
return getIslands().getIslandAt(user.getLocation())
.map(island -> getManager().getIslandLevelString(gm.getOverWorld(), island.getOwner()))
.orElse("0");
}
private void registerCommands(GameModeAddon gm) {
gm.getAdminCommand().ifPresent(adminCommand -> {
new AdminLevelCommand(this, adminCommand);
new AdminTopCommand(this, adminCommand);
new AdminLevelStatusCommand(this, adminCommand);
});
gm.getPlayerCommand().ifPresent(playerCmd -> {
new IslandLevelCommand(this, playerCmd);
new IslandTopCommand(this, playerCmd);
new IslandValueCommand(this, playerCmd);
});
}
@Override
public void onDisable() {
if (manager != null) {
manager.save();
}
}
private void loadBlockSettings() {
@ -210,165 +176,6 @@ public class Level extends Addon {
}
@Override
public void onEnable() {
addon = this;
loadBlockSettings();
// Get the BSkyBlock database
// Set up the database handler to store and retrieve Island classes
// Note that these are saved by the BSkyBlock database
handler = new Database<>(this, LevelsData.class);
// Initialize the cache
levelsCache = new HashMap<>();
// Load the calculator
levelPresenter = new LevelPresenter(this, this.getPlugin());
// Start the top ten and register it for clicks
topTen = new TopTen(this);
registerListener(topTen);
// Register commands for GameModes
getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> settings
.getGameModes()
.contains(gm
.getDescription()
.getName()))
.forEach(gm -> {
log("Level hooking into " + gm.getDescription().getName());
registerCommands(gm);
// Register placeholders
if (getPlugin().getPlaceholdersManager() != null) {
registerPlaceholders(gm);
}
});
// Register new island listener
registerListener(new IslandTeamListeners(this));
registerListener(new JoinLeaveListener(this));
// Register request handlers
registerRequestHandler(new LevelRequestHandler(this));
registerRequestHandler(new TopTenRequestHandler(this));
// Check if WildStackers is enabled on the server
// I only added support for counting blocks into the island level
// Someone else can PR if they want spawners added to the Leveling system :)
CalcIslandLevel.stackersEnabled = Bukkit.getPluginManager().getPlugin("WildStacker") != null;
// Done
}
private void registerPlaceholders(GameModeAddon gm) {
// Island Level
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_island_level",
user -> getLevelPresenter().getLevelString(getIslandLevel(gm.getOverWorld(), user.getUniqueId())));
// Visited Island Level
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_visited_island_level", user -> getVisitedIslandLevel(gm, user));
// Top Ten
for (int i = 1; i <= 10; i++) {
final int rank = i;
// Value
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_top_value_" + rank, u -> String.valueOf(getTopTen().getTopTenLevel(gm.getOverWorld(), rank)));
// Name
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
gm.getDescription().getName().toLowerCase() + "_top_name_" + rank,
u -> getPlayers().getName(getTopTen().getTopTenUUID(gm.getOverWorld(), rank)));
}
}
private String getVisitedIslandLevel(GameModeAddon gm, User user) {
if (!gm.inWorld(user.getLocation())) return "";
return getIslands().getIslandAt(user.getLocation())
.map(island -> getLevelPresenter().getLevelString(getIslandLevel(gm.getOverWorld(), island.getOwner())))
.orElse("0");
}
private void registerCommands(GameModeAddon gm) {
gm.getAdminCommand().ifPresent(adminCommand -> {
new AdminLevelCommand(this, adminCommand);
new AdminTopCommand(this, adminCommand);
});
gm.getPlayerCommand().ifPresent(playerCmd -> {
new IslandLevelCommand(this, playerCmd);
new IslandTopCommand(this, playerCmd);
new IslandValueCommand(this, playerCmd);
});
}
/**
* Save the levels to the database
*/
private void save(){
// Remove any potential null values from the cache
levelsCache.values().removeIf(Objects::isNull);
levelsCache.values().forEach(handler::saveObjectAsync);
}
/**
* Sets the player's level to a value
* @param world - world
* @param targetPlayer - target player
* @param level - level
*/
public void setIslandLevel(World world, UUID targetPlayer, long level) {
if (world == null || targetPlayer == null) {
this.logError("Level: request to store a null " + world + " " + targetPlayer);
return;
}
LevelsData ld = getLevelsData(targetPlayer);
if (ld == null) {
ld = new LevelsData(targetPlayer, level, world);
} else {
ld.setLevel(world, level);
}
// Add to cache
levelsCache.put(targetPlayer, ld);
topTen.addEntry(world, targetPlayer, getIslandLevel(world, targetPlayer));
handler.saveObjectAsync(ld);
}
/**
* Zeros the initial island level
* @param island - island
* @param level - initial calculated island level
*/
public void setInitialIslandLevel(@NonNull Island island, long level) {
if (island.getWorld() == null || island.getOwner() == null) {
this.logError("Level: request to store a null (initial) " + island.getWorld() + " " + island.getOwner());
return;
}
setIslandLevel(island.getWorld(), island.getOwner(), 0L);
levelsCache.get(island.getOwner()).setInitialLevel(island.getWorld(), level);
}
/**
* Get the initial island level
* @param island - island
* @return level or 0 by default
*/
public long getInitialIslandLevel(@NonNull Island island) {
// Remove any potential null values from the cache
levelsCache.values().removeIf(Objects::isNull);
return levelsCache.containsKey(island.getOwner()) ? levelsCache.get(island.getOwner()).getInitialLevel(island.getWorld()) : 0L;
}
/**
* @return database handler
*/
@Nullable
public Database<LevelsData> getHandler() {
return handler;
}
public static Addon getInstance() {
return addon;
}
/**
* @return the blockConfig
@ -377,4 +184,34 @@ public class Level extends Addon {
return blockConfig;
}
/**
* @return the settings
*/
public ConfigSettings getSettings() {
return settings;
}
/**
* @return the pipeliner
*/
public Pipeliner getPipeliner() {
return pipeliner;
}
/**
* @return the manager
*/
public LevelsManager getManager() {
return manager;
}
/**
* Set the config settings - used for tests only
* @param configSettings - config settings
*/
void setSettings(ConfigSettings configSettings) {
this.settings = configSettings;
}
}

View File

@ -1,136 +0,0 @@
package world.bentobox.level;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import org.bukkit.World;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.level.calculators.PlayerLevel;
public class LevelPresenter {
private static final BigInteger THOUSAND = BigInteger.valueOf(1000);
private static final TreeMap<BigInteger, String> LEVELS;
static {
LEVELS = new TreeMap<>();
LEVELS.put(THOUSAND, "k");
LEVELS.put(THOUSAND.pow(2), "M");
LEVELS.put(THOUSAND.pow(3), "G");
LEVELS.put(THOUSAND.pow(4), "T");
}
private int levelWait;
private final Level addon;
private final BentoBox plugin;
// Level calc cool down
private final HashMap<UUID, Long> levelWaitTime = new HashMap<>();
public LevelPresenter(Level addon, BentoBox plugin) {
this.addon = addon;
this.plugin = plugin;
}
/**
* Calculates the island level
* @param world - world to check
* @param sender - asker of the level info
* @param targetPlayer - target player
*/
public void calculateIslandLevel(World world, final User sender, UUID targetPlayer) {
// Get permission prefix for this world
String permPrefix = plugin.getIWM().getPermissionPrefix(world);
// Check if target has island
boolean inTeam = false;
if (!plugin.getIslands().hasIsland(world, targetPlayer)) {
// Player may be in a team
if (plugin.getIslands().inTeam(world, targetPlayer)) {
targetPlayer = plugin.getIslands().getOwner(world, targetPlayer);
inTeam = true;
} else {
if (sender != null) sender.sendMessage("general.errors.player-has-no-island");
return;
}
}
// Player asking for their own island calc
if (sender == null || inTeam || !sender.isPlayer() || sender.getUniqueId().equals(targetPlayer) || sender.isOp() || sender.hasPermission(permPrefix + "mod.info")) {
// Newer better system - uses chunks
if (sender == null || !onLevelWaitTime(sender) || levelWait <= 0 || sender.isOp() || sender.hasPermission(permPrefix + "mod.info")) {
if (sender != null) {
sender.sendMessage("island.level.calculating");
setLevelWaitTime(sender);
}
new PlayerLevel(addon, plugin.getIslands().getIsland(world, targetPlayer), targetPlayer, sender);
} else {
// Cooldown
sender.sendMessage("island.level.cooldown", "[time]", String.valueOf(getLevelWaitTime(sender)));
}
} else {
long lvl = addon.getIslandLevel(world, targetPlayer);
sender.sendMessage("island.level.island-level-is","[level]", getLevelString(lvl));
}
}
/**
* Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k
* @param lvl - long value to represent
* @return string of the level.
*/
public String getLevelString(long lvl) {
String level = String.valueOf(lvl);
// Asking for the level of another player
if(addon.getSettings().isShorthand()) {
BigInteger levelValue = BigInteger.valueOf(lvl);
Map.Entry<BigInteger, String> stage = LEVELS.floorEntry(levelValue);
if (stage != null) { // level > 1000
// 1 052 -> 1.0k
// 1 527 314 -> 1.5M
// 3 874 130 021 -> 3.8G
// 4 002 317 889 -> 4.0T
level = new DecimalFormat("#.#").format(levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue()/1000.0) + stage.getValue();
}
}
return level;
}
/**
* Sets cool down for the level command
*
* @param user - user
*/
private void setLevelWaitTime(final User user) {
levelWaitTime.put(user.getUniqueId(), Calendar.getInstance().getTimeInMillis() + levelWait * 1000);
}
private boolean onLevelWaitTime(final User sender) {
if (levelWaitTime.containsKey(sender.getUniqueId())) {
return levelWaitTime.get(sender.getUniqueId()) > Calendar.getInstance().getTimeInMillis();
}
return false;
}
private long getLevelWaitTime(final User sender) {
if (levelWaitTime.containsKey(sender.getUniqueId())) {
if (levelWaitTime.get(sender.getUniqueId()) > Calendar.getInstance().getTimeInMillis()) {
return (levelWaitTime.get(sender.getUniqueId()) - Calendar.getInstance().getTimeInMillis()) / 1000;
}
return 0L;
}
return 0L;
}
}

View File

@ -0,0 +1,375 @@
package world.bentobox.level;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.World;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.events.addon.AddonEvent;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.calculators.Results;
import world.bentobox.level.events.IslandLevelCalculatedEvent;
import world.bentobox.level.events.IslandPreLevelEvent;
import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.objects.TopTenData;
public class LevelsManager {
private static final String INTOPTEN = "intopten";
private static final TreeMap<BigInteger, String> LEVELS;
private static final BigInteger THOUSAND = BigInteger.valueOf(1000);
static {
LEVELS = new TreeMap<>();
LEVELS.put(THOUSAND, "k");
LEVELS.put(THOUSAND.pow(2), "M");
LEVELS.put(THOUSAND.pow(3), "G");
LEVELS.put(THOUSAND.pow(4), "T");
}
private Level addon;
// Database handler for level data
private final Database<LevelsData> handler;
// A cache of island levels.
private final Map<UUID, LevelsData> levelsCache;
private final int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25};
private final Database<TopTenData> topTenHandler;
// Top ten lists
private Map<World,TopTenData> topTenLists;
public LevelsManager(Level addon) {
this.addon = addon;
// Get the BentoBox database
// Set up the database handler to store and retrieve data
// Note that these are saved by the BentoBox database
handler = new Database<>(addon, LevelsData.class);
// Top Ten handler
topTenHandler = new Database<>(addon, TopTenData.class);
// Initialize the cache
levelsCache = new HashMap<>();
// Initialize top ten lists
topTenLists = new HashMap<>();
}
private void addToTopTen(@NonNull World world, @NonNull UUID targetPlayer, long lv) {
topTenLists.computeIfAbsent(world, k -> new TopTenData(world));
if (hasTopTenPerm(world, targetPlayer)) {
topTenLists.get(world).getTopTen().put(targetPlayer, lv);
} else {
topTenLists.get(world).getTopTen().remove(targetPlayer);
}
}
/**
* Calculate the island level, set all island member's levels to the result and try to add the owner to the top ten
* @param targetPlayer - uuid of targeted player - owner or team member
* @param island - island to calculate
* @return completable future with the results of the calculation
*/
public CompletableFuture<Results> calculateLevel(UUID targetPlayer, Island island) {
CompletableFuture<Results> result = new CompletableFuture<>();
// Fire pre-level calc event
IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island);
Bukkit.getPluginManager().callEvent(e);
if (e.isCancelled()) {
return CompletableFuture.completedFuture(null);
}
// Add island to the pipeline
addon.getPipeliner().addIsland(island).thenAccept(r -> {
// Results are irrelevant because the island is unowned or deleted, or IslandLevelCalcEvent is cancelled
if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) {
result.complete(null);
}
// Save result
island.getMemberSet().forEach(uuid -> setIslandLevel(island.getWorld(), uuid, r.getLevel()));
addToTopTen(island.getWorld(), island.getOwner(), r.getLevel());
result.complete(r);
});
return result;
}
private boolean fireIslandLevelCalcEvent(UUID targetPlayer, Island island, Results results) {
// Fire post calculation event
IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results);
Bukkit.getPluginManager().callEvent(ilce);
// This exposes these values to plugins via the event
Map<String, Object> keyValues = new HashMap<>();
keyValues.put("eventName", "IslandLevelCalculatedEvent");
keyValues.put("targetPlayer", targetPlayer);
keyValues.put("islandUUID", island.getUniqueId());
keyValues.put("level", results.getLevel());
keyValues.put("pointsToNextLevel", results.getPointsToNextLevel());
keyValues.put("deathHandicap", results.getDeathHandicap());
keyValues.put("initialLevel", results.getInitialLevel());
new AddonEvent().builder().addon(addon).keyValues(keyValues).build();
results = ilce.getResults();
return ilce.isCancelled();
}
/**
* Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k
* @param lvl - long value to represent
* @return string of the level.
*/
public String formatLevel(long lvl) {
String level = String.valueOf(lvl);
// Asking for the level of another player
if(addon.getSettings().isShorthand()) {
BigInteger levelValue = BigInteger.valueOf(lvl);
Map.Entry<BigInteger, String> stage = LEVELS.floorEntry(levelValue);
if (stage != null) { // level > 1000
// 1 052 -> 1.0k
// 1 527 314 -> 1.5M
// 3 874 130 021 -> 3.8G
// 4 002 317 889 -> 4.0T
level = new DecimalFormat("#.#").format(levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue()/1000.0) + stage.getValue();
}
}
return level;
}
/**
* Displays the Top Ten list
* @param world - world
* @param user - the requesting player
*/
public void getGUI(World world, final User user) {
// Check world
Map<UUID, Long> topTen = getTopTen(world, 10);
PanelBuilder panel = new PanelBuilder()
.name(user.getTranslation("island.top.gui-title"))
.user(user);
int i = 0;
for (Entry<UUID, Long> m : topTen.entrySet()) {
panel.item(SLOTS[i], getHead(i + 1, m.getValue(), m.getKey(), user, world));
}
panel.build();
}
/**
* Get the head panel item
* @param rank - the top ten rank of this player/team. Can be used in the name of the island for vanity.
* @param level - the level of the island
* @param playerUUID - the UUID of the top ten player
* @param asker - the asker of the top ten
* @return PanelItem
*/
private PanelItem getHead(int rank, long level, UUID playerUUID, User asker, World world) {
final String name = addon.getPlayers().getName(playerUUID);
List<String> description = new ArrayList<>();
description.add(asker.getTranslation("island.top.gui-heading", "[name]", name, "[rank]", String.valueOf(rank)));
description.add(asker.getTranslation("island.top.island-level","[level]", formatLevel(level)));
if (addon.getIslands().inTeam(world, playerUUID)) {
List<String> memberList = new ArrayList<>();
for (UUID members : addon.getIslands().getMembers(world, playerUUID)) {
memberList.add(ChatColor.AQUA + addon.getPlayers().getName(members));
}
description.addAll(memberList);
}
PanelItemBuilder builder = new PanelItemBuilder()
.icon(name)
.name(name)
.description(description);
return builder.build();
}
/**
* Get the initial level of the island. Used to zero island levels
* @param island - island
* @return initial level of island
*/
public long getInitialLevel(Island island) {
@Nullable
LevelsData ld = getLevelsData(island.getOwner());
return ld == null ? 0 : ld.getInitialLevel(island.getWorld());
}
/**
* Get level from cache for a player.
* @param world - world where the island is
* @param targetPlayer - target player UUID
* @return Level of player or zero if player is unknown or UUID is null
*/
public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) return 0L;
LevelsData ld = getLevelsData(targetPlayer);
return ld == null ? 0L : ld.getLevel(world);
}
/**
* Returns a formatted string of the target player's island level
* @param world - world where the island is
* @param targetPlayer - target player's UUID
* @return Formatted level of player or zero if player is unknown or UUID is null
*/
public String getIslandLevelString(@NonNull World world, @Nullable UUID targetPlayer) {
return formatLevel(getIslandLevel(world, targetPlayer));
}
/**
* Load a player from the cache or database
* @param targetPlayer - UUID of target player
* @return LevelsData object or null if not found
*/
@Nullable
public LevelsData getLevelsData(@NonNull UUID targetPlayer) {
// Get from database if not in cache
if (!levelsCache.containsKey(targetPlayer) && handler.objectExists(targetPlayer.toString())) {
LevelsData ld = handler.loadObject(targetPlayer.toString());
if (ld != null) {
levelsCache.put(targetPlayer, ld);
} else {
handler.deleteID(targetPlayer.toString());
}
}
// Return cached value or null
return levelsCache.get(targetPlayer);
}
/**
* Get the number of points required until the next level since the last level calc
* @param world - world where the island is
* @param targetPlayer - target player UUID
* @return string with the number required or blank if the player is unknown
*/
public String getPointsToNextString(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) return "";
LevelsData ld = getLevelsData(targetPlayer);
return ld == null ? "" : String.valueOf(ld.getPointsToNextLevel(world));
}
/**
* Get the top ten for this world. Returns offline players or players with the intopten permission.
* @param world - world requested
* @param size - size of the top ten
* @return sorted top ten map
*/
public Map<UUID, Long> getTopTen(World world, int size) {
topTenLists.computeIfAbsent(world, k -> new TopTenData(k));
// Remove player from top ten if they are online and do not have the perm
topTenLists.get(world).getTopTen().keySet().removeIf(u -> !hasTopTenPerm(world, u));
// Return the sorted map
return Collections.unmodifiableMap(topTenLists.get(world).getTopTen().entrySet().stream()
.filter(l -> l.getValue() > 0)
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(size)
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)));
}
/**
* Checks if player has the correct top ten perm to have their level saved
* @param world
* @param targetPlayer
* @return
*/
boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) {
String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world);
boolean hasPerm = Bukkit.getPlayer(targetPlayer) != null && Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN);
return hasPerm;
}
/**
* Loads all the top tens from the database
*/
void loadTopTens() {
topTenLists = new HashMap<>();
topTenHandler.loadObjects().forEach(tt -> {
World world = Bukkit.getWorld(tt.getUniqueId());
if (world != null) {
topTenLists.put(world, tt);
addon.log("Loaded TopTen for " + world.getName());
// Update based on user data
for (UUID uuid : tt.getTopTen().keySet()) {
tt.getTopTen().compute(uuid, (k,v) -> v = updateLevel(k, world));
}
} else {
addon.logError("TopTen world '" + tt.getUniqueId() + "' is not known on server. You might want to delete this table. Skipping...");
}
});
}
/**
* Removes a player from a world's top ten and removes world from player's level data
* @param world - world
* @param uuid - the player's uuid
*/
public void removeEntry(World world, UUID uuid) {
if (levelsCache.containsKey(uuid)) {
levelsCache.get(uuid).remove(world);
// Save
handler.saveObjectAsync(levelsCache.get(uuid));
}
if (topTenLists.containsKey(world)) {
topTenLists.get(world).getTopTen().remove(uuid);
topTenHandler.saveObjectAsync(topTenLists.get(world));
}
}
/**
* Saves all player data and the top ten
*/
public void save() {
levelsCache.values().forEach(handler::saveObjectAsync);
topTenLists.values().forEach(topTenHandler::saveObjectAsync);
}
/**
* Set an initial island level for player
* @param island - the island to set. Must have a non-null world and owner
* @param lv - initial island level
*/
public void setInitialIslandLevel(@NonNull Island island, long lv) {
if (island.getOwner() == null || island.getWorld() == null) return;
levelsCache.computeIfAbsent(island.getOwner(), k -> new LevelsData(k)).setInitialLevel(island.getWorld(), lv);
handler.saveObjectAsync(levelsCache.get(island.getOwner()));
}
public void setIslandLevel(@NonNull World world, @NonNull UUID targetPlayer, long lv) {
levelsCache.computeIfAbsent(targetPlayer, k -> new LevelsData(targetPlayer)).setLevel(world, lv);
handler.saveObjectAsync(levelsCache.get(targetPlayer));
// Add to Top Ten
addToTopTen(world, targetPlayer, lv);
}
private Long updateLevel(UUID uuid, World world) {
if (handler.objectExists(uuid.toString())) {
@Nullable
LevelsData ld = handler.loadObject(uuid.toString());
if (ld != null) {
return ld.getLevel(world);
}
}
return 0L;
}
}

View File

@ -1,199 +0,0 @@
package world.bentobox.level;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
import world.bentobox.level.objects.TopTenData;
/**
* Handles all Top Ten List functions
*
* @author tastybento
*
*/
public class TopTen implements Listener {
private final Level addon;
// Top ten list of players
private Map<World,TopTenData> topTenList;
private final int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25};
private final Database<TopTenData> handler;
public TopTen(Level addon) {
this.addon = addon;
// Set up the database handler to store and retrieve the TopTenList class
// Note that these are saved in the BSkyBlock database
handler = new Database<>(addon, TopTenData.class);
loadTopTen();
}
/**
* Loads all the top tens from the database
*/
private void loadTopTen() {
topTenList = new HashMap<>();
handler.loadObjects().forEach(tt -> {
World world = Bukkit.getWorld(tt.getUniqueId());
if (world != null) {
topTenList.put(world, tt);
addon.log("Loaded TopTen for " + world.getName());
} else {
addon.logError("TopTen world " + tt.getUniqueId() + " is not known on server. Skipping...");
}
});
}
/**
* Adds a player to the top ten, if the level is good enough
*
* @param ownerUUID - owner UUID
* @param l - level
*/
public void addEntry(World world, UUID ownerUUID, long l) {
// Check if player is an island owner or not
if (!addon.getIslands().isOwner(world, ownerUUID)) {
return;
}
// Set up world data
topTenList.putIfAbsent(world, new TopTenData());
topTenList.get(world).setUniqueId(world.getName());
// Try and see if the player is online
Player player = Bukkit.getServer().getPlayer(ownerUUID);
if (player != null && !player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(world) + "intopten")) {
topTenList.get(world).remove(ownerUUID);
return;
}
topTenList.get(world).addLevel(ownerUUID, l);
}
/**
* Displays the Top Ten list
* @param world - world
* @param user - the requesting player
*/
public void getGUI(World world, final User user, String permPrefix) {
// Check world
topTenList.putIfAbsent(world, new TopTenData());
topTenList.get(world).setUniqueId(world.getName());
PanelBuilder panel = new PanelBuilder()
.name(user.getTranslation("island.top.gui-title"))
.user(user);
int i = 1;
Iterator<Entry<UUID, Long>> it = topTenList.get(world).getTopTen().entrySet().iterator();
while (it.hasNext()) {
Map.Entry<UUID, Long> m = it.next();
UUID topTenUUID = m.getKey();
// Remove from TopTen if the player is online and has the permission
Player entry = Bukkit.getServer().getPlayer(topTenUUID);
boolean show = true;
if (entry != null) {
if (!entry.hasPermission(permPrefix + "intopten")) {
it.remove();
show = false;
// Remove from Top Ten completely
topTenList.get(world).remove(topTenUUID);
}
}
if (show) {
panel.item(SLOTS[i-1], getHead(i, m.getValue(), topTenUUID, user, world));
if (i++ == 10) break;
}
}
panel.build();
}
/**
* Get the head panel item
* @param rank - the top ten rank of this player/team. Can be used in the name of the island for vanity.
* @param level - the level of the island
* @param playerUUID - the UUID of the top ten player
* @param asker - the asker of the top ten
* @return PanelItem
*/
private PanelItem getHead(int rank, long level, UUID playerUUID, User asker, World world) {
final String name = addon.getPlayers().getName(playerUUID);
List<String> description = new ArrayList<>();
description.add(asker.getTranslation("island.top.gui-heading", "[name]", name, "[rank]", String.valueOf(rank)));
description.add(asker.getTranslation("island.top.island-level","[level]", addon.getLevelPresenter().getLevelString(level)));
if (addon.getIslands().inTeam(world, playerUUID)) {
List<String> memberList = new ArrayList<>();
for (UUID members : addon.getIslands().getMembers(world, playerUUID)) {
memberList.add(ChatColor.AQUA + addon.getPlayers().getName(members));
}
description.addAll(memberList);
}
PanelItemBuilder builder = new PanelItemBuilder()
.icon(name)
.name(name)
.description(description);
return builder.build();
}
/**
* Get the top ten list for this world
* @param world - world
* @return top ten data object
*/
@NonNull
public TopTenData getTopTenList(World world) {
return topTenList.computeIfAbsent(world, k -> new TopTenData());
}
/**
* Get the UUID for this rank in this world
* @param world - world
* @param rank - rank between 1 and 10
* @return UUID or null
*/
@Nullable
public UUID getTopTenUUID(World world, int rank) {
return getTopTenList(world).getTopTenUUID(rank);
}
/**
* Get the island level for this rank in this world
* @param world - world
* @param rank - rank between 1 and 10
* @return level or 0
*/
public long getTopTenLevel(World world, int rank) {
return getTopTenList(world).getTopTenLevel(rank);
}
/**
* Removes ownerUUID from the top ten list
*
* @param ownerUUID - uuid to remove
*/
public void removeEntry(World world, UUID ownerUUID) {
topTenList.putIfAbsent(world, new TopTenData());
topTenList.get(world).setUniqueId(world.getName());
topTenList.get(world).remove(ownerUUID);
}
public void saveTopTen() {
topTenList.values().forEach(handler::saveObjectAsync);
}
}

View File

@ -1,534 +0,0 @@
package world.bentobox.level.calculators;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import com.bgsoftware.wildstacker.api.WildStackerAPI;
import com.bgsoftware.wildstacker.api.objects.StackedBarrel;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Slab;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Multisets;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.bentobox.util.Util;
import world.bentobox.level.Level;
public class CalcIslandLevel {
private static final String LINE_BREAK = "==================================";
public static final long MAX_AMOUNT = 10000;
public static Boolean stackersEnabled;
private final Level addon;
private final Set<Pair<Integer, Integer>> chunksToScan;
private final Island island;
private final Results result;
private final Runnable onExit;
// Copy the limits hash map
private final HashMap<Material, Integer> limitCount;
private final List<World> worlds;
private final World world;
private AtomicInteger count;
private int total;
private Queue<Chunk> q;
private int queueid;
/**
* Calculate the island's level
* Results are available in {@link CalcIslandLevel.Results}
* @param addon - Level addon
* @param island - island to be calculated
* @param onExit - what to run when done
*/
public CalcIslandLevel(final Level addon, final Island island, final Runnable onExit) {
this.addon = addon;
this.island = island;
this.world = island.getWorld();
this.limitCount = new HashMap<>(addon.getBlockConfig().getBlockLimits());
this.onExit = onExit;
this.worlds = new ArrayList<>();
this.worlds.add(world);
q = new LinkedList<>();
// Results go here
result = new Results();
// Set the initial island handicap
result.setInitialLevel(addon.getInitialIslandLevel(island));
// Get chunks to scan
chunksToScan = getChunksToScan(island);
count = new AtomicInteger();
// Total number of chunks to scan
total = chunksToScan.size();
// Add nether world scanning
if (addon.getSettings().isNether()) {
World netherWorld = addon.getPlugin().getIWM().getNetherWorld(world);
if (netherWorld != null) {
this.worlds.add(netherWorld);
total += chunksToScan.size();
}
}
// Add End world scanning
if (addon.getSettings().isEnd()) {
World endWorld = addon.getPlugin().getIWM().getEndWorld(world);
if (endWorld != null) {
this.worlds.add(endWorld);
total += chunksToScan.size();
}
}
queueid = Bukkit.getScheduler().scheduleSyncRepeatingTask(addon.getPlugin(), () -> {
for (int i = 0; i < addon.getSettings().getChunks(); i++) {
if (q.size() == 0) {
return;
}
Chunk c = q.remove();
getChunk(c);
}
}, addon.getSettings().getTaskDelay(), addon.getSettings().getTaskDelay());
chunksToScan.forEach(c -> worlds.forEach(w -> Util.getChunkAtAsync(w, c.x, c.z).thenAccept(this::addChunkQueue)));
}
private void addChunkQueue(Chunk ch) {
q.add(ch);
}
private void getChunk(Chunk ch) {
ChunkSnapshot snapShot = ch.getChunkSnapshot();
Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
this.scanChunk(snapShot, ch);
count.getAndIncrement();
if (count.get() == total) {
Bukkit.getScheduler().cancelTask(queueid);
this.tidyUp();
}
});
}
private void scanChunk(ChunkSnapshot chunk, Chunk physicalChunk) {
World chunkWorld = Bukkit.getWorld(chunk.getWorldName());
if (chunkWorld == null) return;
int maxHeight = chunkWorld.getMaxHeight();
for (int x = 0; x< 16; x++) {
// Check if the block coordinate is inside the protection zone and if not, don't count it
if (chunk.getX() * 16 + x < island.getMinProtectedX() || chunk.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) {
continue;
}
for (int z = 0; z < 16; z++) {
// Check if the block coordinate is inside the protection zone and if not, don't count it
if (chunk.getZ() * 16 + z < island.getMinProtectedZ() || chunk.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
continue;
}
for (int y = 0; y < maxHeight; y++) {
BlockData blockData = chunk.getBlockData(x, y, z);
int seaHeight = addon.getPlugin().getIWM().getSeaHeight(chunkWorld);
boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight;
// Slabs can be doubled, so check them twice
if (Tag.SLABS.isTagged(blockData.getMaterial())) {
Slab slab = (Slab)blockData;
if (slab.getType().equals(Slab.Type.DOUBLE)) {
checkBlock(blockData.getMaterial(), belowSeaLevel);
}
}
// Hook for Wild Stackers (Blocks Only)
if (stackersEnabled && blockData.getMaterial() == Material.CAULDRON) {
Block cauldronBlock = physicalChunk.getBlock(x, y, z);
if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) {
StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock);
int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock);
for (int _x = 0; _x < barrelAmt; _x++) {
checkBlock(barrel.getType(), belowSeaLevel);
}
}
}
checkBlock(blockData.getMaterial(), belowSeaLevel);
}
}
}
}
// Didnt see a reason to pass BlockData when all that's used was the material
private void checkBlock(Material mat, boolean belowSeaLevel) {
int count = limitCount(mat);
if (belowSeaLevel) {
result.underWaterBlockCount.addAndGet(count);
result.uwCount.add(mat);
} else {
result.rawBlockCount.addAndGet(count);
result.mdCount.add(mat);
}
}
/**
* Checks if a block has been limited or not and whether a block has any value or not
* @param md Material
* @return value of the block if can be counted
*/
private int limitCount(Material md) {
if (limitCount.containsKey(md)) {
int count = limitCount.get(md);
if (count > 0) {
limitCount.put(md, --count);
return getValue(md);
} else {
result.ofCount.add(md);
return 0;
}
}
return getValue(md);
}
/**
* Get value of a material
* World blocks trump regular block values
* @param md - Material to check
* @return value of a material
*/
private int getValue(Material md) {
Integer value = addon.getBlockConfig().getValue(world, md);
if (value == null) {
// Not in config
result.ncCount.add(md);
return 0;
}
return value;
}
/**
* Get a set of all the chunks in island
* @param island - island
* @return - set of pairs of x,z coordinates to check
*/
private Set<Pair<Integer, Integer>> getChunksToScan(Island island) {
Set<Pair<Integer, Integer>> chunkSnapshot = new HashSet<>();
for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 + 16); x += 16) {
for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 + 16); z += 16) {
chunkSnapshot.add(new Pair<>(x >> 4, z >> 4));
}
}
return chunkSnapshot;
}
private void tidyUp() {
// Finalize calculations
result.rawBlockCount.addAndGet((long)(result.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
// Set the death penalty
if (this.addon.getSettings().isSumTeamDeaths())
{
for (UUID uuid : this.island.getMemberSet())
{
this.result.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(this.world, uuid));
}
}
else
{
// At this point, it may be that the island has become unowned.
this.result.deathHandicap.set(this.island.getOwner() == null ? 0 :
this.addon.getPlayers().getDeaths(this.world, this.island.getOwner()));
}
long blockAndDeathPoints = this.result.rawBlockCount.get();
if (this.addon.getSettings().getDeathPenalty() > 0)
{
// Proper death penalty calculation.
blockAndDeathPoints -= this.result.deathHandicap.get() * this.addon.getSettings().getDeathPenalty();
}
this.result.level.set(calculateLevel(blockAndDeathPoints));
// Calculate how many points are required to get to the next level
long nextLevel = this.result.level.get();
long blocks = blockAndDeathPoints;
while (nextLevel < this.result.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) {
nextLevel = calculateLevel(++blocks);
}
this.result.pointsToNextLevel.set(blocks - blockAndDeathPoints);
// Report
result.report = getReport();
// All done.
if (onExit != null) {
Bukkit.getScheduler().runTask(addon.getPlugin(), onExit);
}
}
private long calculateLevel(long blockAndDeathPoints) {
String calcString = addon.getSettings().getLevelCalc();
String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", String.valueOf(this.addon.getSettings().getLevelCost()));
return (long)eval(withValues) - this.island.getLevelHandicap() - result.initialLevel.get();
}
private List<String> getReport() {
List<String> reportLines = new ArrayList<>();
// provide counts
reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld()) + " at " + Util.xyz(island.getCenter().toVector()));
reportLines.add("Island owner UUID = " + island.getOwner());
reportLines.add("Total block value count = " + String.format("%,d",result.rawBlockCount.get()));
reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc());
reportLines.add("Level cost = " + addon.getSettings().getLevelCost());
reportLines.add("Deaths handicap = " + result.deathHandicap.get());
reportLines.add("Initial island level = " + (0L - result.initialLevel.get()));
reportLines.add("Level calculated = " + result.level.get());
reportLines.add(LINE_BREAK);
int total = 0;
if (!result.uwCount.isEmpty()) {
reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier() + ") value");
reportLines.add("Total number of underwater blocks = " + String.format("%,d",result.uwCount.size()));
reportLines.addAll(sortedReport(total, result.uwCount));
}
reportLines.add("Regular block count");
reportLines.add("Total number of blocks = " + String.format("%,d",result.mdCount.size()));
reportLines.addAll(sortedReport(total, result.mdCount));
reportLines.add("Blocks not counted because they exceeded limits: " + String.format("%,d",result.ofCount.size()));
Iterable<Multiset.Entry<Material>> entriesSortedByCount = result.ofCount.entrySet();
Iterator<Entry<Material>> it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement());
String explain = ")";
if (limit == null) {
Material generic = type.getElement();
limit = addon.getBlockConfig().getBlockLimits().get(generic);
explain = " - All types)";
}
reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks (max " + limit + explain);
}
reportLines.add(LINE_BREAK);
reportLines.add("Blocks on island that are not in config.yml");
reportLines.add("Total number = " + String.format("%,d",result.ncCount.size()));
entriesSortedByCount = result.ncCount.entrySet();
it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks");
}
reportLines.add(LINE_BREAK);
return reportLines;
}
private Collection<String> sortedReport(int total, Multiset<Material> MaterialCount) {
Collection<String> r = new ArrayList<>();
Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(MaterialCount).entrySet();
for (Entry<Material> en : entriesSortedByCount) {
Material type = en.getElement();
int value = getValue(type);
r.add(type.toString() + ":"
+ String.format("%,d", en.getCount()) + " blocks x " + value + " = " + (value * en.getCount()));
total += (value * en.getCount());
}
r.add("Subtotal = " + total);
r.add(LINE_BREAK);
return r;
}
/**
* @return the result
*/
public Results getResult() {
return result;
}
/**
* Results class
*
*/
public class Results {
private List<String> report;
private final Multiset<Material> mdCount = HashMultiset.create();
private final Multiset<Material> uwCount = HashMultiset.create();
private final Multiset<Material> ncCount = HashMultiset.create();
private final Multiset<Material> ofCount = HashMultiset.create();
// AtomicLong and AtomicInteger must be used because they are changed by multiple concurrent threads
private AtomicLong rawBlockCount = new AtomicLong(0);
private AtomicLong underWaterBlockCount = new AtomicLong(0);
private AtomicLong level = new AtomicLong(0);
private AtomicInteger deathHandicap = new AtomicInteger(0);
private AtomicLong pointsToNextLevel = new AtomicLong(0);
private AtomicLong initialLevel = new AtomicLong(0);
/**
* @return the deathHandicap
*/
public int getDeathHandicap() {
return deathHandicap.get();
}
/**
* @return the report
*/
public List<String> getReport() {
return report;
}
/**
* Set level
* @param level - level
*/
public void setLevel(long level) {
this.level.set(level);
}
/**
* @return the level
*/
public long getLevel() {
return level.get();
}
/**
* @return the pointsToNextLevel
*/
public long getPointsToNextLevel() {
return pointsToNextLevel.get();
}
public long getInitialLevel() {
return initialLevel.get();
}
public void setInitialLevel(long initialLevel) {
this.initialLevel.set(initialLevel);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Results [report=" + report + ", mdCount=" + mdCount + ", uwCount=" + uwCount + ", ncCount="
+ ncCount + ", ofCount=" + ofCount + ", rawBlockCount=" + rawBlockCount + ", underWaterBlockCount="
+ underWaterBlockCount + ", level=" + level + ", deathHandicap=" + deathHandicap
+ ", pointsToNextLevel=" + pointsToNextLevel + ", initialLevel=" + initialLevel + "]";
}
}
private static double eval(final String str) {
return new Object() {
int pos = -1, ch;
void nextChar() {
ch = (++pos < str.length()) ? str.charAt(pos) : -1;
}
boolean eat(int charToEat) {
while (ch == ' ') nextChar();
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
double parse() {
nextChar();
double x = parseExpression();
if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
return x;
}
// Grammar:
// expression = term | expression `+` term | expression `-` term
// term = factor | term `*` factor | term `/` factor
// factor = `+` factor | `-` factor | `(` expression `)`
// | number | functionName factor | factor `^` factor
double parseExpression() {
double x = parseTerm();
for (;;) {
if (eat('+')) x += parseTerm(); // addition
else if (eat('-')) x -= parseTerm(); // subtraction
else return x;
}
}
double parseTerm() {
double x = parseFactor();
for (;;) {
if (eat('*')) x *= parseFactor(); // multiplication
else if (eat('/')) x /= parseFactor(); // division
else return x;
}
}
double parseFactor() {
if (eat('+')) return parseFactor(); // unary plus
if (eat('-')) return -parseFactor(); // unary minus
double x;
int startPos = this.pos;
if (eat('(')) { // parentheses
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
x = Double.parseDouble(str.substring(startPos, this.pos));
} else if (ch >= 'a' && ch <= 'z') { // functions
while (ch >= 'a' && ch <= 'z') nextChar();
String func = str.substring(startPos, this.pos);
x = parseFactor();
switch (func) {
case "sqrt":
x = Math.sqrt(x);
break;
case "sin":
x = Math.sin(Math.toRadians(x));
break;
case "cos":
x = Math.cos(Math.toRadians(x));
break;
case "tan":
x = Math.tan(Math.toRadians(x));
break;
default:
throw new RuntimeException("Unknown function: " + func);
}
} else {
throw new RuntimeException("Unexpected: " + (char)ch);
}
if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation
return x;
}
}.parse();
}
}

View File

@ -1,10 +1,535 @@
package world.bentobox.level.calculators;
public interface IslandLevelCalculator {
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Container;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Slab;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.eclipse.jdt.annotation.NonNull;
import com.bgsoftware.wildstacker.api.WildStackerAPI;
import com.bgsoftware.wildstacker.api.objects.StackedBarrel;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Multisets;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.bentobox.util.Util;
import world.bentobox.level.Level;
public class IslandLevelCalculator {
private static final String LINE_BREAK = "==================================";
public static final long MAX_AMOUNT = 10000;
public static Boolean stackersEnabled;
/**
* @return the results of the island calculation
* Method to evaluate a mathematical equation
* @param str - equation to evaluate
* @return value of equation
*/
Results getResult();
private static double eval(final String str) {
return new Object() {
int pos = -1, ch;
boolean eat(int charToEat) {
while (ch == ' ') nextChar();
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
void nextChar() {
ch = (++pos < str.length()) ? str.charAt(pos) : -1;
}
double parse() {
nextChar();
double x = parseExpression();
if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
return x;
}
// Grammar:
// expression = term | expression `+` term | expression `-` term
// term = factor | term `*` factor | term `/` factor
// factor = `+` factor | `-` factor | `(` expression `)`
// | number | functionName factor | factor `^` factor
double parseExpression() {
double x = parseTerm();
for (;;) {
if (eat('+')) x += parseTerm(); // addition
else if (eat('-')) x -= parseTerm(); // subtraction
else return x;
}
}
double parseFactor() {
if (eat('+')) return parseFactor(); // unary plus
if (eat('-')) return -parseFactor(); // unary minus
double x;
int startPos = this.pos;
if (eat('(')) { // parentheses
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
x = Double.parseDouble(str.substring(startPos, this.pos));
} else if (ch >= 'a' && ch <= 'z') { // functions
while (ch >= 'a' && ch <= 'z') nextChar();
String func = str.substring(startPos, this.pos);
x = parseFactor();
switch (func) {
case "sqrt":
x = Math.sqrt(x);
break;
case "sin":
x = Math.sin(Math.toRadians(x));
break;
case "cos":
x = Math.cos(Math.toRadians(x));
break;
case "tan":
x = Math.tan(Math.toRadians(x));
break;
default:
throw new RuntimeException("Unknown function: " + func);
}
} else {
throw new RuntimeException("Unexpected: " + (char)ch);
}
if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation
return x;
}
double parseTerm() {
double x = parseFactor();
for (;;) {
if (eat('*')) x *= parseFactor(); // multiplication
else if (eat('/')) x /= parseFactor(); // division
else return x;
}
}
}.parse();
}
private final Level addon;
private final Queue<Pair<Integer, Integer>> chunksToCheck;
private final Island island;
private final HashMap<Material, Integer> limitCount;
private final CompletableFuture<Results> r;
private final Results results;
/**
* Constructor to get the level for an island
* @param addon - Level addon
* @param island - the island to scan
* @param r - completeable result that will be completed when the calculation is complete
*/
public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Results> r) {
this.addon = addon;
this.island = island;
this.r = r;
results = new Results();
chunksToCheck = getChunksToScan(island);
this.limitCount = new HashMap<>(addon.getBlockConfig().getBlockLimits());
}
/**
* Calculate the level based on the raw points
* @param blockAndDeathPoints - raw points counted on island
* @return level of island
*/
private long calculateLevel(long blockAndDeathPoints) {
String calcString = addon.getSettings().getLevelCalc();
String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", String.valueOf(this.addon.getSettings().getLevelCost()));
return (long)eval(withValues) - this.island.getLevelHandicap() - results.initialLevel.get();
}
/**
* Adds value to the results based on the material and whether the block is below sea level or not
* @param mat - material of the block
* @param belowSeaLevel - true if below sea level
*/
private void checkBlock(Material mat, boolean belowSeaLevel) {
int count = limitCount(mat);
if (belowSeaLevel) {
results.underWaterBlockCount.addAndGet(count);
results.uwCount.add(mat);
} else {
results.rawBlockCount.addAndGet(count);
results.mdCount.add(mat);
}
}
/**
* Get a set of all the chunks in island
* @param island - island
* @return - set of pairs of x,z coordinates to check
*/
private Queue<Pair<Integer, Integer>> getChunksToScan(Island island) {
Queue<Pair<Integer, Integer>> chunkQueue = new ConcurrentLinkedQueue<>();
for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() * 2 + 16); x += 16) {
for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() * 2 + 16); z += 16) {
chunkQueue.add(new Pair<>(x >> 4, z >> 4));
}
}
return chunkQueue;
}
/**
* @return the island
*/
public Island getIsland() {
return island;
}
/**
* Get the completable result for this calculation
* @return the r
*/
public CompletableFuture<Results> getR() {
return r;
}
/**
* Get the full analysis report
* @return a list of lines
*/
private List<String> getReport() {
List<String> reportLines = new ArrayList<>();
// provide counts
reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld()) + " at " + Util.xyz(island.getCenter().toVector()));
reportLines.add("Island owner UUID = " + island.getOwner());
reportLines.add("Total block value count = " + String.format("%,d",results.rawBlockCount.get()));
reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc());
reportLines.add("Level cost = " + addon.getSettings().getLevelCost());
reportLines.add("Deaths handicap = " + results.deathHandicap.get());
reportLines.add("Initial island level = " + (0L - addon.getManager().getInitialLevel(island)));
reportLines.add("Level calculated = " + addon.getManager().getIslandLevel(island.getWorld(), island.getOwner()));
reportLines.add(LINE_BREAK);
int total = 0;
if (!results.uwCount.isEmpty()) {
reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier() + ") value");
reportLines.add("Total number of underwater blocks = " + String.format("%,d",results.uwCount.size()));
reportLines.addAll(sortedReport(total, results.uwCount));
}
reportLines.add("Regular block count");
reportLines.add("Total number of blocks = " + String.format("%,d",results.mdCount.size()));
reportLines.addAll(sortedReport(total, results.mdCount));
reportLines.add("Blocks not counted because they exceeded limits: " + String.format("%,d",results.ofCount.size()));
Iterable<Multiset.Entry<Material>> entriesSortedByCount = results.ofCount.entrySet();
Iterator<Entry<Material>> it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement());
String explain = ")";
if (limit == null) {
Material generic = type.getElement();
limit = addon.getBlockConfig().getBlockLimits().get(generic);
explain = " - All types)";
}
reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks (max " + limit + explain);
}
reportLines.add(LINE_BREAK);
reportLines.add("Blocks on island that are not in config.yml");
reportLines.add("Total number = " + String.format("%,d",results.ncCount.size()));
entriesSortedByCount = results.ncCount.entrySet();
it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks");
}
reportLines.add(LINE_BREAK);
return reportLines;
}
/**
* @return the results
*/
public Results getResults() {
return results;
}
/**
* Get value of a material
* World blocks trump regular block values
* @param md - Material to check
* @return value of a material
*/
private int getValue(Material md) {
Integer value = addon.getBlockConfig().getValue(island.getWorld(), md);
if (value == null) {
// Not in config
results.ncCount.add(md);
return 0;
}
return value;
}
/**
* Get a chunk async
* @param world - the world where the chunk is
* @param env - the environment
* @param x - chunk x coordinate
* @param z - chunk z coordinate
* @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether
*/
private CompletableFuture<Chunk> getWorldChunk(@NonNull World world, Environment env, int x, int z) {
switch (env) {
case NETHER:
if (addon.getSettings().isNether()) {
World nether = addon.getPlugin().getIWM().getNetherWorld(island.getWorld());
if (nether != null) {
return Util.getChunkAtAsync(nether, x, z, false);
}
}
// There is no chunk to scan, so return a null chunk
return CompletableFuture.completedFuture(null);
case THE_END:
if (addon.getSettings().isEnd()) {
World end = addon.getPlugin().getIWM().getEndWorld(island.getWorld());
if (end != null) {
return Util.getChunkAtAsync(end, x, z, false);
}
}
// There is no chunk to scan, so return a null chunk
return CompletableFuture.completedFuture(null);
default:
return Util.getChunkAtAsync(world, x, z, false);
}
}
/**
* Checks if a block has been limited or not and whether a block has any value or not
* @param md Material
* @return value of the block if can be counted
*/
private int limitCount(Material md) {
if (limitCount.containsKey(md)) {
int count = limitCount.get(md);
if (count > 0) {
limitCount.put(md, --count);
return getValue(md);
} else {
results.ofCount.add(md);
return 0;
}
}
return getValue(md);
}
/**
* Count the blocks on the island
* @param result - the CompletableFuture that should be completed when this scan is done
* @param chunk - the chunk to scan
*/
private void scanAsync(CompletableFuture<Boolean> result, Chunk chunk) {
// Get a thread-safe snapshot of the chunk
ChunkSnapshot chunkSnapshot = chunk.getChunkSnapshot();
for (int x = 0; x< 16; x++) {
// Check if the block coordinate is inside the protection zone and if not, don't count it
if (chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || chunkSnapshot.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) {
continue;
}
for (int z = 0; z < 16; z++) {
// Check if the block coordinate is inside the protection zone and if not, don't count it
if (chunkSnapshot.getZ() * 16 + z < island.getMinProtectedZ() || chunkSnapshot.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
continue;
}
// Only count to the highest block in the world for some optimization
for (int y = 0; y < chunkSnapshot.getHighestBlockYAt(x, z); y++) {
BlockData blockData = chunkSnapshot.getBlockData(x, y, z);
int seaHeight = addon.getPlugin().getIWM().getSeaHeight(island.getWorld());
boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight;
// Slabs can be doubled, so check them twice
if (Tag.SLABS.isTagged(blockData.getMaterial())) {
Slab slab = (Slab)blockData;
if (slab.getType().equals(Slab.Type.DOUBLE)) {
checkBlock(blockData.getMaterial(), belowSeaLevel);
}
}
// Hook for Wild Stackers (Blocks Only) - this has to use the real chunk
if (stackersEnabled && blockData.getMaterial() == Material.CAULDRON) {
Block cauldronBlock = chunk.getBlock(x, y, z);
if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) {
StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock);
int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock);
for (int _x = 0; _x < barrelAmt; _x++) {
checkBlock(barrel.getType(), belowSeaLevel);
}
}
}
// Add the value of the block's material
checkBlock(blockData.getMaterial(), belowSeaLevel);
}
}
}
// Chunk finished
if (chunk.getWorld().getEnvironment().equals(Environment.NORMAL) && chunksToCheck.isEmpty()) {
// This was the last chunk
tidyUp();
}
// Complete the future - this must go back onto the primary thread to exit async otherwise subsequent actions will be async
Bukkit.getScheduler().runTask(addon.getPlugin(),() -> result.complete(true));
}
/**
* Scan all containers in a chunk and count their blocks
* @param chunk - the chunk to scan
*/
private void scanChests(Chunk chunk) {
// Count blocks in chests
for (BlockState bs : chunk.getTileEntities()) {
if (bs instanceof Container) {
Inventory inv = ((Container)bs).getSnapshotInventory();
for (ItemStack i : inv) {
if (i != null && i.getType().isBlock()) {
for (int c = 0; c < i.getAmount(); c++) {
checkBlock(i.getType(), false);
}
}
}
}
}
}
/**
* Scan the chunk chests and count the blocks
* @param chunk - the chunk to scan
* @return future that completes when the scan is done and supplies a boolean that will be true if the scan was successful, false if not
*/
private CompletableFuture<Boolean> scanChunk(@NonNull Chunk chunk) {
if (chunk == null) {
return CompletableFuture.completedFuture(false);
}
// Scan chests
if (addon.getSettings().isIncludeChests()) {
scanChests(chunk);
}
// Count blocks in chunk
CompletableFuture<Boolean> result = new CompletableFuture<>();
Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> scanAsync(result, chunk));
return result;
}
/**
* Scan the next chunk on the island
* @return completable boolean future that will be true if more chunks are left to be scanned, and false if not
*/
public CompletableFuture<Boolean> scanNextChunk() {
if (chunksToCheck.isEmpty()) {
// This should not be needed, but just in case
return CompletableFuture.completedFuture(false);
}
// Retrieve and remove from the queue
Pair<Integer, Integer> p = chunksToCheck.poll();
// Set up the result
CompletableFuture<Boolean> result = new CompletableFuture<>();
// Get chunks and scan
getWorldChunk(island.getWorld(), Environment.THE_END, p.x, p.z).thenAccept(endChunk ->
scanChunk(endChunk).thenAccept(b ->
getWorldChunk(island.getWorld(), Environment.NETHER, p.x, p.z).thenAccept(netherChunk ->
scanChunk(netherChunk).thenAccept(b2 ->
getWorldChunk(island.getWorld(), Environment.NORMAL, p.x, p.z).thenAccept(normalChunk ->
scanChunk(normalChunk).thenAccept(b3 ->
// Complete the result now that all chunks have been scanned
result.complete(!chunksToCheck.isEmpty()))))
)
)
);
return result;
}
private Collection<String> sortedReport(int total, Multiset<Material> MaterialCount) {
Collection<String> r = new ArrayList<>();
Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(MaterialCount).entrySet();
for (Entry<Material> en : entriesSortedByCount) {
Material type = en.getElement();
int value = getValue(type);
r.add(type.toString() + ":"
+ String.format("%,d", en.getCount()) + " blocks x " + value + " = " + (value * en.getCount()));
total += (value * en.getCount());
}
r.add("Subtotal = " + total);
r.add(LINE_BREAK);
return r;
}
private void tidyUp() {
// Finalize calculations
results.rawBlockCount.addAndGet((long)(results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
// Set the death penalty
if (this.addon.getSettings().isSumTeamDeaths())
{
for (UUID uuid : this.island.getMemberSet())
{
this.results.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(island.getWorld(), uuid));
}
}
else
{
// At this point, it may be that the island has become unowned.
this.results.deathHandicap.set(this.island.getOwner() == null ? 0 :
this.addon.getPlayers().getDeaths(island.getWorld(), this.island.getOwner()));
}
long blockAndDeathPoints = this.results.rawBlockCount.get();
if (this.addon.getSettings().getDeathPenalty() > 0)
{
// Proper death penalty calculation.
blockAndDeathPoints -= this.results.deathHandicap.get() * this.addon.getSettings().getDeathPenalty();
}
this.results.level.set(calculateLevel(blockAndDeathPoints));
// Calculate how many points are required to get to the next level
long nextLevel = this.results.level.get();
long blocks = blockAndDeathPoints;
while (nextLevel < this.results.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) {
nextLevel = calculateLevel(++blocks);
}
this.results.pointsToNextLevel.set(blocks - blockAndDeathPoints);
// Report
results.report = getReport();
// All done.
}
}

View File

@ -0,0 +1,100 @@
package world.bentobox.level.calculators;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.Level;
/**
* A pipeliner that will process one island at a time
* @author tastybento
*
*/
public class Pipeliner {
private final Queue<IslandLevelCalculator> processQueue;
private final BukkitTask task;
private boolean inProcess;
private final Level addon;
/**
* Construct the pipeliner
*/
public Pipeliner(Level addon) {
this.addon = addon;
processQueue = new ConcurrentLinkedQueue<>();
// Loop continuously - check every tick if there is an island to scan
task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> {
if (!BentoBox.getInstance().isEnabled()) {
cancel();
return;
}
// One island at a time
if (inProcess || processQueue.isEmpty()) return;
IslandLevelCalculator iD = processQueue.poll();
// Ignore deleted or unonwed islands
if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned()) return;
// Start the process
inProcess = true;
// Start the scanning of a island with the first chunk
scanChunk(iD);
}, 1L, 1L);
}
private void cancel() {
task.cancel();
}
public int getIslandsInQueue() {
return processQueue.size();
}
/**
* Scans one chunk of an island and adds the results to a results object
* @param iD
*/
private void scanChunk(IslandLevelCalculator iD) {
if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned()) {
// Island is deleted, so finish early with nothing
inProcess = false;
iD.getR().complete(null);
return;
}
// Scan the next chunk
iD.scanNextChunk().thenAccept(r -> {
if (!Bukkit.isPrimaryThread()) {
addon.getPlugin().logError("scanChunk not on Primary Thread!");
}
if (r) {
// scanNextChunk returns true if there are more chunks to scan
scanChunk(iD);
} else {
// Done
inProcess = false;
iD.getR().complete(iD.getResults());
}
});
}
/**
* Adds an island to the scanning queue
* @param island - the island to scan
*
*/
public CompletableFuture<Results> addIsland(Island island) {
CompletableFuture<Results> r = new CompletableFuture<>();
processQueue.add(new IslandLevelCalculator(addon, island, r));
return r;
}
}

View File

@ -1,106 +0,0 @@
package world.bentobox.level.calculators;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.World;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.events.addon.AddonEvent;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.Level;
import world.bentobox.level.calculators.CalcIslandLevel.Results;
import world.bentobox.level.event.IslandLevelCalculatedEvent;
import world.bentobox.level.event.IslandPreLevelEvent;
/**
* Gets the player's island level. For admin or players
* @author tastybento
*
*/
public class PlayerLevel {
private final Level addon;
private final Island island;
private final World world;
private final User asker;
private final UUID targetPlayer;
private final long oldLevel;
private CalcIslandLevel calc;
public PlayerLevel(final Level addon, final Island island, final UUID targetPlayer, @Nullable final User asker) {
this.addon = addon;
this.island = island;
this.world = island.getCenter().getWorld();
this.asker = asker;
this.targetPlayer = targetPlayer;
this.oldLevel = addon.getIslandLevel(world, targetPlayer);
// Fire pre-level calc event
IslandPreLevelEvent e = new IslandPreLevelEvent(targetPlayer, island);
addon.getServer().getPluginManager().callEvent(e);
if (!e.isCancelled()) {
// Calculate if not cancelled
calc = new CalcIslandLevel(addon, island, this::fireIslandLevelCalcEvent);
}
}
private void fireIslandLevelCalcEvent() {
// Fire post calculation event
IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, calc.getResult());
addon.getServer().getPluginManager().callEvent(ilce);
// This exposes these values to plugins via the event
Map<String, Object> keyValues = new HashMap<>();
keyValues.put("eventName", "IslandLevelCalculatedEvent");
keyValues.put("targetPlayer", targetPlayer);
keyValues.put("islandUUID", island.getUniqueId());
keyValues.put("level", calc.getResult().getLevel());
keyValues.put("pointsToNextLevel", calc.getResult().getPointsToNextLevel());
keyValues.put("deathHandicap", calc.getResult().getDeathHandicap());
keyValues.put("initialLevel", calc.getResult().getInitialLevel());
new AddonEvent().builder().addon(addon).keyValues(keyValues).build();
Results results = ilce.getResults();
// Save the results
island.getMemberSet().forEach(m -> addon.setIslandLevel(world, m, results.getLevel()));
// Display result if event is not cancelled
if (!ilce.isCancelled() && asker != null) {
informPlayers(results);
}
}
private void informPlayers(Results results) {
// Tell the asker
asker.sendMessage("island.level.island-level-is", "[level]", String.valueOf(addon.getIslandLevel(world, targetPlayer)));
// Console
if (!asker.isPlayer()) {
results.getReport().forEach(asker::sendRawMessage);
return;
}
// Player
if (addon.getSettings().getDeathPenalty() != 0) {
asker.sendMessage("island.level.deaths", "[number]", String.valueOf(results.getDeathHandicap()));
}
// Send player how many points are required to reach next island level
if (results.getPointsToNextLevel() >= 0 && results.getPointsToNextLevel() < CalcIslandLevel.MAX_AMOUNT) {
asker.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(results.getPointsToNextLevel()));
}
// Tell other team members
if (addon.getIslandLevel(world, targetPlayer) != oldLevel) {
island.getMemberSet().stream()
.filter(u -> !u.equals(asker.getUniqueId()))
.forEach(m -> User.getInstance(m).sendMessage("island.level.island-level-is", "[level]", String.valueOf(addon.getIslandLevel(world, targetPlayer))));
}
}
}

View File

@ -1,78 +1,33 @@
package world.bentobox.level.calculators;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.bukkit.Material;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
/**
* Results class
*
*/
public class Results {
private int deathHandicap = 0;
private long initialLevel = 0;
private long level = 0;
private final Multiset<Material> mdCount = HashMultiset.create();
private final Multiset<Material> ncCount = HashMultiset.create();
private final Multiset<Material> ofCount = HashMultiset.create();
private long pointsToNextLevel = 0;
private long rawBlockCount = 0;
private List<String> report = new ArrayList<>();
private long underWaterBlockCount = 0;
private final Multiset<Material> uwCount = HashMultiset.create();
List<String> report;
final Multiset<Material> mdCount = HashMultiset.create();
final Multiset<Material> uwCount = HashMultiset.create();
final Multiset<Material> ncCount = HashMultiset.create();
final Multiset<Material> ofCount = HashMultiset.create();
// AtomicLong and AtomicInteger must be used because they are changed by multiple concurrent threads
AtomicLong rawBlockCount = new AtomicLong(0);
AtomicLong underWaterBlockCount = new AtomicLong(0);
AtomicLong level = new AtomicLong(0);
AtomicInteger deathHandicap = new AtomicInteger(0);
AtomicLong pointsToNextLevel = new AtomicLong(0);
AtomicLong initialLevel = new AtomicLong(0);
/**
* @return the deathHandicap
*/
public int getDeathHandicap() {
return deathHandicap;
}
public long getInitialLevel() {
return initialLevel;
}
/**
* @return the level
*/
public long getLevel() {
return level;
}
/**
* @return the mdCount
*/
public Multiset<Material> getMdCount() {
return mdCount;
}
/**
* @return the ncCount
*/
public Multiset<Material> getNcCount() {
return ncCount;
}
/**
* @return the ofCount
*/
public Multiset<Material> getOfCount() {
return ofCount;
}
/**
* @return the pointsToNextLevel
*/
public long getPointsToNextLevel() {
return pointsToNextLevel;
}
/**
* @return the rawBlockCount
*/
public long getRawBlockCount() {
return rawBlockCount;
return deathHandicap.get();
}
/**
@ -81,82 +36,32 @@ public class Results {
public List<String> getReport() {
return report;
}
/**
* @return the underWaterBlockCount
*/
public long getUnderWaterBlockCount() {
return underWaterBlockCount;
}
/**
* @return the uwCount
*/
public Multiset<Material> getUwCount() {
return uwCount;
}
/**
* @param deathHandicap the deathHandicap to set
*/
public void setDeathHandicap(int deathHandicap) {
this.deathHandicap = deathHandicap;
}
public void setInitialLevel(long initialLevel) {
this.initialLevel = initialLevel;
}
/**
* Set level
* @param level - level
*/
public void setLevel(int level) {
this.level = level;
}
/**
* @param level the level to set
*/
public void setLevel(long level) {
this.level = level;
this.level.set(level);
}
/**
* @return the level
*/
public long getLevel() {
return level.get();
}
/**
* @return the pointsToNextLevel
*/
public long getPointsToNextLevel() {
return pointsToNextLevel.get();
}
/**
* @param pointsToNextLevel the pointsToNextLevel to set
*/
public void setPointsToNextLevel(long pointsToNextLevel) {
this.pointsToNextLevel = pointsToNextLevel;
public long getInitialLevel() {
return initialLevel.get();
}
/**
* @param rawBlockCount the rawBlockCount to set
*/
public void setRawBlockCount(long rawBlockCount) {
this.rawBlockCount = rawBlockCount;
}
/**
* @param report the report to set
*/
public void setReport(List<String> report) {
this.report = report;
}
/**
* @param underWaterBlockCount the underWaterBlockCount to set
*/
public void setUnderWaterBlockCount(long underWaterBlockCount) {
this.underWaterBlockCount = underWaterBlockCount;
}
/**
* Add to death handicap
* @param deaths - number to add
*/
public void addToDeathHandicap(int deaths) {
this.deathHandicap += deaths;
public void setInitialLevel(long initialLevel) {
this.initialLevel.set(initialLevel);
}
/* (non-Javadoc)
@ -164,9 +69,9 @@ public class Results {
*/
@Override
public String toString() {
return "Results [report=" + report + ", mdCount=" + mdCount + ", uwCount=" + getUwCount() + ", ncCount="
return "Results [report=" + report + ", mdCount=" + mdCount + ", uwCount=" + uwCount + ", ncCount="
+ ncCount + ", ofCount=" + ofCount + ", rawBlockCount=" + rawBlockCount + ", underWaterBlockCount="
+ getUnderWaterBlockCount() + ", level=" + level + ", deathHandicap=" + deathHandicap
+ underWaterBlockCount + ", level=" + level + ", deathHandicap=" + deathHandicap
+ ", pointsToNextLevel=" + pointsToNextLevel + ", initialLevel=" + initialLevel + "]";
}

View File

@ -1,23 +1,18 @@
package world.bentobox.level.commands.admin;
package world.bentobox.level.commands;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.util.Util;
import world.bentobox.level.Level;
public class AdminLevelCommand extends CompositeCommand {
private final Level addon;
public class AdminLevelCommand extends IslandLevelCommand {
public AdminLevelCommand(Level addon, CompositeCommand parent) {
super(parent, "level");
this.addon = addon;
super(addon, parent);
}
@Override
@ -28,25 +23,6 @@ public class AdminLevelCommand extends CompositeCommand {
this.setDescription("admin.level.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() == 1) {
// Asking for another player's level?
// Convert name to a UUID
final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0));
if (playerUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return true;
} else {
addon.calculateIslandLevel(getWorld(), user, playerUUID);
}
return true;
} else {
showHelp(this, user);
return false;
}
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";

View File

@ -0,0 +1,30 @@
package world.bentobox.level.commands;
import java.util.List;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.level.Level;
public class AdminLevelStatusCommand extends CompositeCommand {
private final Level addon;
public AdminLevelStatusCommand(Level addon, CompositeCommand parent) {
super(parent, "levelstatus");
this.addon = addon;
}
@Override
public void setup() {
this.setPermission("admin.levelstatus");
this.setOnlyPlayer(false);
this.setDescription("admin.levelstatus.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
user.sendRawMessage("Islands in queue: " + addon.getPipeliner().getIslandsInQueue());
return true;
}
}

View File

@ -1,4 +1,4 @@
package world.bentobox.level.commands.admin;
package world.bentobox.level.commands;
import java.util.List;
import java.util.Map;
@ -13,10 +13,10 @@ public class AdminTopCommand extends CompositeCommand {
private final Level levelPlugin;
public AdminTopCommand(Level levelPlugin, CompositeCommand parent) {
public AdminTopCommand(Level addon, CompositeCommand parent) {
super(parent, "top", "topten");
this.levelPlugin = levelPlugin;
new AdminTopRemoveCommand(levelPlugin, this);
this.levelPlugin = addon;
new AdminTopRemoveCommand(addon, this);
}
@Override
@ -28,8 +28,9 @@ public class AdminTopCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List<String> args) {
user.sendMessage("island.top.gui-title");
int rank = 0;
for (Map.Entry<UUID, Long> topTen : levelPlugin.getTopTen().getTopTenList(getWorld()).getTopTen().entrySet()) {
for (Map.Entry<UUID, Long> topTen : levelPlugin.getManager().getTopTen(getWorld(), 10).entrySet()) {
Island island = getPlugin().getIslands().getIsland(getWorld(), topTen.getKey());
if (island != null) {
rank++;

View File

@ -1,6 +1,8 @@
package world.bentobox.level.commands.admin;
package world.bentobox.level.commands;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
@ -44,12 +46,20 @@ public class AdminTopRemoveCommand extends CompositeCommand {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
addon.getTopTen().getTopTenList(getWorld()).remove(target.getUniqueId());
addon.getManager().removeEntry(getWorld(), target.getUniqueId());
user.sendMessage("general.success");
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
return Optional.of(addon.getManager().getTopTen(getWorld(), 10).keySet().stream().map(addon.getPlayers()::getName)
.filter(n -> !n.isEmpty()).collect(Collectors.toList()));
}
}

View File

@ -0,0 +1,108 @@
package world.bentobox.level.commands;
import java.util.List;
import java.util.UUID;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.Level;
public class IslandLevelCommand extends CompositeCommand {
private final Level addon;
public IslandLevelCommand(Level addon, CompositeCommand parent) {
super(parent, "level");
this.addon = addon;
}
@Override
public void setup() {
this.setPermission("island.level");
this.setParametersHelp("island.level.parameters");
this.setDescription("island.level.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (!args.isEmpty()) {
// Asking for another player's level?
// Convert name to a UUID
final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0));
if (playerUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return true;
}
// Ops, console and admin perms can request and calculate other player levels
if (!user.isPlayer() || user.isOp() || user.hasPermission(this.getPermissionPrefix() + "admin.level")) {
return scanIsland(user, playerUUID);
}
// Request for another player's island level
if (!user.getUniqueId().equals(playerUUID) ) {
user.sendMessage("island.level.island-level-is", "[level]", addon.getManager().getIslandLevelString(getWorld(), playerUUID));
return true;
}
}
// Self request
// Check player cooldown
int coolDown = this.addon.getSettings().getLevelWait();
if (coolDown > 0) {
// Check cool down
if (checkCooldown(user)) return false;
// Set cool down
setCooldown(user.getUniqueId(), coolDown);
}
// Self level request
return scanIsland(user, user.getUniqueId());
}
private boolean scanIsland(User user, UUID playerUUID) {
Island island = getIslands().getIsland(getWorld(), playerUUID);
if (island != null) {
user.sendMessage("island.level.calculating");
int inQueue = addon.getPipeliner().getIslandsInQueue();
if (inQueue > 1) {
user.sendMessage("island.level.in-queue", TextVariables.NUMBER, String.valueOf(inQueue + 1));
}
// Get the old level
long oldLevel = addon.getManager().getIslandLevel(getWorld(), playerUUID);
addon.getManager().calculateLevel(playerUUID, island).thenAccept(results -> {
if (results == null) return; // island was deleted or become unowned
if (user.isPlayer()) {
user.sendMessage("island.level.island-level-is", "[level]", addon.getManager().getIslandLevelString(getWorld(), playerUUID));
// Player
if (addon.getSettings().getDeathPenalty() != 0) {
user.sendMessage("island.level.deaths", "[number]", String.valueOf(results.getDeathHandicap()));
}
// Send player how many points are required to reach next island level
if (results.getPointsToNextLevel() >= 0 && results.getPointsToNextLevel() < 10000) {
user.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(results.getPointsToNextLevel()));
}
// Tell other team members
if (results.getLevel() != oldLevel) {
island.getMemberSet().stream()
.filter(u -> !u.equals(user.getUniqueId()))
.forEach(m -> User.getInstance(m).sendMessage("island.level.island-level-is", "[level]", addon.getManager().getIslandLevelString(getWorld(), playerUUID)));
}
} else {
results.getReport().forEach(BentoBox.getInstance()::log);
}
});
return true;
} else {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
}
}

View File

@ -1,4 +1,4 @@
package world.bentobox.level.commands.island;
package world.bentobox.level.commands;
import java.util.List;
@ -8,11 +8,11 @@ import world.bentobox.level.Level;
public class IslandTopCommand extends CompositeCommand {
private final Level plugin;
private final Level addon;
public IslandTopCommand(Level plugin, CompositeCommand parent) {
public IslandTopCommand(Level addon, CompositeCommand parent) {
super(parent, "top", "topten");
this.plugin = plugin;
this.addon = addon;
}
@Override
@ -24,7 +24,7 @@ public class IslandTopCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List<String> list) {
plugin.getTopTen().getGUI(getWorld(), user, getPermissionPrefix());
addon.getManager().getGUI(getWorld(), user);
return true;
}
}

View File

@ -1,14 +1,15 @@
package world.bentobox.level.commands.island;
package world.bentobox.level.commands;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.PlayerInventory;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.level.Level;
import java.util.List;
public class IslandValueCommand extends CompositeCommand {
private final Level addon;

View File

@ -1,73 +0,0 @@
package world.bentobox.level.commands.island;
import java.util.List;
import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.level.Level;
public class IslandLevelCommand extends CompositeCommand {
private final Level levelPlugin;
public IslandLevelCommand(Level levelPlugin, CompositeCommand parent) {
super(parent, "level");
this.levelPlugin = levelPlugin;
}
@Override
public void setup() {
this.setPermission("island.level");
this.setParametersHelp("island.level.parameters");
this.setDescription("island.level.description");
this.setOnlyPlayer(true);
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (!args.isEmpty()) {
// Asking for another player's level?
// Convert name to a UUID
final UUID playerUUID = getPlugin().getPlayers().getUUID(args.get(0));
if (playerUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return true;
} else if (user.getUniqueId().equals(playerUUID) ) {
return this.calculateLevel(user);
} else {
user.sendMessage("island.level.island-level-is", "[level]", String.valueOf(levelPlugin.getIslandLevel(getWorld(), playerUUID)));
return true;
}
} else {
return this.calculateLevel(user);
}
}
/**
* This method calls island level calculation if it is allowed by cooldown.
* @param user User which island level must be calculated.
* @return True if le
*/
private boolean calculateLevel(User user)
{
int coolDown = this.levelPlugin.getSettings().getLevelWait();
if (coolDown > 0 && this.checkCooldown(user, null))
{
return false;
}
// Self level request
this.levelPlugin.calculateIslandLevel(getWorld(), user, user.getUniqueId());
if (coolDown > 0)
{
this.setCooldown(user.getUniqueId(), null, coolDown);
}
return true;
}
}

View File

@ -1,37 +1,23 @@
package world.bentobox.level.config;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.configuration.ConfigComment;
import world.bentobox.bentobox.api.configuration.ConfigEntry;
import world.bentobox.bentobox.api.configuration.ConfigObject;
import world.bentobox.bentobox.api.configuration.StoreAt;
import world.bentobox.level.Level;
@StoreAt(filename="config.yml", path="addons/Level")
@ConfigComment("Level Configuration [version]")
@ConfigComment("")
public class ConfigSettings implements ConfigObject {
@ConfigComment("")
@ConfigComment("Game Mode Addons")
@ConfigComment("Level will hook into these game mode addons. Don't forget to set any world-specific")
@ConfigComment("block values below!")
@ConfigEntry(path = "game-modes")
private List<String> gameModes = Arrays.asList("BSkyBlock","AcidIsland","CaveBlock");
@ConfigComment("")
@ConfigComment("Performance settings")
@ConfigComment("Level is very processor-intensive, so these settings may need to be tweaked to optimize for your server")
@ConfigComment("Delay between each task that loads chunks for calculating levels")
@ConfigComment("Increasing this will slow down level calculations but reduce average load")
@ConfigEntry(path = "task-delay")
private long taskDelay = 1;
@ConfigComment("")
@ConfigComment("Number of chunks that will be processed per task")
@ConfigEntry(path = "chunks")
private int chunks = 10;
@ConfigComment("Disabled Game Mode Addons")
@ConfigComment("Level will NOT hook into these game mode addons.")
@ConfigEntry(path = "disabled-game-modes")
private List<String> gameModes = Collections.emptyList();
@ConfigComment("")
@ConfigComment("Calculate island level on login")
@ -54,6 +40,12 @@ public class ConfigSettings implements ConfigObject {
@ConfigEntry(path = "end")
private boolean end = false;
@ConfigComment("")
@ConfigComment("Include chest contents in level calculations.")
@ConfigComment("Will count blocks in chests or containers.")
@ConfigEntry(path = "include-chests")
private boolean includeChests = false;
@ConfigComment("")
@ConfigComment("Underwater block multiplier")
@ConfigComment("If blocks are below sea-level, they can have a higher value. e.g. 2x")
@ -116,48 +108,6 @@ public class ConfigSettings implements ConfigObject {
this.gameModes = gameModes;
}
/**
* @return the taskDelay
*/
public long getTaskDelay() {
if (taskDelay < 1L) {
Level.getInstance().logError("task-delay must be at least 1");
taskDelay = 1;
}
return taskDelay;
}
/**
* @param taskDelay the taskDelay to set
*/
public void setTaskDelay(long taskDelay) {
this.taskDelay = taskDelay;
}
/**
* @return the chunks
*/
public int getChunks() {
if (chunks < 1) {
Level.getInstance().logError("chunks must be at least 1");
chunks = 1;
}
return chunks;
}
/**
* @param chunks the chunks to set
*/
public void setChunks(int chunks) {
this.chunks = chunks;
}
/**
* @return the calcOnLogin
*/
@ -228,7 +178,7 @@ public class ConfigSettings implements ConfigObject {
public long getLevelCost() {
if (levelCost < 1) {
levelCost = 1;
Level.getInstance().logError("levelcost in config.yml cannot be less than 1. Setting to 1.");
BentoBox.getInstance().logError("levelcost in config.yml cannot be less than 1. Setting to 1.");
}
return levelCost;
}
@ -263,7 +213,7 @@ public class ConfigSettings implements ConfigObject {
*/
public int getLevelWait() {
if (levelWait < 0) {
Level.getInstance().logError("levelwait must be at least 0");
BentoBox.getInstance().logError("levelwait must be at least 0");
levelWait = 0;
}
return levelWait;
@ -326,5 +276,24 @@ public class ConfigSettings implements ConfigObject {
}
/**
* @return the includeChests
*/
public boolean isIncludeChests() {
return includeChests;
}
/**
* @param includeChests the includeChests to set
*/
public void setIncludeChests(boolean includeChests) {
this.includeChests = includeChests;
}
}

View File

@ -1,51 +0,0 @@
package world.bentobox.level.event;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
/**
* This event is fired when a player clicks on a top ten head.
*
* @author tastybento
*/
class TopTenClick extends Event implements Cancellable {
private boolean cancelled;
private static final HandlerList handlers = new HandlerList();
private final String owner;
public TopTenClick(String owner) {
this.owner = owner;
}
/**
* @return name of head owner that was clicked
*/
public String getOwner() {
return owner;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
@Override
public HandlerList getHandlers() {
return getHandlerList();
}
private static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -1,11 +1,11 @@
package world.bentobox.level.event;
package world.bentobox.level.events;
import java.util.List;
import java.util.UUID;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.calculators.CalcIslandLevel.Results;
import world.bentobox.level.calculators.Results;
/**
* This event is fired after the island level is calculated and before the results are saved.
@ -36,30 +36,30 @@ public class IslandLevelCalculatedEvent extends IslandBaseEvent {
public Results getResults() {
return results;
}
/**
* @return death handicap value
*/
public int getDeathHandicap() {
return results.getDeathHandicap();
}
/**
* Get the island's initial level. It may be zero if it was never calculated
* Get the island's initial level. It may be zero if it was never calculated
* or if a player was registered to the island after it was made.
* @return initial level of island as calculated when the island was created.
*/
public long getInitialLevel() {
return results.getInitialLevel();
}
/**
* @return the level calculated
*/
public long getLevel() {
return results.getLevel();
}
/**
* Overwrite the level. This level will be used instead of the calculated level.
@ -68,14 +68,14 @@ public class IslandLevelCalculatedEvent extends IslandBaseEvent {
public void setLevel(long level) {
results.setLevel(level);
}
/**
* @return number of points required to next level
*/
public long getPointsToNextLevel() {
return results.getPointsToNextLevel();
}
/**
* @return a human readable report explaining how the calculation was made
*/

View File

@ -1,4 +1,4 @@
package world.bentobox.level.event;
package world.bentobox.level.events;
import java.util.UUID;

View File

@ -1,7 +1,5 @@
package world.bentobox.level.listeners;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.World;
@ -20,107 +18,91 @@ import world.bentobox.bentobox.api.events.team.TeamEvent.TeamLeaveEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent.TeamSetownerEvent;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.level.Level;
import world.bentobox.level.calculators.CalcIslandLevel;
/**
* Listens for new islands or ownership changes and sets the level to zero automatically
* @author tastybento
*
*/
public class IslandTeamListeners implements Listener {
public class IslandActivitiesListeners implements Listener {
private final Level addon;
private final Map<Island, CalcIslandLevel> cil;
/**
* @param addon - addon
*/
public IslandTeamListeners(Level addon) {
public IslandActivitiesListeners(Level addon) {
this.addon = addon;
cil = new HashMap<>();
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onNewIsland(IslandCreatedEvent e) {
// Clear the island setting
addon.setInitialIslandLevel(e.getIsland(), 0L);
if (e.getIsland().getOwner() != null && e.getIsland().getWorld() != null) {
cil.putIfAbsent(e.getIsland(), new CalcIslandLevel(addon, e.getIsland(), () -> zeroLevel(e.getIsland())));
}
zeroIsland(e.getIsland());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onNewIsland(IslandResettedEvent e) {
zeroIsland(e.getIsland());
}
private void zeroIsland(final Island island) {
// Clear the island setting
addon.setInitialIslandLevel(e.getIsland(), 0L);
if (e.getIsland().getOwner() != null && e.getIsland().getWorld() != null) {
cil.putIfAbsent(e.getIsland(), new CalcIslandLevel(addon, e.getIsland(), () -> zeroLevel(e.getIsland())));
if (island.getOwner() != null && island.getWorld() != null) {
addon.getPipeliner().addIsland(island).thenAccept(results -> {
addon.getManager().setInitialIslandLevel(island, results.getLevel());
});
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onIslandDelete(IslandPreclearEvent e) {
// Remove player from the top ten and level
final UUID owner = e.getIsland().getOwner();
final World world = e.getIsland().getWorld();
addon.setIslandLevel(world, owner, 0);
addon.getTopTen().removeEntry(world, owner);
UUID uuid = e.getIsland().getOwner();
World world = e.getIsland().getWorld();
remove(world, uuid);
}
private void remove(World world, UUID uuid) {
if (uuid != null && world != null) {
addon.getManager().removeEntry(world, uuid);
}
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onNewIslandOwner(TeamSetownerEvent e) {
// Remove player from the top ten and level
addon.setIslandLevel(e.getIsland().getWorld(), e.getIsland().getOwner(), 0);
addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getIsland().getOwner());
remove(e.getIsland().getWorld(), e.getIsland().getOwner());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(TeamJoinedEvent e) {
// Remove player from the top ten and level
addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0);
addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID());
remove(e.getIsland().getWorld(), e.getPlayerUUID());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(IslandUnregisteredEvent e) {
// Remove player from the top ten and level
addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0);
addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID());
remove(e.getIsland().getWorld(), e.getPlayerUUID());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(IslandRegisteredEvent e) {
// Remove player from the top ten and level
addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0);
addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID());
remove(e.getIsland().getWorld(), e.getPlayerUUID());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(TeamLeaveEvent e) {
// Remove player from the top ten and level
addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0);
addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID());
remove(e.getIsland().getWorld(), e.getPlayerUUID());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIsland(TeamKickEvent e) {
// Remove player from the top ten and level
addon.setIslandLevel(e.getIsland().getWorld(), e.getPlayerUUID(), 0);
addon.getTopTen().removeEntry(e.getIsland().getWorld(), e.getPlayerUUID());
remove(e.getIsland().getWorld(), e.getPlayerUUID());
}
private void zeroLevel(Island island) {
if (cil.containsKey(island)) {
long level = cil.get(island).getResult().getLevel();
// Get deaths
int deaths = addon.getPlayers().getDeaths(island.getWorld(), island.getOwner());
// Death penalty calculation.
if (addon.getSettings().getLevelCost() != 0) {
// Add the deaths because this makes the original island that much "bigger"
level += deaths * addon.getSettings().getDeathPenalty() / addon.getSettings().getLevelCost();
}
addon.setInitialIslandLevel(island, level);
cil.remove(island);
}
}
}

View File

@ -1,5 +1,7 @@
package world.bentobox.level.listeners;
import java.util.Objects;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@ -26,13 +28,16 @@ public class JoinLeaveListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent e) {
// Load player into cache
addon.getLevelsData(e.getPlayer().getUniqueId());
addon.getManager().getLevelsData(e.getPlayer().getUniqueId());
// If level calc on login is enabled, run through all the worlds and calculate the level
if (addon.getSettings().isCalcOnLogin()) {
addon.getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> addon.getSettings().getGameModes().contains(gm.getDescription().getName()))
.forEach(gm -> addon.calculateIslandLevel(gm.getOverWorld(), null, e.getPlayer().getUniqueId()));
.filter(gm -> !addon.getSettings().getGameModes().contains(gm.getDescription().getName()))
.map(gm -> gm.getIslands().getIsland(gm.getOverWorld(), e.getPlayer().getUniqueId()))
.filter(Objects::nonNull)
.forEach(island -> addon.getManager().calculateLevel(e.getPlayer().getUniqueId(), island));
}
}
}

View File

@ -1,5 +1,6 @@
package world.bentobox.level.objects;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
@ -28,8 +29,11 @@ public class LevelsData implements DataObject {
*/
@Expose
private Map<String, Long> initialLevel = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
public LevelsData() {} // For Bean loading
/**
* Map of world name to points to next level
*/
@Expose
private Map<String, Long> pointsToNextLevel = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
/**
* Create a level entry for target player
@ -37,9 +41,8 @@ public class LevelsData implements DataObject {
* @param level - level
* @param world - world
*/
public LevelsData(UUID targetPlayer, long level, World world) {
public LevelsData(UUID targetPlayer) {
uniqueId = targetPlayer.toString();
levels.put(world.getName(), level);
}
/* (non-Javadoc)
@ -81,8 +84,14 @@ public class LevelsData implements DataObject {
this.levels = levels;
}
/**
* Sets the island level to level - the initial level
* @param world - world where island is
* @param lv - level
*/
public void setLevel(World world, Long lv) {
levels.put(world.getName(),lv);
String name = world.getName().toLowerCase(Locale.ENGLISH);
levels.put(name, lv - this.initialLevel.getOrDefault(name, 0L));
}
/**
@ -91,7 +100,7 @@ public class LevelsData implements DataObject {
* @param level - level
*/
public void setInitialLevel(World world, long level) {
this.initialLevel.put(world.getName(), level);
this.initialLevel.put(world.getName().toLowerCase(Locale.ENGLISH), level);
}
/**
@ -114,6 +123,51 @@ public class LevelsData implements DataObject {
* @return initial island level or 0 by default
*/
public long getInitialLevel(World world) {
return initialLevel.getOrDefault(world.getName(), 0L);
return initialLevel.getOrDefault(world.getName().toLowerCase(Locale.ENGLISH), 0L);
}
/**
* Remove a world from a player's data
* @param world - world to remove
*/
public void remove(World world) {
levels.remove(world.getName().toLowerCase(Locale.ENGLISH));
initialLevel.remove(world.getName().toLowerCase(Locale.ENGLISH));
}
/**
* @return the pointsToNextLevel
*/
public Map<String, Long> getPointsToNextLevel() {
return pointsToNextLevel;
}
/**
* @param pointsToNextLevel the pointsToNextLevel to set
*/
public void setPointsToNextLevel(Map<String, Long> pointsToNextLevel) {
this.pointsToNextLevel = pointsToNextLevel;
}
/**
* Sets the island points to next level.
* This is calculated the last time the level was calculated and will not change dynamically.
* @param world - world where island is
* @param points - points to next level
*/
public void setPointsToNextLevel(World world, Long points) {
pointsToNextLevel.put(world.getName().toLowerCase(Locale.ENGLISH), points);
}
/**
* Get the points required to get to the next island level for this world.
* This is calculated when the island level is calculated and will not change dynamically.
* @param world - world
* @return points to next level or zero if unknown
*/
public long getPointsToNextLevel(World world) {
return pointsToNextLevel.getOrDefault(world.getName().toLowerCase(Locale.ENGLISH), 0L);
}
}

View File

@ -1,12 +1,11 @@
package world.bentobox.level.objects;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.Nullable;
import org.bukkit.World;
import com.google.gson.annotations.Expose;
@ -14,7 +13,7 @@ import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
/**
* This class stores and sorts the top ten.
* This class stores the top ten.
* @author tastybento
*
*/
@ -27,41 +26,8 @@ public class TopTenData implements DataObject {
@Expose
private Map<UUID, Long> topTen = new LinkedHashMap<>();
public Map<UUID, Long> getTopTen() {
// Remove any entries that have level values less than 1
//topTen.values().removeIf(l -> l < 1);
// make copy
Map<UUID, Long> snapTopTen = Collections.unmodifiableMap(topTen);
return snapTopTen.entrySet().stream()
.filter(l -> l.getValue() > 0)
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(10)
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
/**
* Get the level for the rank
* @param rank - rank
* @return level value or 0 if none.
*/
public long getTopTenLevel(int rank) {
Map<UUID, Long> tt = getTopTen();
return rank <= tt.size() ? (long)tt.values().toArray()[(rank-1)] : 0;
}
/**
* Get the UUID of the rank
* @param rank - rank
* @return UUID or null
*/
@Nullable
public UUID getTopTenUUID(int rank) {
Map<UUID, Long> tt = getTopTen();
return rank <= tt.size() ? (UUID)tt.keySet().toArray()[(rank-1)] : null;
}
public void setTopTen(Map<UUID, Long> topTen) {
this.topTen = topTen;
public TopTenData(World k) {
uniqueId = k.getName().toLowerCase(Locale.ENGLISH);
}
@Override
@ -73,35 +39,20 @@ public class TopTenData implements DataObject {
@Override
public void setUniqueId(String uniqueId) {
// This is the world name - make it always lowercase
this.uniqueId = uniqueId.toLowerCase();
this.uniqueId = uniqueId.toLowerCase(Locale.ENGLISH);
}
/**
* @return the topTen
*/
public Map<UUID, Long> getTopTen() {
return topTen;
}
/**
* @param topTen the topTen to set
*/
public void setTopTen(Map<UUID, Long> topTen) {
this.topTen = topTen;
}
/**
* Add level for this island owner or team leader, sort the top ten and limit to ten entries
* @param uuid - UUID of owner or team leader
* @param level - island level
*/
public void addLevel(UUID uuid, Long level) {
this.topTen.put(uuid, level);
}
/**
* Get the level for this UUID, or zero if the UUID is not found
* @param uuid - UUID to check
* @return island level
*/
public long getLevel(UUID uuid) {
if (topTen.containsKey(uuid))
return topTen.get(uuid);
return 0L;
}
/**
* Removes ownerUUID from the top ten
* @param ownerUUID - UUID to remove
*/
public void remove(UUID ownerUUID) {
this.topTen.remove(ownerUUID);
}
}

View File

@ -38,6 +38,6 @@ public class LevelRequestHandler extends AddonRequestHandler {
return 0L;
}
return addon.getIslandLevel(Bukkit.getWorld((String) map.get("world-name")), (UUID) map.get("player"));
return addon.getManager().getIslandLevel(Bukkit.getWorld((String) map.get("world-name")), (UUID) map.get("player"));
}
}

View File

@ -54,6 +54,6 @@ public class TopTenRequestHandler extends AddonRequestHandler {
}
// No null check required
return addon.getTopTen().getTopTenList(Bukkit.getWorld((String) map.get(WORLD_NAME))).getTopTen();
return addon.getManager().getTopTen(Bukkit.getWorld((String) map.get(WORLD_NAME)), 10);
}
}

View File

@ -4,83 +4,24 @@ version: ${version}${build.number}
icon: DIAMOND
api-version: 1.14
softdepend: AcidIsland, BSkyBlock, CaveBlock, AOneBlock, SkyGrid
authors: tastybento
permissions:
bskyblock.intopten:
'[gamemode].intopten':
description: Player is in the top ten.
default: true
bskyblock.island.level:
'[gamemode].island.level':
description: Player can use level command
default: true
bskyblock.island.top:
'[gamemode].island.top':
description: Player can use top ten command
default: true
bskyblock.island.value:
'[gamemode].island.value':
description: Player can use value command
default: true
bskyblock.admin.level:
'[gamemode].admin.level':
description: Player can use admin level command
default: true
bskyblock.admin.topten:
description: Player can use admin top ten command
default: true
acidisland.intopten:
description: Player is in the top ten.
default: true
acidisland.island.level:
description: Player can use level command
default: true
acidisland.island.top:
description: Player can use top ten command
default: true
acidisland.island.value:
description: Player can use value command
default: true
acidisland.admin.level:
description: Player can use admin level command
default: true
acidisland.admin.topten:
description: Player can use admin top ten command
default: true
caveblock.intopten:
description: Player is in the top ten.
default: true
caveblock.island.level:
description: Player can use level command
default: true
caveblock.island.top:
description: Player can use top ten command
default: true
caveblock.island.value:
description: Player can use value command
default: true
caveblock.admin.level:
description: Player can use admin level command
default: true
caveblock.admin.topten:
description: Player can use admin top ten command
default: true
aoneblock.intopten:
description: Player is in the top ten.
default: true
aoneblock.island.level:
description: Player can use level command
default: true
aoneblock.island.top:
description: Player can use top ten command
default: true
aoneblock.island.value:
description: Player can use value command
default: true
aoneblock.admin.level:
description: Player can use admin level command
default: true
aoneblock.admin.topten:
'[gamemode].admin.topten':
description: Player can use admin top ten command
default: true

View File

@ -1,58 +1,48 @@
# Config file for Level add-on Version ${version}
# Game Mode Addons
# Level will hook into these game mode addons. Don't forget to set any world-specific
# block values below!
game-modes:
- AcidIsland
- BSkyBlock
- CaveBlock
#- SkyGrid
#- AOneBlock
# Performance settings
# Level is very processor-intensive, so these settings may need to be tweaked to optimize for your server
# Delay between each task that loads chunks for calculating levels
# Increasing this will slow down level calculations but reduce average load
task-delay: 1
# Number of chunks that will be processed per task
chunks: 10
# Level Configuration ${version}
#
#
# Disabled Game Mode Addons
# Level will NOT hook into these game mode addons.
disabled-game-modes:
- AOneBlock
#
# Calculate island level on login
# This silently calculates the player's island level when they login
# This applies to all islands the player has on the server, e.g., BSkyBlock, AcidIsland
login: false
#
# Include nether island in level calculations.
# Warning: Enabling this mid-game will give players with an island a jump in
# island level. New islands will be correctly zeroed.
nether: false
# Include end island in level calculations
#
# Include end island in level calculations.
# Warning: Enabling this mid-game will give players with an island a jump in
# island level. New islands will be correctly zeroed.
end: false
#
# Include chest contents in level calculations.
# Will count blocks in chests or containers.
include-chests: false
#
# Underwater block multiplier
# If blocks are below sea-level, they can have a higher value. e.g. 2x
# Promotes under-water development if there is a sea. Value can be fractional.
underwater: 1.0
#
# Value of one island level. Default 100. Minimum value is 1.
levelcost: 100
#
# Island level calculation formula
# blocks - the sum total of all block values, less any death penalty
# level_cost - in a linear equation, the value of one level
# This formula can include +,=,*,/,sqrt,^,sin,cos,tan. Result will always be rounded to a long integer
# for example, an alternative non-linear option could be: 3 * sqrt(blocks / level_cost)
level-calc: blocks / level_cost
#
# Cooldown between level requests in seconds
levelwait: 60
#
# Death penalty
# How many block values a player will lose per death.
# Default value of 100 means that for every death, the player will lose 1 level (if levelcost is 100)
@ -60,9 +50,8 @@ levelwait: 60
deathpenalty: 100
# Sum team deaths - if true, all the teams deaths are summed
# If false, only the leader's deaths counts
sumteamdeaths: false
# For other death related settings, see the GameModeAddon's config.yml settings.
sumteamdeaths: false
# Shorthand island level
# Shows large level values rounded down, e.g., 10,345 -> 10k
shorthand: false

View File

@ -9,7 +9,7 @@ admin:
description: "calculate the island level for player"
top:
description: "show the top ten list"
unknown-world: "&cUnknown world!"
unknown-world: "&c Unknown world!"
display: "&f[rank]. &a[name] &7- &b[level]"
remove:
description: "remove player from Top Ten"
@ -19,22 +19,23 @@ island:
level:
parameters: "[player]"
description: "calculate your island level or show the level of [player]"
calculating: "&aCalculating level..."
island-level-is: "&aIsland level is &b[level]"
required-points-to-next-level: "&a[points] points required until the next level"
calculating: "&a Calculating level..."
in-queue: "&a You are number [number] in the queue"
island-level-is: "&a Island level is &b[level]"
required-points-to-next-level: "&a [points] points required until the next level"
deaths: "&c([number] deaths)"
cooldown: "&cYou must wait &b[time] &cseconds until you can do that again"
cooldown: "&c You must wait &b[time] &c seconds until you can do that again"
top:
description: "show the Top Ten"
gui-title: "&aTop Ten"
gui-title: "&a Top Ten"
gui-heading: "&6[name]: &B[rank]"
island-level: "&BLevel [level]"
warp-to: "&AWarping to [name]'s island"
island-level: "&b Level [level]"
warp-to: "&A Warping to [name]'s island"
value:
description: "shows the value of any block"
success: "&7The value of this block is: &e[value]"
success-underwater: "&7The value of this block below sea-level: &e[value]"
empty-hand: "&cThere are no blocks in your hand"
no-value: "&cThat item has no value."
success: "&7 The value of this block is: &e[value]"
success-underwater: "&7 The value of this block below sea-level: &e[value]"
empty-hand: "&c There are no blocks in your hand"
no-value: "&c That item has no value."

View File

@ -1,115 +0,0 @@
package world.bentobox.level;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.level.calculators.PlayerLevel;
import world.bentobox.level.config.BlockConfig;
import world.bentobox.level.config.ConfigSettings;
/**
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class, LevelPresenter.class})
public class LevelPresenterTest {
@Mock
private BentoBox plugin;
@Mock
private Level addon;
@Mock
private PlayerLevel pl;
@Mock
private ConfigSettings settings;
@Mock
private BlockConfig blockConfig;
@Before
public void setUp() throws Exception {
IslandWorldManager iwm = mock(IslandWorldManager.class);
when(plugin.getIWM()).thenReturn(iwm);
when(iwm.getPermissionPrefix(Mockito.any())).thenReturn("world");
IslandsManager im = mock(IslandsManager.class);
when(plugin.getIslands()).thenReturn(im);
// Has island
when(im.hasIsland(Mockito.any(), Mockito.any(User.class))).thenReturn(true);
// In team
when(im.inTeam(Mockito.any(), Mockito.any())).thenReturn(true);
// team leader
when(im.getOwner(Mockito.any(), Mockito.any())).thenReturn(UUID.randomUUID());
// Player level
PowerMockito.whenNew(PlayerLevel.class).withAnyArguments().thenReturn(pl);
// Settings
when(addon.getSettings()).thenReturn(settings);
when(addon.getBlockConfig()).thenReturn(blockConfig);
}
/**
* Test method for {@link LevelPresenter#LevelPresenter(Level, world.bentobox.bentobox.BentoBox)}.
*/
@Test
public void testLevelPresenter() {
new LevelPresenter(addon, plugin);
}
/**
* Test method for {@link LevelPresenter#calculateIslandLevel(org.bukkit.World, world.bentobox.bentobox.api.user.User, java.util.UUID)}.
*/
@Test
public void testCalculateIslandLevel() {
LevelPresenter lp = new LevelPresenter(addon, plugin);
World world = mock(World.class);
User sender = mock(User.class);
UUID targetPlayer = UUID.randomUUID();
lp.calculateIslandLevel(world, sender, targetPlayer);
Mockito.verify(sender).sendMessage("island.level.calculating");
}
/**
* Test method for {@link LevelPresenter#getLevelString(long)}.
*/
@Test
public void testGetLevelStringLong() {
LevelPresenter lp = new LevelPresenter(addon, plugin);
assertEquals("123456789", lp.getLevelString(123456789L));
}
/**
* Test method for {@link LevelPresenter#getLevelString(long)}.
*/
@Test
public void testGetLevelStringLongShorthand() {
when(settings.isShorthand()).thenReturn(true);
LevelPresenter lp = new LevelPresenter(addon, plugin);
assertEquals("123.5M", lp.getLevelString(123456789L));
assertEquals("1.2k", lp.getLevelString(1234L));
assertEquals("123.5G", lp.getLevelString(123456789352L));
assertEquals("1.2T", lp.getLevelString(1234567893524L));
assertEquals("12345.7T", lp.getLevelString(12345678345345349L));
}
}

View File

@ -1,13 +1,9 @@
package world.bentobox.level;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -67,7 +63,7 @@ import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlaceholdersManager;
import world.bentobox.level.config.BlockConfig;
import world.bentobox.level.config.ConfigSettings;
import world.bentobox.level.listeners.IslandTeamListeners;
import world.bentobox.level.listeners.IslandActivitiesListeners;
import world.bentobox.level.listeners.JoinLeaveListener;
/**
@ -97,6 +93,9 @@ public class LevelTest {
@Mock
private BukkitScheduler scheduler;
@Mock
private Settings pluginSettings;
private Level addon;
@Mock
@ -115,8 +114,6 @@ public class LevelTest {
private PluginManager pim;
@Mock
private BlockConfig blockConfig;
@Mock
private Settings pluginSettings;
@BeforeClass
public static void beforeClass() throws IOException {
@ -152,6 +149,12 @@ public class LevelTest {
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger());
// The database type has to be created one line before the thenReturn() to work!
DatabaseType value = DatabaseType.JSON;
when(plugin.getSettings()).thenReturn(pluginSettings);
when(pluginSettings.getDatabaseType()).thenReturn(value);
//when(plugin.isEnabled()).thenReturn(true);
// Command manager
CommandsManager cm = mock(CommandsManager.class);
@ -172,6 +175,7 @@ public class LevelTest {
when(plugin.getIWM()).thenReturn(iwm);
// Player has island to begin with
when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island);
when(plugin.getIslands()).thenReturn(im);
@ -201,6 +205,7 @@ public class LevelTest {
when(am.getGameModeAddons()).thenReturn(Collections.singletonList(gameMode));
AddonDescription desc2 = new AddonDescription.Builder("bentobox", "BSkyBlock", "1.3").description("test").authors("tasty").build();
when(gameMode.getDescription()).thenReturn(desc2);
when(gameMode.getOverWorld()).thenReturn(world);
// Player command
@NonNull
@ -214,10 +219,6 @@ public class LevelTest {
when(plugin.getFlagsManager()).thenReturn(fm);
when(fm.getFlags()).thenReturn(Collections.emptyList());
// The database type has to be created one line before the thenReturn() to work!
DatabaseType value = DatabaseType.JSON;
when(plugin.getSettings()).thenReturn(pluginSettings);
when(pluginSettings.getDatabaseType()).thenReturn(value);
// Bukkit
PowerMockito.mockStatic(Bukkit.class);
@ -275,47 +276,20 @@ public class LevelTest {
verify(plugin).logWarning("[Level] Level Addon: No such world in blockconfig.yml : acidisland_world");
verify(plugin).log("[Level] Level hooking into BSkyBlock");
verify(cmd, times(3)).getAddon(); // Three commands
verify(adminCmd, times(2)).getAddon(); // Two commands
verify(adminCmd, times(3)).getAddon(); // Three commands
// Placeholders
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_island_level"), any());
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_visited_island_level"), any());
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_points_to_next_level"), any());
for (int i = 1; i < 11; i++) {
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_name_" + i), any());
verify(phm).registerPlaceholder(eq(addon), eq("bskyblock_top_value_" + i), any());
}
// Commands
verify(am).registerListener(eq(addon), any(IslandTeamListeners.class));
verify(am).registerListener(eq(addon), any(IslandActivitiesListeners.class));
verify(am).registerListener(eq(addon), any(JoinLeaveListener.class));
}
/**
* Test method for {@link world.bentobox.level.Level#getIslandLevel(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testGetIslandLevelUnknown() {
addon.onEnable();
assertEquals(0L, addon.getIslandLevel(world, UUID.randomUUID()));
}
/**
* Test method for {@link world.bentobox.level.Level#getIslandLevel(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testGetIslandLevelNullTarget() {
addon.onEnable();
assertEquals(0L, addon.getIslandLevel(world, UUID.randomUUID()));
}
/**
* Test method for {@link world.bentobox.level.Level#getLevelsData(java.util.UUID)}.
*/
@Test
public void testGetLevelsDataUnknown() {
addon.onEnable();
assertNull(addon.getLevelsData(UUID.randomUUID()));
}
/**
* Test method for {@link world.bentobox.level.Level#getSettings()}.
*/
@ -326,55 +300,5 @@ public class LevelTest {
assertEquals(100, s.getLevelCost());
}
/**
* Test method for {@link world.bentobox.level.Level#getTopTen()}.
*/
@Test
public void testGetTopTen() {
addon.onEnable();
assertNotNull(addon.getTopTen());
}
/**
* Test method for {@link world.bentobox.level.Level#getLevelPresenter()}.
*/
@Test
public void testGetLevelPresenter() {
addon.onEnable();
assertNotNull(addon.getLevelPresenter());
}
/**
* Test method for {@link world.bentobox.level.Level#setIslandLevel(org.bukkit.World, java.util.UUID, long)}.
*/
@Test
public void testSetIslandLevel() {
addon.onEnable();
addon.setIslandLevel(world, uuid, 345L);
assertEquals(345L, addon.getIslandLevel(world, uuid));
verify(plugin, never()).logError(anyString());
}
/**
* Test method for {@link world.bentobox.level.Level#getInitialIslandLevel(world.bentobox.bentobox.database.objects.Island)}.
*/
@Test
public void testGetInitialIslandLevel() {
addon.onEnable();
addon.setInitialIslandLevel(island, 40);
verify(plugin, never()).logError(anyString());
assertEquals(40, addon.getInitialIslandLevel(island));
}
/**
* Test method for {@link world.bentobox.level.Level#getHandler()}.
*/
@Test
public void testGetHandler() {
addon.onEnable();
assertNotNull(addon.getHandler());
}
}

View File

@ -0,0 +1,420 @@
package world.bentobox.level;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.PluginManager;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import com.google.common.collect.ImmutableSet;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.level.calculators.Pipeliner;
import world.bentobox.level.calculators.Results;
import world.bentobox.level.config.ConfigSettings;
import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.objects.TopTenData;
/**
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class, BentoBox.class, DatabaseSetup.class, PanelBuilder.class})
public class LevelsManagerTest {
@Mock
private static AbstractDatabaseHandler<Object> handler;
@Mock
Level addon;
@Mock
private BentoBox plugin;
@Mock
private Settings pluginSettings;
// Class under test
private LevelsManager lm;
@Mock
private Island island;
@Mock
private Pipeliner pipeliner;
private CompletableFuture<Results> cf;
private UUID uuid;
@Mock
private World world;
@Mock
private Player player;
@Mock
private ConfigSettings settings;
@Mock
private User user;
@Mock
private PlayersManager pm;
@Mock
private Inventory inv;
@Mock
private IslandWorldManager iwm;
@Mock
private PluginManager pim;
@Mock
private LevelsData levelsData;
@SuppressWarnings("unchecked")
@BeforeClass
public static void beforeClass() {
// This has to be done beforeClass otherwise the tests will interfere with each other
handler = mock(AbstractDatabaseHandler.class);
// Database
PowerMockito.mockStatic(DatabaseSetup.class);
DatabaseSetup dbSetup = mock(DatabaseSetup.class);
when(DatabaseSetup.getDatabase()).thenReturn(dbSetup);
when(dbSetup.getHandler(any())).thenReturn(handler);
}
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
when(addon.getPlugin()).thenReturn(plugin);
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Bukkit
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getWorld(anyString())).thenReturn(world);
when(Bukkit.getPluginManager()).thenReturn(pim);
when(Bukkit.getPlayer(any(UUID.class))).thenReturn(player);
// The database type has to be created one line before the thenReturn() to work!
DatabaseType value = DatabaseType.JSON;
when(plugin.getSettings()).thenReturn(pluginSettings);
when(pluginSettings.getDatabaseType()).thenReturn(value);
// Pipeliner
when(addon.getPipeliner()).thenReturn(pipeliner);
cf = new CompletableFuture<>();
when(pipeliner.addIsland(any())).thenReturn(cf);
// Island
uuid = UUID.randomUUID();
ImmutableSet<UUID> iset = ImmutableSet.of(uuid);
when(island.getMemberSet()).thenReturn(iset);
when(island.getOwner()).thenReturn(uuid);
when(island.getWorld()).thenReturn(world);
// Player
when(player.getUniqueId()).thenReturn(uuid);
when(player.hasPermission(anyString())).thenReturn(true);
// World
when(world.getName()).thenReturn("bskyblock-world");
// Settings
when(addon.getSettings()).thenReturn(settings);
// User
when(user.getTranslation(anyString())).thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
when(user.getTranslation(eq("island.top.gui-heading"), eq("[name]"), anyString(), eq("[rank]"), anyString())).thenReturn("gui-heading");
when(user.getTranslation(eq("island.top.island-level"),eq("[level]"), anyString())).thenReturn("island-level");
when(user.getPlayer()).thenReturn(player);
// Player Manager
when(addon.getPlayers()).thenReturn(pm);
when(pm.getName(any())).thenReturn("player1",
"player2",
"player3",
"player4",
"player5",
"player6",
"player7",
"player8",
"player9",
"player10"
);
// Mock item factory (for itemstacks)
ItemFactory itemFactory = mock(ItemFactory.class);
when(Bukkit.getItemFactory()).thenReturn(itemFactory);
ItemMeta itemMeta = mock(ItemMeta.class);
when(itemFactory.getItemMeta(any())).thenReturn(itemMeta);
// Has perms
when(player.hasPermission(anyString())).thenReturn(true);
// Fill the top ten
TopTenData ttd = new TopTenData(world);
ttd.setUniqueId("world");
List<Object> topTen = new ArrayList<>();
for (long i = -5; i < 5; i ++) {
ttd.getTopTen().put(UUID.randomUUID(), i);
}
// Include a known UUID
ttd.getTopTen().put(uuid, 456789L);
topTen.add(ttd);
when(handler.loadObjects()).thenReturn(topTen);
when(handler.objectExists(anyString())).thenReturn(true);
when(levelsData.getLevel(any())).thenReturn(-5L, -4L, -3L, -2L, -1L, 0L, 1L, 2L, 3L, 4L, 5L, 45678L);
when(levelsData.getUniqueId()).thenReturn(uuid.toString());
when(handler.loadObject(anyString())).thenReturn(levelsData );
// Inventory GUI
when(Bukkit.createInventory(any(), anyInt(), anyString())).thenReturn(inv);
// IWM
when(plugin.getIWM()).thenReturn(iwm);
when(iwm.getPermissionPrefix(any())).thenReturn("bskyblock.");
lm = new LevelsManager(addon);
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
deleteAll(new File("database"));
User.clearUsers();
Mockito.framework().clearInlineMocks();
}
private static void deleteAll(File file) throws IOException {
if (file.exists()) {
Files.walk(file.toPath())
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
}
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#calculateLevel(UUID, world.bentobox.bentobox.database.objects.Island)}.
*/
@Test
public void testCalculateLevel() {
Results results = new Results();
results.setLevel(10000);
results.setInitialLevel(3);
lm.calculateLevel(uuid, island);
cf.complete(results);
assertTrue(lm.getLevelsData(uuid).getLevel(world) == 10000);
//Map<UUID, Long> tt = lm.getTopTen(world, 10);
//assertEquals(1, tt.size());
//assertTrue(tt.get(uuid) == 10000);
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getInitialLevel(world.bentobox.bentobox.database.objects.Island)}.
*/
@Test
public void testGetInitialLevel() {
assertEquals(0,lm.getInitialLevel(island));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getIslandLevel(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testGetIslandLevel() {
assertEquals(-5, lm.getIslandLevel(world, uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getPointsToNextString(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testGetPointsToNextString() {
assertEquals("0", lm.getPointsToNextString(world, UUID.randomUUID()));
assertEquals("0", lm.getPointsToNextString(world, uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getIslandLevelString(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testGetIslandLevelString() {
assertEquals("-5", lm.getIslandLevelString(world, uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getLevelsData(java.util.UUID)}.
*/
@Test
public void testGetLevelsData() {
assertEquals(levelsData, lm.getLevelsData(uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#formatLevel(long)}.
*/
@Test
public void testFormatLevel() {
assertEquals("123456789", lm.formatLevel(123456789L));
when(settings.isShorthand()).thenReturn(true);
assertEquals("123.5M", lm.formatLevel(123456789L));
assertEquals("1.2k", lm.formatLevel(1234L));
assertEquals("123.5G", lm.formatLevel(123456789352L));
assertEquals("1.2T", lm.formatLevel(1234567893524L));
assertEquals("12345.7T", lm.formatLevel(12345678345345349L));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
*/
@Test
public void testGetTopTenEmpty() {
Map<UUID, Long> tt = lm.getTopTen(world, 10);
assertTrue(tt.isEmpty());
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getTopTen(org.bukkit.World, int)}.
*/
@Test
public void testGetTopTen() {
testLoadTopTens();
Map<UUID, Long> tt = lm.getTopTen(world, 10);
assertFalse(tt.isEmpty());
assertEquals(5, tt.size());
assertEquals(1, lm.getTopTen(world, 1).size());
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#hasTopTenPerm(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testHasTopTenPerm() {
assertTrue(lm.hasTopTenPerm(world, uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#loadTopTens()}.
*/
@Test
public void testLoadTopTens() {
lm.loadTopTens();
PowerMockito.verifyStatic(Bukkit.class); // 1
Bukkit.getWorld(eq("world"));
verify(addon).log(eq("Loaded TopTen for bskyblock-world"));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#loadTopTens()}.
*/
@Test
public void testLoadTopTensNullWorlds() {
when(Bukkit.getWorld(anyString())).thenReturn(null);
lm.loadTopTens();
PowerMockito.verifyStatic(Bukkit.class); // 1
Bukkit.getWorld(eq("world"));
verify(addon).logError(eq("TopTen world 'world' is not known on server. You might want to delete this table. Skipping..."));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#removeEntry(org.bukkit.World, java.util.UUID)}.
*/
@Test
public void testRemoveEntry() {
testLoadTopTens();
Map<UUID, Long> tt = lm.getTopTen(world, 10);
assertTrue(tt.containsKey(uuid));
lm.removeEntry(world, uuid);
tt = lm.getTopTen(world, 10);
assertFalse(tt.containsKey(uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#save()}.
*/
@Test
public void testSave() {
lm.save();
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#setInitialIslandLevel(world.bentobox.bentobox.database.objects.Island, long)}.
*/
@Test
public void testSetInitialIslandLevel() {
lm.setInitialIslandLevel(island, 10);
assertEquals(10, lm.getInitialLevel(island));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#setIslandLevel(org.bukkit.World, java.util.UUID, long)}.
*/
@Test
public void testSetIslandLevel() {
lm.setIslandLevel(world, uuid, 1234);
assertEquals(1234, lm.getIslandLevel(world, uuid));
}
/**
* Test method for {@link world.bentobox.level.LevelsManager#getGUI(org.bukkit.World, world.bentobox.bentobox.api.user.User)}.
*/
@Test
public void testGetGUI() {
lm.getGUI(world, user);
verify(user).getTranslation(eq("island.top.gui-title"));
verify(player).openInventory(inv);
/*
int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25};
for (int i : SLOTS) {
verify(inv).setItem(eq(i), any());
}
*/
}
}

View File

@ -1,278 +0,0 @@
package world.bentobox.level;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.meta.ItemMeta;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.level.config.BlockConfig;
import world.bentobox.level.objects.TopTenData;
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class, BentoBox.class, DatabaseSetup.class, PanelBuilder.class})
public class TopTenTest {
@Mock
private Level addon;
@Mock
private World world;
@Mock
private BentoBox plugin;
@Mock
private static AbstractDatabaseHandler<Object> handler;
@Mock
private IslandsManager im;
@Mock
private Player player;
@Mock
private IslandWorldManager iwm;
@Mock
private User user;
@Mock
private PlayersManager pm;
@Mock
private Inventory inv;
@Mock
private LevelPresenter lp;
@Mock
private BlockConfig settings;
@SuppressWarnings("unchecked")
@BeforeClass
public static void beforeClass() {
// This has to be done beforeClass otherwise the tests will interfere with each other
handler = mock(AbstractDatabaseHandler.class);
// Database
PowerMockito.mockStatic(DatabaseSetup.class);
DatabaseSetup dbSetup = mock(DatabaseSetup.class);
when(DatabaseSetup.getDatabase()).thenReturn(dbSetup);
when(dbSetup.getHandler(any())).thenReturn(handler);
}
@Before
public void setUp() throws Exception {
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(addon.getPlugin()).thenReturn(plugin);
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getWorld(anyString())).thenReturn(world);
Server server = mock(Server.class);
when(server.getPlayer(any(UUID.class))).thenReturn(player);
when(Bukkit.getServer()).thenReturn(server);
// Has perms
when(player.hasPermission(anyString())).thenReturn(true);
// Fill the top ten
TopTenData ttd = new TopTenData();
ttd.setUniqueId("world");
List<Object> 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<String>) 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<String>) invocation -> String.valueOf(invocation.getArgument(0, Long.class)));
}
@Test
public void testTopTen() {
new TopTen(addon);
PowerMockito.verifyStatic(Bukkit.class, times(200)); // 1
Bukkit.getWorld(eq("world"));
}
@Test
public void testTopTenNullWorld() {
when(Bukkit.getWorld(anyString())).thenReturn(null);
new TopTen(addon);
verify(addon, times(200)).logError("TopTen world world is not known on server. Skipping...");
}
@Test
public void testAddEntryNotOwner() throws Exception {
// Database
when(handler.loadObjects()).thenReturn(new ArrayList<>());
TopTen tt = new TopTen(addon);
UUID ownerUUID = UUID.randomUUID();
tt.addEntry(world, ownerUUID, 200L);
assertEquals(0, tt.getTopTenList(world).getTopTen().size());
}
@Test
public void testAddEntryIsOwner() throws Exception {
when(im.isOwner(any(), any())).thenReturn(true);
when(handler.loadObjects()).thenReturn(new ArrayList<>());
TopTen tt = new TopTen(addon);
UUID ownerUUID = UUID.randomUUID();
tt.addEntry(world, ownerUUID, 200L);
assertEquals(200L, (long) tt.getTopTenList(world).getTopTen().get(ownerUUID));
}
@Test
public void testAddEntryIsOwnerNoPermission() throws Exception {
when(player.hasPermission(anyString())).thenReturn(false);
when(im.isOwner(any(), any())).thenReturn(true);
when(handler.loadObjects()).thenReturn(new ArrayList<>());
TopTen tt = new TopTen(addon);
UUID ownerUUID = UUID.randomUUID();
tt.addEntry(world, ownerUUID, 200L);
assertNull(tt
.getTopTenList(world)
.getTopTen()
.get(ownerUUID));
}
@Test
public void testGetGUIFullTopTen() {
TopTen tt = new TopTen(addon);
tt.getGUI(world, user, "bskyblock");
verify(user).getTranslation(eq("island.top.gui-title"));
verify(player).openInventory(inv);
int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25};
for (int i : SLOTS) {
verify(inv).setItem(eq(i), any());
}
}
@Test
public void testGetGUINoPerms() {
when(player.hasPermission(anyString())).thenReturn(false);
TopTen tt = new TopTen(addon);
tt.getGUI(world, user, "bskyblock");
verify(user).getTranslation(eq("island.top.gui-title"));
verify(player).openInventory(inv);
int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25};
for (int i : SLOTS) {
verify(inv, Mockito.never()).setItem(eq(i), any());
}
}
@Test
public void testGetGUINoTopTen() throws Exception {
when(handler.loadObjects()).thenReturn(new ArrayList<>());
TopTen tt = new TopTen(addon);
tt.getGUI(world, user, "bskyblock");
verify(user).getTranslation(eq("island.top.gui-title"));
verify(player).openInventory(inv);
int[] SLOTS = new int[] {4, 12, 14, 19, 20, 21, 22, 23, 24, 25};
for (int i : SLOTS) {
verify(inv, Mockito.never()).setItem(eq(i), any());
}
}
@Test
public void testGetTopTenList() {
TopTen tt = new TopTen(addon);
TopTenData ttdList = tt.getTopTenList(world);
assertEquals(plugin, ttdList.getPlugin());
}
@Test
public void testGetTopTenListNewWorld() {
TopTen tt = new TopTen(addon);
TopTenData ttdList = tt.getTopTenList(mock(World.class));
assertEquals(plugin, ttdList.getPlugin());
}
@Test
public void testRemoveEntry() throws Exception {
// Add entry
when(im.isOwner(any(), any())).thenReturn(true);
when(handler.loadObjects()).thenReturn(new ArrayList<>());
TopTen tt = new TopTen(addon);
UUID ownerUUID = UUID.randomUUID();
tt.addEntry(world, ownerUUID, 200L);
assertEquals(200L, (long) tt.getTopTenList(world).getTopTen().get(ownerUUID));
// Remove it
tt.removeEntry(world, ownerUUID);
assertNull(tt.getTopTenList(world).getTopTen().get(ownerUUID));
}
@Test
public void testSaveTopTen() throws Exception {
TopTen tt = new TopTen(addon);
tt.saveTopTen();
verify(handler).saveObject(any());
}
}

View File

@ -36,7 +36,8 @@ import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.level.Level;
import world.bentobox.level.TopTen;
import world.bentobox.level.LevelsManager;
import world.bentobox.level.commands.AdminTopRemoveCommand;
import world.bentobox.level.objects.TopTenData;
/**
@ -73,9 +74,9 @@ public class AdminTopRemoveCommandTest {
private AdminTopRemoveCommand atrc;
@Mock
private TopTen tt;
@Mock
private TopTenData ttd;
@Mock
private LevelsManager manager;
@Before
public void setUp() {
@ -105,8 +106,7 @@ public class AdminTopRemoveCommandTest {
when(plugin.getPlayers()).thenReturn(pm);
when(pm.getUser(anyString())).thenReturn(user);
// topTen
when(addon.getTopTen()).thenReturn(tt);
when(tt.getTopTenList(any())).thenReturn(ttd);
when(addon.getManager()).thenReturn(manager);
// User
uuid = UUID.randomUUID();
when(user.getUniqueId()).thenReturn(uuid);
@ -175,7 +175,7 @@ public class AdminTopRemoveCommandTest {
public void testExecuteUserStringListOfString() {
testCanExecuteKnown();
assertTrue(atrc.execute(user, "delete", Collections.singletonList("tastybento")));
verify(ttd).remove(eq(uuid));
verify(manager).removeEntry(any(World.class), eq(uuid));
verify(user).sendMessage(eq("general.success"));
}

View File

@ -1,124 +0,0 @@
package world.bentobox.level.objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
/**
* @author tastybento
*
*/
public class TopTenDataTest {
private final Map<UUID, Long> topTen = new LinkedHashMap<>();
private TopTenData ttd;
private final UUID uuid = UUID.randomUUID();
@Before
public void setUp() {
// Create a top ten map
for (long i = 0; i < 100; i++) {
topTen.put(UUID.randomUUID(), i);
}
// Add the top player
topTen.put(uuid, 100L);
// Add negative values
for (long i = 0; i < 100; i++) {
topTen.put(UUID.randomUUID(), - i);
}
ttd = new TopTenData();
}
/**
* Test method for {@link world.bentobox.level.objects.TopTenData#getTopTen()}.
*/
@Test
public void testGetTopTen() {
assertTrue(ttd.getTopTen().isEmpty());
}
/**
* Test method for {@link world.bentobox.level.objects.TopTenData#setTopTen(java.util.Map)}.
*/
@Test
public void testSetAndGetTopTen() {
ttd.setTopTen(topTen);
// Ten only
assertEquals(10, ttd.getTopTen().size());
// Check order
long i = 100;
for (long l : ttd.getTopTen().values()) {
assertEquals(i--, l);
}
}
/**
* Test method for {@link world.bentobox.level.objects.TopTenData#setTopTen(java.util.Map)}.
*/
@Test
public void testSetAndGetTopTenShort() {
Map<UUID, Long> topTen2 = new LinkedHashMap<>();
// Create a top ten map
for (long i = 0; i < 3; i++) {
topTen2.put(UUID.randomUUID(), i);
}
// Add the top player
topTen2.put(uuid, 3L);
ttd.setTopTen(topTen2);
// Three only
assertEquals(3, ttd.getTopTen().size());
// Check order
long i = 3;
for (long l : ttd.getTopTen().values()) {
System.out.println(l);
assertEquals(i--, l);
}
}
/**
* Test method for {@link world.bentobox.level.objects.TopTenData#getUniqueId()}.
*/
@Test
public void testGetUniqueId() {
assertTrue(ttd.getUniqueId().isEmpty());
}
/**
* Test method for {@link world.bentobox.level.objects.TopTenData#setUniqueId(java.lang.String)}.
*/
@Test
public void testSetUniqueId() {
ttd.setUniqueId("unique");
assertEquals("unique", ttd.getUniqueId());
}
/**
* Test method for {@link world.bentobox.level.objects.TopTenData#addLevel(java.util.UUID, java.lang.Long)}.
*/
@Test
public void testAddAndGetLevel() {
topTen.forEach(ttd::addLevel);
topTen.keySet().forEach(k -> assertEquals((long) topTen.get(k), ttd.getLevel(k)));
}
/**
* Test method for {@link world.bentobox.level.objects.TopTenData#remove(java.util.UUID)}.
*/
@Test
public void testRemove() {
ttd.remove(uuid);
// Check order
long i = 99;
for (long l : ttd.getTopTen().values()) {
assertEquals(i--, l);
}
}
}