Adds an admin stats command. See #293

This commit is contained in:
tastybento 2023-11-18 14:41:18 -08:00
parent eb71b35c5c
commit 3a3c8a320c
4 changed files with 878 additions and 737 deletions

View File

@ -24,6 +24,7 @@ import world.bentobox.level.calculators.Pipeliner;
import world.bentobox.level.commands.AdminLevelCommand;
import world.bentobox.level.commands.AdminLevelStatusCommand;
import world.bentobox.level.commands.AdminSetInitialLevelCommand;
import world.bentobox.level.commands.AdminStatsCommand;
import world.bentobox.level.commands.AdminTopCommand;
import world.bentobox.level.commands.IslandLevelCommand;
import world.bentobox.level.commands.IslandTopCommand;
@ -39,7 +40,6 @@ import world.bentobox.level.requests.TopTenRequestHandler;
import world.bentobox.visit.VisitAddon;
import world.bentobox.warps.Warp;
/**
* @author tastybento
*
@ -71,7 +71,6 @@ public class Level extends Addon {
*/
private VisitAddon visitHook;
@Override
public void onLoad() {
// Save the default config from config.yml
@ -112,8 +111,7 @@ public class Level extends Addon {
// Register commands for GameModes
registeredGameModes.clear();
getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName()))
.forEach(gm -> {
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> {
log("Level hooking into " + gm.getDescription().getName());
registerCommands(gm);
new PlaceholderManager(this).registerPlaceholders(gm);
@ -142,7 +140,8 @@ public class Level extends Addon {
if (compareVersions(advChest.getDescription().getVersion(), "23.0") > 0) {
log("Hooked into AdvancedChests.");
} else {
logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion() + " - requires version 23.0 or later");
logError("Could not hook into AdvancedChests " + advChest.getDescription().getVersion()
+ " - requires version 23.0 or later");
advChestEnabled = false;
}
}
@ -166,40 +165,34 @@ public class Level extends Addon {
}
@Override
public void allLoaded()
{
public void allLoaded() {
super.allLoaded();
if (this.isEnabled())
{
if (this.isEnabled()) {
this.hookExtensions();
}
}
/**
* This method tries to hook into addons and plugins
*/
private void hookExtensions()
{
private void hookExtensions() {
// Try to find Visit addon and if it does not exist, display a warning
this.getAddonByName("Visit").ifPresentOrElse(addon ->
{
this.getAddonByName("Visit").ifPresentOrElse(addon -> {
this.visitHook = (VisitAddon) addon;
this.log("Level Addon hooked into Visit addon.");
}, () -> this.visitHook = null);
// Try to find Warps addon and if it does not exist, display a warning
this.getAddonByName("Warps").ifPresentOrElse(addon ->
{
this.getAddonByName("Warps").ifPresentOrElse(addon -> {
this.warpHook = (Warp) addon;
this.log("Level Addon hooked into Warps addon.");
}, () -> this.warpHook = null);
}
/**
* Compares versions
*
* @param version1 version 1
* @param version2 version 2
* @return {@code <0 if version 1 is older than version 2, =0 if the same, >0 if version 1 is newer than version 2}
@ -231,6 +224,7 @@ public class Level extends Addon {
if (getSettings().isZeroNewIslandLevels()) {
new AdminSetInitialLevelCommand(this, adminCommand);
}
new AdminStatsCommand(this, adminCommand);
});
gm.getPlayerCommand().ifPresent(playerCmd -> {
new IslandLevelCommand(this, playerCmd);
@ -263,7 +257,6 @@ public class Level extends Addon {
}
/**
* @return the blockConfig
*/
@ -294,6 +287,7 @@ public class Level extends Addon {
/**
* Set the config settings - used for tests only
*
* @param configSettings - config settings
*/
void setSettings(ConfigSettings configSettings) {
@ -317,6 +311,7 @@ public class Level extends Addon {
/**
* 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
*/
@ -326,6 +321,7 @@ public class Level extends Addon {
/**
* Sets the player's level to a value
*
* @param world - world
* @param targetPlayer - target player
* @param level - level
@ -336,6 +332,7 @@ public class Level extends Addon {
/**
* Zeros the initial island level
*
* @param island - island
* @param level - initial calculated island level
*/
@ -345,6 +342,7 @@ public class Level extends Addon {
/**
* Get the initial island level
*
* @param island - island
* @return level or 0 by default
*/
@ -354,19 +352,23 @@ public class Level extends Addon {
/**
* Calculates a user's island
*
* @param world - the world where this island is
* @param user - not used! See depecration message
* @param playerUUID - the target island member's UUID
* @deprecated Do not use this anymore. Use getManager().calculateLevel(playerUUID, island)
* @deprecated Do not use this anymore. Use
* getManager().calculateLevel(playerUUID, island)
*/
@Deprecated(since = "2.3.0", forRemoval = true)
public void calculateIslandLevel(World world, @Nullable User user, @NonNull UUID playerUUID) {
Island island = getIslands().getIsland(world, playerUUID);
if (island != null) getManager().calculateLevel(playerUUID, island);
if (island != null)
getManager().calculateLevel(playerUUID, island);
}
/**
* Provide the levels data for the target player
*
* @param targetPlayer - UUID of target player
* @return LevelsData object or null if not found. Only island levels are set!
* @deprecated Do not use this anymore. Use {@link #getIslandLevel(World, UUID)}
@ -375,8 +377,7 @@ public class Level extends Addon {
public LevelsData getLevelsData(UUID targetPlayer) {
LevelsData ld = new LevelsData(targetPlayer);
getPlugin().getAddonsManager().getGameModeAddons().stream()
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName()))
.forEach(gm -> {
.filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())).forEach(gm -> {
if (getSettings().isZeroNewIslandLevels()) {
Island island = getIslands().getIsland(gm.getOverWorld(), targetPlayer);
if (island != null) {
@ -397,6 +398,7 @@ public class Level extends Addon {
/**
* Check if Level addon is active in game mode
*
* @param gm Game Mode Addon
* @return true if active, false if not
*/
@ -406,6 +408,7 @@ public class Level extends Addon {
/**
* Checks if Level addon is active in world
*
* @param world world
* @return true if active, false if not
*/
@ -432,8 +435,7 @@ public class Level extends Addon {
*
* @return {@code Visit} of this object, {@code null} otherwise.
*/
public VisitAddon getVisitHook()
{
public VisitAddon getVisitHook() {
return this.visitHook;
}
@ -442,8 +444,7 @@ public class Level extends Addon {
*
* @return {@code Warp} of this object, {@code null} otherwise.
*/
public Warp getWarpHook()
{
public Warp getWarpHook() {
return this.warpHook;
}
}

View File

@ -31,7 +31,6 @@ import world.bentobox.level.objects.IslandLevels;
import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.objects.TopTenData;
public class LevelsManager {
private static final String INTOPTEN = "intopten";
private static final TreeMap<BigInteger, String> LEVELS;
@ -53,7 +52,6 @@ public class LevelsManager {
// Top ten lists
private final Map<World, TopTenData> topTenLists;
public LevelsManager(Level addon) {
this.addon = addon;
// Get the BentoBox database
@ -76,8 +74,7 @@ public class LevelsManager {
// World
.map(Bukkit::getWorld).filter(Objects::nonNull)
// Island
.map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull)
.forEach(i -> {
.map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull).forEach(i -> {
// Make new database entry
World w = i.getWorld();
IslandLevels il = new IslandLevels(i.getUniqueId());
@ -101,6 +98,7 @@ public class LevelsManager {
/**
* Add a score to the top players list
*
* @param world - world
* @param targetPlayer - target player
* @param lv - island level
@ -108,7 +106,8 @@ public class LevelsManager {
private void addToTopTen(@NonNull World world, @NonNull UUID targetPlayer, long lv) {
// Get top ten
Map<UUID, Long> topTen = topTenLists.computeIfAbsent(world, k -> new TopTenData(world)).getTopTen();
// Remove this player from the top list no matter what (we'll put them back later if required)
// Remove this player from the top list no matter what (we'll put them back
// later if required)
topTen.remove(targetPlayer);
// Get the island
@ -121,21 +120,24 @@ public class LevelsManager {
/**
* Add an island to a top ten
*
* @param island - island to add
* @param lv - level
* @return true if successful, false if not added
*/
private boolean addToTopTen(Island island, long lv) {
if (island != null && island.getOwner() != null && hasTopTenPerm(island.getWorld(), island.getOwner())) {
topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld()))
.getTopTen().put(island.getOwner(), lv);
topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())).getTopTen()
.put(island.getOwner(), lv);
return true;
}
return false;
}
/**
* Calculate the island level, set all island member's levels to the result and try to add the owner to the top ten
* 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
@ -150,7 +152,8 @@ public class LevelsManager {
}
// 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
// Results are irrelevant because the island is unowned or deleted, or
// IslandLevelCalcEvent is cancelled
if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) {
result.complete(null);
}
@ -164,6 +167,7 @@ public class LevelsManager {
/**
* Fires the IslandLevelCalculatedEvent and returns true if it is canceled
*
* @param targetPlayer - target player
* @param island - island
* @param results - results set
@ -173,23 +177,28 @@ public class LevelsManager {
// Fire post calculation event
IslandLevelCalculatedEvent ilce = new IslandLevelCalculatedEvent(targetPlayer, island, results);
Bukkit.getPluginManager().callEvent(ilce);
if (ilce.isCancelled()) return true;
if (ilce.isCancelled())
return true;
// Set the values if they were altered
results.setLevel((Long) ilce.getKeyValues().getOrDefault("level", results.getLevel()));
results.setInitialLevel((Long) ilce.getKeyValues().getOrDefault("initialLevel", results.getInitialLevel()));
results.setDeathHandicap((int) ilce.getKeyValues().getOrDefault("deathHandicap", results.getDeathHandicap()));
results.setPointsToNextLevel((Long)ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel()));
results.setPointsToNextLevel(
(Long) ilce.getKeyValues().getOrDefault("pointsToNextLevel", results.getPointsToNextLevel()));
results.setTotalPoints((Long) ilce.getKeyValues().getOrDefault("totalPoints", results.getTotalPoints()));
return ((Boolean) ilce.getKeyValues().getOrDefault("isCancelled", false));
}
/**
* Get the string representation of the level. May be converted to shorthand notation, e.g., 104556 = 10.5k
* Get the string representation of the level. May be converted to shorthand
* notation, e.g., 104556 = 10.5k
*
* @param lvl - long value to represent
* @return string of the level.
*/
public String formatLevel(@Nullable Long lvl) {
if (lvl == null) return "";
if (lvl == null)
return "";
String level = String.valueOf(lvl);
// Asking for the level of another player
if (addon.getSettings().isShorthand()) {
@ -202,7 +211,8 @@ public class LevelsManager {
// 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();
level = new DecimalFormat("#.#").format(
levelValue.divide(stage.getKey().divide(THOUSAND)).doubleValue() / 1000.0) + stage.getValue();
}
}
return level;
@ -210,6 +220,7 @@ public class LevelsManager {
/**
* Get the initial level of the island. Used to zero island levels
*
* @param island - island
* @return initial level of island
*/
@ -219,12 +230,15 @@ public class LevelsManager {
/**
* Get level of island from cache for a player.
*
* @param world - world where the island is
* @param targetPlayer - target player UUID
* @return Level of the player's island or zero if player is unknown or UUID is null
* @return Level of the player's island or zero if player is unknown or UUID is
* null
*/
public long getIslandLevel(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) return 0L;
if (targetPlayer == null)
return 0L;
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? 0L : getLevelsData(island).getLevel();
@ -232,12 +246,15 @@ public class LevelsManager {
/**
* Get the maximum level ever given to this island
*
* @param world - world where the island is
* @param targetPlayer - target player UUID
* @return Max level of the player's island or zero if player is unknown or UUID is null
* @return Max level of the player's island or zero if player is unknown or UUID
* is null
*/
public long getIslandMaxLevel(@NonNull World world, @Nullable UUID targetPlayer) {
if (targetPlayer == null) return 0L;
if (targetPlayer == null)
return 0L;
// Get the island
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? 0L : getLevelsData(island).getMaxLevel();
@ -245,9 +262,11 @@ public class LevelsManager {
/**
* 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
* @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));
@ -255,6 +274,7 @@ public class LevelsManager {
/**
* Load a level data for the island from the cache or database.
*
* @param island - UUID of island
* @return IslandLevels object
*/
@ -281,19 +301,24 @@ public class LevelsManager {
}
/**
* Get the number of points required until the next level since the last level calc
* 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 "";
if (targetPlayer == null)
return "";
Island island = addon.getIslands().getIsland(world, targetPlayer);
return island == null ? "" : String.valueOf(getLevelsData(island).getPointsToNextLevel());
}
/**
* Get the top ten for this world. Returns offline players or players with the intopten permission.
* 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
@ -303,11 +328,9 @@ public class LevelsManager {
createAndCleanRankings(world);
// Return the sorted map
return Collections.unmodifiableMap(topTenLists.get(world).getTopTen().entrySet().stream()
.filter(e -> addon.getIslands().isOwner(world, e.getKey()))
.filter(l -> l.getValue() > 0)
.filter(e -> addon.getIslands().hasIsland(world, e.getKey())).filter(l -> l.getValue() > 0)
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(size)
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)));
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)));
}
void createAndCleanRankings(@NonNull World world) {
@ -319,12 +342,13 @@ public class LevelsManager {
/**
* @return the topTenLists
*/
protected Map<World, TopTenData> getTopTenLists() {
public Map<World, TopTenData> getTopTenLists() {
return topTenLists;
}
/**
* Get the rank of the player in the rankings
*
* @param world - world
* @param uuid - player UUID
* @return rank placing - note - placing of 1 means top ranked
@ -332,21 +356,22 @@ public class LevelsManager {
public int getRank(@NonNull World world, UUID uuid) {
createAndCleanRankings(world);
Stream<Entry<UUID, Long>> stream = topTenLists.get(world).getTopTen().entrySet().stream()
.filter(e -> addon.getIslands().isOwner(world, e.getKey()))
.filter(l -> l.getValue() > 0)
.filter(e -> addon.getIslands().isOwner(world, e.getKey())).filter(l -> l.getValue() > 0)
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
return (int) (stream.takeWhile(x -> !x.getKey().equals(uuid)).map(Map.Entry::getKey).count() + 1);
}
/**
* Checks if player has the correct top ten perm to have their level saved
*
* @param world
* @param targetPlayer
* @return true if player has the perm or the player is offline
*/
boolean hasTopTenPerm(@NonNull World world, @NonNull UUID targetPlayer) {
String permPrefix = addon.getPlugin().getIWM().getPermissionPrefix(world);
return Bukkit.getPlayer(targetPlayer) == null || Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN);
return Bukkit.getPlayer(targetPlayer) == null
|| Bukkit.getPlayer(targetPlayer).hasPermission(permPrefix + INTOPTEN);
}
/**
@ -358,7 +383,8 @@ public class LevelsManager {
addon.log("Generating rankings");
handler.loadObjects().forEach(il -> {
if (il.getLevel() > 0) {
addon.getIslands().getIslandById(il.getUniqueId()).ifPresent(i -> this.addToTopTen(i, il.getLevel()));
addon.getIslands().getIslandById(il.getUniqueId())
.ifPresent(i -> this.addToTopTen(i, il.getLevel()));
}
});
topTenLists.keySet().forEach(w -> addon.log("Generated rankings for " + w.getName()));
@ -366,7 +392,9 @@ public class LevelsManager {
}
/**
* Removes a player from a world's top ten and removes world from player's level data
* 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
*/
@ -379,17 +407,21 @@ public class LevelsManager {
/**
* Set an initial island level
*
* @param island - the island to set. Must have a non-null world
* @param lv - initial island level
*/
public void setInitialIslandLevel(@NonNull Island island, long lv) {
if (island.getWorld() == null) return;
if (island.getWorld() == null)
return;
levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new).setInitialLevel(lv);
handler.saveObjectAsync(levelsCache.get(island.getUniqueId()));
}
/**
* Set the island level for the owner of the island that targetPlayer is a member
* Set the island level for the owner of the island that targetPlayer is a
* member
*
* @param world - world
* @param targetPlayer - player, may be a team member
* @param lv - level
@ -414,7 +446,9 @@ public class LevelsManager {
}
/**
* Set the island level for the owner of the island that targetPlayer is a member
* Set the island level for the owner of the island that targetPlayer is a
* member
*
* @param world - world
* @param owner - owner of the island
* @param r - results of the calculation
@ -422,7 +456,8 @@ public class LevelsManager {
private void setIslandResults(World world, @NonNull UUID owner, Results r) {
// Get the island
Island island = addon.getIslands().getIsland(world, owner);
if (island == null) return;
if (island == null)
return;
IslandLevels ld = levelsCache.computeIfAbsent(island.getUniqueId(), IslandLevels::new);
ld.setLevel(r.getLevel());
ld.setUwCount(Maps.asMap(r.getUwCount().elementSet(), elem -> r.getUwCount().count(elem)));
@ -437,6 +472,7 @@ public class LevelsManager {
/**
* Removes island from cache when it is deleted
*
* @param uniqueId - id of island
*/
public void deleteIsland(String uniqueId) {

View File

@ -0,0 +1,94 @@
package world.bentobox.level.commands;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.World;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.level.Level;
import world.bentobox.level.objects.TopTenData;
public class AdminStatsCommand extends CompositeCommand {
private final Level level;
public AdminStatsCommand(Level addon, CompositeCommand parent) {
super(parent, "stats");
this.level = addon;
new AdminTopRemoveCommand(addon, this);
}
@Override
public void setup() {
this.setPermission("admin.stats");
this.setOnlyPlayer(false);
this.setDescription("admin.stats.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
user.sendMessage("admin.stats.title");
for (Entry<World, TopTenData> en : level.getManager().getTopTenLists().entrySet()) {
user.sendMessage("admin.stats.world", TextVariables.NAME,
level.getPlugin().getIWM().getWorldName(en.getKey()));
Map<UUID, Long> topTen = en.getValue().getTopTen();
if (topTen.isEmpty()) {
user.sendMessage("admin.stats.no-data");
return false;
}
// Calculating basic statistics
long sum = 0, max = Long.MIN_VALUE, min = Long.MAX_VALUE;
Map<Long, Integer> levelFrequency = new HashMap<>();
for (Long level : topTen.values()) {
sum += level;
max = Math.max(max, level);
min = Math.min(min, level);
levelFrequency.merge(level, 1, Integer::sum);
}
double average = sum / (double) topTen.size();
List<Long> sortedLevels = topTen.values().stream().sorted().collect(Collectors.toList());
long median = sortedLevels.get(sortedLevels.size() / 2);
Long mode = Collections.max(levelFrequency.entrySet(), Map.Entry.comparingByValue()).getKey();
// Logging basic statistics
user.sendMessage("admin.stats.average-level", TextVariables.NUMBER, String.valueOf(average));
user.sendMessage("admin.stats.median-level", TextVariables.NUMBER, String.valueOf(median));
user.sendMessage("admin.stats.mode-level", TextVariables.NUMBER, String.valueOf(mode));
user.sendMessage("admin.stats.highest-level", TextVariables.NUMBER, String.valueOf(max));
user.sendMessage("admin.stats.lowest-level", TextVariables.NUMBER, String.valueOf(min));
// Grouping data for distribution analysis
Map<String, Integer> rangeMap = new TreeMap<>();
for (Long level : topTen.values()) {
String range = getRange(level);
rangeMap.merge(range, 1, Integer::sum);
}
// Logging distribution
user.sendMessage("admin.stats.distribution");
for (Map.Entry<String, Integer> entry : rangeMap.entrySet()) {
user.sendMessage(
entry.getKey() + ": " + entry.getValue() + " " + user.getTranslation("admin.stats.islands"));
}
}
return true;
}
private static String getRange(long level) {
long rangeStart = level / 100 * 100;
long rangeEnd = rangeStart + 99;
return rangeStart + "-" + rangeEnd;
}
}

View File

@ -22,8 +22,18 @@ admin:
remove:
description: "remove player from Top Ten"
parameters: "<player>"
stats:
description: "show stats on islands on this server"
title: "Server Island Stats"
world: "&a [name]"
no-data: "&c No data to process."
average-level: "Average Island Level: [number]"
median-level: "Median Island Level: [number]"
mode-level: "Mode Island Level: [number]"
highest-level: "Highest Island Level: [number]"
lowest-level: "Lowest Island Level: [number]"
distribution: "Island Level Distribution:"
islands: "islands"
island:
level:
parameters: "[player]"