let there be jank
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
<name>StatsTop</name>
|
<name>StatsTop</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>17</java.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@
|
|||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.1</version>
|
<version>3.8.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>${java.version}</source>
|
<source>16</source>
|
||||||
<target>${java.version}</target>
|
<target>16</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# StatsTop
|
||||||
|
|
||||||
|
Simple plugin adding a command that shows the top players on the server for given statistic.
|
||||||
|
|
||||||
|
Some stats are probably broken or don't have correct translation strings set.
|
||||||
|
|
||||||
|
This project is completely unsupported, use at your own risk.
|
||||||
@@ -1,2 +1,86 @@
|
|||||||
package me.youhavetrouble.statstop;public class StatsData {
|
package me.youhavetrouble.statstop;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Statistic;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class StatsData {
|
||||||
|
|
||||||
|
private final LinkedHashMap<String, Integer> data = new LinkedHashMap<>();
|
||||||
|
private final long timestamp;
|
||||||
|
private final Statistic stat;
|
||||||
|
private final Object arg;
|
||||||
|
|
||||||
|
protected StatsData(LinkedHashMap<String, Integer> data, Statistic stat, Object arg) {
|
||||||
|
this.data.putAll(data);
|
||||||
|
this.stat = stat;
|
||||||
|
this.arg = arg;
|
||||||
|
this.timestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getTop(int amount, CommandSender sender) {
|
||||||
|
Component component = Component.text("Top", NamedTextColor.GOLD)
|
||||||
|
.append(Component.space())
|
||||||
|
.append(Component.translatable("stat.minecraft." + stat.toString().toLowerCase(Locale.ENGLISH), NamedTextColor.GOLD));
|
||||||
|
|
||||||
|
if (arg != null) {
|
||||||
|
if (arg instanceof EntityType entityType) {
|
||||||
|
component = component
|
||||||
|
.append(Component.text(" - "))
|
||||||
|
.append(Component.translatable("entity.minecraft." + entityType.toString().toLowerCase(Locale.ENGLISH)));
|
||||||
|
} else if (arg instanceof Material material) {
|
||||||
|
component = component.append(Component.text(" - ", NamedTextColor.GOLD));
|
||||||
|
|
||||||
|
if (material.isBlock()) {
|
||||||
|
component = component.append(Component.translatable("block.minecraft." + material.toString().toLowerCase(Locale.ENGLISH), NamedTextColor.GOLD));
|
||||||
|
} else if (material.isItem()) {
|
||||||
|
component = component.append(Component.translatable("item.minecraft." + material.toString().toLowerCase(Locale.ENGLISH), NamedTextColor.GOLD));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String senderName = null;
|
||||||
|
|
||||||
|
if (sender instanceof Player player) {
|
||||||
|
senderName = player.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<String> names = new HashSet<>(amount);
|
||||||
|
Component senderData = null;
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, Integer> key : data.entrySet()) {
|
||||||
|
if (key.getKey().equals(senderName)) {
|
||||||
|
senderData = getLine(i + 1, key.getKey(), key.getValue(), NamedTextColor.YELLOW);
|
||||||
|
}
|
||||||
|
if (i >= amount && senderData != null) continue;
|
||||||
|
names.add(key.getKey());
|
||||||
|
component = component.append(Component.newline());
|
||||||
|
component = component.append(key.getKey().equals(senderName) ? senderData : getLine(i + 1, key.getKey(), key.getValue(), NamedTextColor.WHITE));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sender instanceof Player player && !names.contains(player.getName()) && senderData != null) {
|
||||||
|
component = component.append(senderData);
|
||||||
|
}
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component getLine(int place, String name, int value, TextColor textColor) {
|
||||||
|
return Component.text(place + ". " + name + " - " + value, textColor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ public final class StatsTop extends JavaPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
// Plugin startup logic
|
getServer().getCommandMap().register("top", new StatsTopCommand());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,245 @@
|
|||||||
package me.youhavetrouble.statstop;public class StatsTopCommand {
|
package me.youhavetrouble.statstop;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.permissions.PermissionDefault;
|
||||||
|
import org.bukkit.util.StringUtil;
|
||||||
|
import org.bukkit.util.permissions.DefaultPermissions;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class StatsTopCommand extends Command {
|
||||||
|
|
||||||
|
private final HashMap<String, StatsData> cachedData = new HashMap<>();
|
||||||
|
|
||||||
|
protected StatsTopCommand() {
|
||||||
|
super("top", "Display top for given stat", "/top <stat> [arg]", new ArrayList<>());
|
||||||
|
setPermission("statstop.top");
|
||||||
|
DefaultPermissions.registerPermission("statstop.top", "Allows you to use /top", PermissionDefault.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
|
||||||
|
if (args.length == 1) {
|
||||||
|
for (Statistic stat : Statistic.values()) {
|
||||||
|
if (StringUtil.startsWithIgnoreCase(stat.toString(), args[0])) {
|
||||||
|
completions.add(stat.toString().toLowerCase(Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (args.length == 2) {
|
||||||
|
Statistic statistic;
|
||||||
|
try {
|
||||||
|
statistic = Statistic.valueOf(args[0].toUpperCase(Locale.ENGLISH));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (statistic.getType()) {
|
||||||
|
case ITEM -> {
|
||||||
|
for (Material material : Material.values()) {
|
||||||
|
if (!material.isItem()) continue;
|
||||||
|
if (StringUtil.startsWithIgnoreCase(material.toString(), args[1])) {
|
||||||
|
completions.add(material.toString().toLowerCase(Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case BLOCK -> {
|
||||||
|
for (Material material : Material.values()) {
|
||||||
|
if (!material.isBlock()) continue;
|
||||||
|
if (StringUtil.startsWithIgnoreCase(material.toString(), args[1])) {
|
||||||
|
completions.add(material.toString().toLowerCase(Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ENTITY -> {
|
||||||
|
for (EntityType entityType : EntityType.values()) {
|
||||||
|
if (StringUtil.startsWithIgnoreCase(entityType.toString(), args[1])) {
|
||||||
|
completions.add(entityType.toString().toLowerCase(Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean execute(@NotNull CommandSender commandSender, @NotNull String s, @NotNull String[] strings) {
|
||||||
|
if (!(commandSender instanceof Player player)) {
|
||||||
|
commandSender.sendMessage(Component.text("Only players can use this command"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strings.length < 1) return false;
|
||||||
|
String statName = strings[0];
|
||||||
|
Statistic stat = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
stat = Statistic.valueOf(statName.toUpperCase(Locale.ENGLISH));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
commandSender.sendMessage(Component.text(getUsage()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object arg = null;
|
||||||
|
|
||||||
|
if (strings.length == 2) {
|
||||||
|
try {
|
||||||
|
arg = Material.valueOf(strings[1].toUpperCase(Locale.ENGLISH));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
}
|
||||||
|
if (arg == null) {
|
||||||
|
try {
|
||||||
|
arg = EntityType.valueOf(strings[1].toUpperCase(Locale.ENGLISH));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arg == null) {
|
||||||
|
commandSender.sendMessage(Component.text(getUsage()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatsData statsData = null;
|
||||||
|
|
||||||
|
switch (stat.getType()) {
|
||||||
|
case UNTYPED -> {
|
||||||
|
statsData = cachedData.get(getStatId(stat, null));
|
||||||
|
if (statsData == null || statsData.getTimestamp() + 60000 < System.currentTimeMillis()) {
|
||||||
|
statsData = createData(stat);
|
||||||
|
cachedData.put(getStatId(stat, null), statsData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ITEM, BLOCK -> {
|
||||||
|
if (!(arg instanceof Material material)) return false;
|
||||||
|
statsData = cachedData.get(getStatId(stat, arg));
|
||||||
|
if (statsData == null || statsData.getTimestamp() + 60000 < System.currentTimeMillis()) {
|
||||||
|
statsData = createData(stat, material);
|
||||||
|
cachedData.put(getStatId(stat, arg), statsData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ENTITY -> {
|
||||||
|
if (!(arg instanceof EntityType entityType)) return false;
|
||||||
|
statsData = cachedData.get(getStatId(stat, arg));
|
||||||
|
if (statsData == null || statsData.getTimestamp() + 60000 < System.currentTimeMillis()) {
|
||||||
|
statsData = createData(stat, entityType);
|
||||||
|
cachedData.put(getStatId(stat, arg), statsData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statsData == null) {
|
||||||
|
commandSender.sendMessage(Component.text("Error getting data"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component topComponent = statsData.getTop(10, commandSender);
|
||||||
|
commandSender.sendMessage(topComponent);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStatId(Statistic stat, Object arg) {
|
||||||
|
|
||||||
|
String statKey = stat.getKey().getKey();
|
||||||
|
|
||||||
|
return switch (stat.getType()) {
|
||||||
|
case UNTYPED -> statKey;
|
||||||
|
case ITEM, BLOCK -> {
|
||||||
|
if (arg instanceof Material item) {
|
||||||
|
yield statKey + "." + item;
|
||||||
|
} else {
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ENTITY -> {
|
||||||
|
if (arg instanceof EntityType entityType) {
|
||||||
|
yield statKey + "." + entityType;
|
||||||
|
} else {
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a map of player names to their stat value. This will return null for stats that are not UNTYPED.
|
||||||
|
*
|
||||||
|
* @param stat statistic to get data for
|
||||||
|
* @return map of player names to their stat value
|
||||||
|
*/
|
||||||
|
private StatsData createData(Statistic stat) {
|
||||||
|
if (stat.getType() != Statistic.Type.UNTYPED) return null;
|
||||||
|
HashMap<String, Integer> data = new HashMap<>();
|
||||||
|
for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) {
|
||||||
|
int statValue = offlinePlayer.getStatistic(stat);
|
||||||
|
String playerName = offlinePlayer.getName();
|
||||||
|
data.put(playerName, statValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedHashMap<String, Integer> sortedData = new LinkedHashMap<>();
|
||||||
|
data.entrySet()
|
||||||
|
.stream()
|
||||||
|
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||||
|
.forEachOrdered(x -> sortedData.put(x.getKey(), x.getValue()));
|
||||||
|
|
||||||
|
return new StatsData(sortedData, stat, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a map of player names to their stat value. This will return null for stats that are not ENTITY.
|
||||||
|
*
|
||||||
|
* @param stat statistic to get data for
|
||||||
|
* @return map of player names to their stat value
|
||||||
|
*/
|
||||||
|
private StatsData createData(Statistic stat, EntityType entityType) {
|
||||||
|
if (stat.getType() != Statistic.Type.ENTITY) return null;
|
||||||
|
HashMap<String, Integer> data = new HashMap<>();
|
||||||
|
for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) {
|
||||||
|
int statValue = offlinePlayer.getStatistic(stat, entityType);
|
||||||
|
String playerName = offlinePlayer.getName();
|
||||||
|
data.put(playerName, statValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedHashMap<String, Integer> sortedData = new LinkedHashMap<>();
|
||||||
|
data.entrySet()
|
||||||
|
.stream()
|
||||||
|
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||||
|
.forEachOrdered(x -> sortedData.put(x.getKey(), x.getValue()));
|
||||||
|
|
||||||
|
return new StatsData(sortedData, stat, entityType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a map of player names to their stat value. This will return null for stats that are not BLOCK or ITEM.
|
||||||
|
*
|
||||||
|
* @param stat statistic to get data for
|
||||||
|
* @return map of player names to their stat value
|
||||||
|
*/
|
||||||
|
private StatsData createData(Statistic stat, Material item) {
|
||||||
|
if (stat.getType() != Statistic.Type.ITEM && stat.getType() != Statistic.Type.BLOCK) return null;
|
||||||
|
HashMap<String, Integer> data = new HashMap<>();
|
||||||
|
for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) {
|
||||||
|
int statValue = offlinePlayer.getStatistic(stat, item);
|
||||||
|
String playerName = offlinePlayer.getName();
|
||||||
|
data.put(playerName, statValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedHashMap<String, Integer> sortedData = new LinkedHashMap<>();
|
||||||
|
data.entrySet()
|
||||||
|
.stream()
|
||||||
|
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||||
|
.forEachOrdered(x -> sortedData.put(x.getKey(), x.getValue()));
|
||||||
|
|
||||||
|
return new StatsData(sortedData, stat, item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
name: StatsTop
|
name: StatsTop
|
||||||
version: '${project.version}'
|
version: '${project.version}'
|
||||||
main: me.youhavetrouble.statstop.StatsTop
|
main: me.youhavetrouble.statstop.StatsTop
|
||||||
api-version: 1.20
|
authors:
|
||||||
|
- YouHaveTrouble
|
||||||
|
api-version: "1.20"
|
||||||
|
|||||||
Reference in New Issue
Block a user