mirror of
https://github.com/itHotL/PlayerStats.git
synced 2024-11-22 11:55:17 +01:00
commit
b117c2b88a
@ -5,7 +5,6 @@ import com.gmail.artemis.the.gr8.playerstats.commands.StatCommand;
|
||||
import com.gmail.artemis.the.gr8.playerstats.commands.TabCompleter;
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.listeners.JoinListener;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MessageFactory;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.Bukkit;
|
||||
@ -34,15 +33,14 @@ public class Main extends JavaPlugin {
|
||||
|
||||
//get instances of the classes that should be initialized
|
||||
ConfigHandler config = new ConfigHandler(this);
|
||||
MessageFactory messageFactory = new MessageFactory(config, this);
|
||||
OfflinePlayerHandler offlinePlayerHandler = new OfflinePlayerHandler(config);
|
||||
ThreadManager threadManager = new ThreadManager(this, adventure(), config, offlinePlayerHandler, messageFactory);
|
||||
MessageFactory messageFactory = new MessageFactory(config);
|
||||
ThreadManager threadManager = new ThreadManager(this, adventure(), config, messageFactory);
|
||||
|
||||
//register the commands
|
||||
PluginCommand statcmd = this.getCommand("statistic");
|
||||
if (statcmd != null) {
|
||||
statcmd.setExecutor(new StatCommand(threadManager, adventure(),offlinePlayerHandler, messageFactory));
|
||||
statcmd.setTabCompleter(new TabCompleter(offlinePlayerHandler));
|
||||
statcmd.setExecutor(new StatCommand(threadManager, adventure(), messageFactory));
|
||||
statcmd.setTabCompleter(new TabCompleter());
|
||||
}
|
||||
PluginCommand reloadcmd = this.getCommand("statisticreload");
|
||||
if (reloadcmd != null) reloadcmd.setExecutor(new ReloadCommand(threadManager));
|
||||
|
@ -1,66 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MessageFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ReloadThread extends Thread {
|
||||
|
||||
private final ConfigHandler config;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private final Main plugin;
|
||||
|
||||
private final StatThread statThread;
|
||||
private final CommandSender sender;
|
||||
private final boolean firstTimeLoading;
|
||||
|
||||
public ReloadThread(ConfigHandler c, OfflinePlayerHandler o, Main p, @Nullable StatThread s, @Nullable CommandSender se, boolean firstTime) {
|
||||
config = c;
|
||||
offlinePlayerHandler = o;
|
||||
plugin = p;
|
||||
|
||||
statThread = s;
|
||||
sender = se;
|
||||
firstTimeLoading = firstTime;
|
||||
|
||||
plugin.getLogger().info("ReloadThread created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
if (!firstTimeLoading) {
|
||||
if (statThread != null && statThread.isAlive()) {
|
||||
try {
|
||||
plugin.getLogger().info("Waiting for statThread to finish up...");
|
||||
statThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
plugin.getLogger().warning(e.toString());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
plugin.getLogger().info("Reloading!");
|
||||
if (config.reloadConfig()) {
|
||||
offlinePlayerHandler.updateOfflinePlayerList();
|
||||
|
||||
plugin.getLogger().info("Amount of relevant players: " + offlinePlayerHandler.getOfflinePlayerCount());
|
||||
plugin.logTimeTaken("ReloadThread", "loading offline players", time);
|
||||
if (sender != null) {
|
||||
sender.sendMessage(MessageFactory.getPluginPrefix() + ChatColor.GREEN + "Config reloaded!");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
plugin.getLogger().info("Loading offline players...");
|
||||
offlinePlayerHandler.updateOfflinePlayerList();
|
||||
plugin.getLogger().info("Amount of relevant players: " + offlinePlayerHandler.getOfflinePlayerCount());
|
||||
plugin.logTimeTaken("ReloadThread", "loading offline players", time);
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +1,46 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.TestFileHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MessageFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
|
||||
public class ThreadManager {
|
||||
|
||||
private static final int threshold = 10;
|
||||
|
||||
private final Main plugin;
|
||||
private final BukkitAudiences adventure;
|
||||
private final ConfigHandler config;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static ConfigHandler config;
|
||||
private static TestFileHandler testFile;
|
||||
private final MessageFactory messageFactory;
|
||||
|
||||
private ReloadThread reloadThread;
|
||||
private StatThread statThread;
|
||||
private static long lastRecordedCalcTime;
|
||||
|
||||
public ThreadManager(Main p, BukkitAudiences b, ConfigHandler c, OfflinePlayerHandler o, MessageFactory m) {
|
||||
public ThreadManager(Main p, BukkitAudiences b, ConfigHandler c, MessageFactory m) {
|
||||
plugin = p;
|
||||
adventure = b;
|
||||
config = c;
|
||||
offlinePlayerHandler = o;
|
||||
messageFactory = m;
|
||||
|
||||
testFile = new TestFileHandler(plugin);
|
||||
startReloadThread(null, true);
|
||||
}
|
||||
|
||||
public void startReloadThread(CommandSender sender, boolean firstTimeLoading) {
|
||||
reloadThread = new ReloadThread(config, offlinePlayerHandler, plugin, statThread, sender, firstTimeLoading);
|
||||
reloadThread = new ReloadThread(threshold, adventure, config, testFile, messageFactory, plugin, statThread, sender, firstTimeLoading);
|
||||
reloadThread.start();
|
||||
}
|
||||
|
||||
public void startStatThread(StatRequest request) {
|
||||
statThread = new StatThread(request, reloadThread, adventure, config, offlinePlayerHandler, messageFactory, plugin);
|
||||
statThread = new StatThread(threshold, request, reloadThread, adventure, config, testFile, messageFactory, plugin);
|
||||
statThread.start();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.commands;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Query;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
@ -19,41 +20,109 @@ public class StatCommand implements CommandExecutor {
|
||||
|
||||
private final ThreadManager threadManager;
|
||||
private final BukkitAudiences adventure;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private final MessageFactory messageFactory;
|
||||
|
||||
public StatCommand(ThreadManager t, BukkitAudiences b, OfflinePlayerHandler of, MessageFactory o) {
|
||||
public StatCommand(ThreadManager t, BukkitAudiences b, MessageFactory o) {
|
||||
threadManager = t;
|
||||
adventure = b;
|
||||
offlinePlayerHandler = of;
|
||||
messageFactory = o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
//part 1: collecting all relevant information from the args
|
||||
if (args.length >= 1) {
|
||||
if (args.length >= 1) { //part 1: collecting all relevant information from the args
|
||||
StatRequest request = generateRequest(sender, args);
|
||||
|
||||
//part 2: sending the information to the StatThread, or give feedback if request is invalid
|
||||
if (isValidStatRequest(request)) {
|
||||
if (isValidStatRequest(request)) { //part 2: sending the information to the StatThread
|
||||
threadManager.startStatThread(request);
|
||||
return true;
|
||||
}
|
||||
|
||||
else {
|
||||
else { //part 2: or give feedback if request is invalid
|
||||
adventure.sender(sender).sendMessage(getRelevantFeedback(request));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//in case of less than 1 argument, always display the help message
|
||||
else {
|
||||
else { //in case of less than 1 argument, display the help message
|
||||
adventure.sender(sender).sendMessage(messageFactory.helpMsg());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//create a StatRequest Object with all the relevant information from the args
|
||||
private StatRequest generateRequest(CommandSender sender, String[] args) {
|
||||
StatRequest request = new StatRequest(sender);
|
||||
|
||||
for (String arg : args) {
|
||||
//check for statName
|
||||
if (EnumHandler.isStatistic(arg) && request.getStatName() == null) {
|
||||
request.setStatName(arg);
|
||||
}
|
||||
//check for subStatEntry and playerFlag
|
||||
else if (EnumHandler.isSubStatEntry(arg)) {
|
||||
if (arg.equalsIgnoreCase("player") && !request.playerFlag()) {
|
||||
request.setPlayerFlag(true);
|
||||
}
|
||||
else {
|
||||
if (request.getSubStatEntry() == null) request.setSubStatEntry(arg);
|
||||
}
|
||||
}
|
||||
//check for selection
|
||||
else if (request.getSelection() == null) {
|
||||
if (arg.equalsIgnoreCase("top")) {
|
||||
request.setSelection(Query.TOP);
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("server")) {
|
||||
request.setSelection(Query.SERVER);
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("me") && sender instanceof Player) {
|
||||
request.setPlayerName(sender.getName());
|
||||
request.setSelection(Query.PLAYER);
|
||||
}
|
||||
else if (OfflinePlayerHandler.isOfflinePlayerName(arg) && request.getPlayerName() == null) {
|
||||
request.setPlayerName(arg);
|
||||
request.setSelection(Query.PLAYER);
|
||||
}
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
//part 2: check whether all necessary ingredients are present to proceed with a lookup
|
||||
private boolean isValidStatRequest(StatRequest request) {
|
||||
if (request.getStatName() != null) {
|
||||
if (request.playerFlag()) unpackPlayerFlag(request);
|
||||
if (request.getSelection() == null) assumeTopAsDefault(request);
|
||||
if (request.getSubStatEntry() != null) verifySubStat(request);
|
||||
|
||||
return EnumHandler.isValidStatEntry(request.getStatType(), request.getSubStatEntry());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//account for the fact that "player" could be either a subStatEntry, a flag to indicate the target for the lookup, or both
|
||||
private void unpackPlayerFlag(StatRequest request) {
|
||||
if (request.getStatType() == Statistic.Type.ENTITY && request.getSubStatEntry() == null) {
|
||||
request.setSubStatEntry("player");
|
||||
}
|
||||
if (request.getSelection() == null) {
|
||||
request.setSelection(Query.PLAYER);
|
||||
}
|
||||
}
|
||||
|
||||
//in case the statistic is untyped, set the unnecessary subStatEntry to null
|
||||
private void verifySubStat(StatRequest request) {
|
||||
if (request.getSubStatEntry() != null && request.getStatType() == Statistic.Type.UNTYPED) {
|
||||
request.setSubStatEntry(null);
|
||||
}
|
||||
}
|
||||
|
||||
//if no playerName was provided, and there is no topFlag or serverFlag, substitute a top flag
|
||||
private void assumeTopAsDefault(StatRequest request) {
|
||||
request.setSelection(Query.TOP);
|
||||
}
|
||||
|
||||
//call this method when isValidStatRequest has returned false to get a relevant error-message
|
||||
private TextComponent getRelevantFeedback(@NotNull StatRequest request) {
|
||||
if (request.getStatName() == null) {
|
||||
return messageFactory.missingStatName();
|
||||
@ -64,74 +133,9 @@ public class StatCommand implements CommandExecutor {
|
||||
else if (!EnumHandler.isValidStatEntry(request.getStatType(), request.getSubStatEntry())){
|
||||
return messageFactory.wrongSubStatType(request.getStatType(), request.getSubStatEntry());
|
||||
}
|
||||
else if (!request.topFlag()) {
|
||||
if (!request.playerFlag()) {
|
||||
return messageFactory.missingTarget();
|
||||
}
|
||||
else {
|
||||
return messageFactory.missingPlayerName();
|
||||
}
|
||||
else if (request.getSelection() == Query.PLAYER && request.getPlayerName() == null) {
|
||||
return messageFactory.missingPlayerName();
|
||||
}
|
||||
return messageFactory.unknownError();
|
||||
}
|
||||
|
||||
//part 1: create a StatRequest Object with all the relevant information from the args
|
||||
private StatRequest generateRequest(CommandSender sender, String[] args) {
|
||||
StatRequest request = new StatRequest(sender);
|
||||
|
||||
for (String arg : args) {
|
||||
if (EnumHandler.isStatistic(arg) && request.getStatName() == null) {
|
||||
request.setStatName(arg);
|
||||
}
|
||||
else if (EnumHandler.isSubStatEntry(arg)) {
|
||||
if (arg.equalsIgnoreCase("player")) {
|
||||
if (request.playerFlag()) {
|
||||
if (request.getSubStatEntry() == null) request.setSubStatEntry(arg);
|
||||
}
|
||||
else {
|
||||
request.setPlayerFlag(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (request.getSubStatEntry() == null) request.setSubStatEntry(arg);
|
||||
}
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("top")) {
|
||||
request.setTopFlag(true);
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("me") && sender instanceof Player) {
|
||||
request.setPlayerName(sender.getName());
|
||||
}
|
||||
else if (offlinePlayerHandler.isOfflinePlayerName(arg) && request.getPlayerName() == null) {
|
||||
request.setPlayerName(arg);
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
//part 2: check whether all necessary ingredients are present to proceed with a lookup
|
||||
private boolean isValidStatRequest(StatRequest request) {
|
||||
validatePlayerFlag(request);
|
||||
removeUnnecessarySubStat(request);
|
||||
|
||||
if (request.getStatName() != null) {
|
||||
if (request.topFlag() || request.getPlayerName() != null) {
|
||||
return EnumHandler.isValidStatEntry(request.getStatType(), request.getSubStatEntry());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//account for the fact that "player" could be either a subStatEntry or a flag to indicate the target for the lookup, and correct the request if necessary
|
||||
private void validatePlayerFlag(StatRequest request) {
|
||||
if (request.getStatType() == Statistic.Type.ENTITY && request.getSubStatEntry() == null && request.playerFlag()) {
|
||||
request.setSubStatEntry("player");
|
||||
}
|
||||
}
|
||||
|
||||
private void removeUnnecessarySubStat(StatRequest request) {
|
||||
if (request.getSubStatEntry() != null && request.getStatType() == Statistic.Type.UNTYPED) {
|
||||
request.setSubStatEntry(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,16 +12,14 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private final List<String> commandOptions;
|
||||
|
||||
|
||||
public TabCompleter(OfflinePlayerHandler o) {
|
||||
offlinePlayerHandler = o;
|
||||
|
||||
public TabCompleter() {
|
||||
commandOptions = new ArrayList<>();
|
||||
commandOptions.add("top");
|
||||
commandOptions.add("player");
|
||||
commandOptions.add("server");
|
||||
commandOptions.add("me");
|
||||
}
|
||||
|
||||
@ -61,7 +59,7 @@ public class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
tabSuggestions = commandOptions;
|
||||
}
|
||||
else {
|
||||
tabSuggestions = offlinePlayerHandler.getOfflinePlayerNames().stream().filter(player ->
|
||||
tabSuggestions = OfflinePlayerHandler.getOfflinePlayerNames().stream().filter(player ->
|
||||
player.toLowerCase().contains(args[args.length-1].toLowerCase())).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.enums;
|
||||
|
||||
public enum Query {
|
||||
PLAYER, SERVER, TOP
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.filehandlers;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Query;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
@ -17,9 +19,10 @@ public class ConfigHandler {
|
||||
public ConfigHandler(Main p) {
|
||||
plugin = p;
|
||||
saveDefaultConfig();
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
}
|
||||
|
||||
//reload the config after changes have been made to it
|
||||
/** Reloads the config from file, or creates a new file with default values if there is none. */
|
||||
public boolean reloadConfig() {
|
||||
try {
|
||||
if (!configFile.exists()) {
|
||||
@ -34,67 +37,51 @@ public class ConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
//returns the config setting for include-whitelist-only, or the default value "false"
|
||||
/** Returns the config setting for include-whitelist-only, or the default value "false". */
|
||||
public boolean whitelistOnly() {
|
||||
try {
|
||||
return config.getBoolean("include-whitelist-only");
|
||||
}
|
||||
catch (Exception e) {
|
||||
plugin.getLogger().warning(e.toString());
|
||||
return false;
|
||||
}
|
||||
return config.getBoolean("include-whitelist-only", false);
|
||||
}
|
||||
|
||||
//returns the config setting for exclude-banned-players, or the default value "false"
|
||||
/** Returns the config setting for exclude-banned-players, or the default value "false". */
|
||||
public boolean excludeBanned() {
|
||||
try {
|
||||
return config.getBoolean("exclude-banned-players");
|
||||
}
|
||||
catch (Exception e) {
|
||||
plugin.getLogger().warning(e.toString());
|
||||
return false;
|
||||
}
|
||||
return config.getBoolean("exclude-banned-players", false);
|
||||
}
|
||||
|
||||
//returns the number of maximum days since a player has last been online, or the default value of 0 to not use this constraint
|
||||
/** Returns the number of maximum days since a player has last been online, or the default value of 0 to not use this constraint. */
|
||||
public int lastPlayedLimit() {
|
||||
try {
|
||||
return config.getInt("number-of-days-since-last-joined");
|
||||
}
|
||||
catch (Exception e) {
|
||||
plugin.getLogger().warning(e.toString());
|
||||
return 0;
|
||||
}
|
||||
return config.getInt("number-of-days-since-last-joined", 0);
|
||||
}
|
||||
|
||||
//returns the config setting for top-list-max-size, or the default value of 10 if no value can be retrieved
|
||||
/** Returns the config setting for top-list-max-size, or the default value of 10 if no value can be retrieved. */
|
||||
public int getTopListMaxSize() {
|
||||
try {
|
||||
return config.getInt("top-list-max-size");
|
||||
}
|
||||
catch (Exception e) {
|
||||
plugin.getLogger().warning(e.toString());
|
||||
return 10;
|
||||
}
|
||||
return config.getInt("top-list-max-size", 10);
|
||||
}
|
||||
|
||||
//returns the config setting for use-dots, or the default value "true" if no value can be retrieved
|
||||
/** Returns the config setting for use-dots, or the default value "true" if no value can be retrieved. */
|
||||
public boolean useDots() {
|
||||
try {
|
||||
return config.getBoolean("use-dots");
|
||||
}
|
||||
catch (Exception e) {
|
||||
plugin.getLogger().warning(e.toString());
|
||||
return true;
|
||||
}
|
||||
return config.getBoolean("use-dots", true);
|
||||
}
|
||||
|
||||
public String getPlayerNameFormatting(boolean topStat, boolean isStyle) {
|
||||
return getStringFromConfig(topStat, isStyle, "player-names");
|
||||
/** Returns the specified server name, or "this server" if no value can be retrieved. */
|
||||
public String getServerName() {
|
||||
return config.getString("your-server-name", "this server");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are "none" for Style,
|
||||
and "green" or "gold" for Color (for top or individual color). */
|
||||
public String getPlayerNameFormatting(Query selection, boolean isStyle) {
|
||||
String def;
|
||||
if (selection == Query.TOP) {
|
||||
def = "green";
|
||||
}
|
||||
else {
|
||||
def = "gold";
|
||||
}
|
||||
return getStringFromConfig(selection, isStyle, def, "player-names");
|
||||
}
|
||||
|
||||
public boolean playerNameIsBold() {
|
||||
ConfigurationSection style = getRelevantSection(true, true);
|
||||
ConfigurationSection style = getRelevantSection(Query.PLAYER);
|
||||
|
||||
if (style != null) {
|
||||
String styleString = style.getString("player-names");
|
||||
@ -103,53 +90,96 @@ public class ConfigHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getStatNameFormatting(boolean topStat, boolean isStyle) {
|
||||
return getStringFromConfig(topStat, isStyle, "stat-names");
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are "none" for Style,
|
||||
and "yellow" for Color. */
|
||||
public String getStatNameFormatting(Query selection, boolean isStyle) {
|
||||
return getStringFromConfig(selection, isStyle, "yellow", "stat-names");
|
||||
}
|
||||
|
||||
public String getSubStatNameFormatting(boolean topStat, boolean isStyle) {
|
||||
return getStringFromConfig(topStat, isStyle, "sub-stat-names");
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are "none" for Style,
|
||||
and "#FFD52B" for Color. */
|
||||
public String getSubStatNameFormatting(Query selection, boolean isStyle) {
|
||||
return getStringFromConfig(selection, isStyle, "#FFD52B", "sub-stat-names");
|
||||
}
|
||||
|
||||
public String getStatNumberFormatting(boolean topStat, boolean isStyle) {
|
||||
return getStringFromConfig(topStat, isStyle, "stat-numbers");
|
||||
}
|
||||
|
||||
public String getListNumberFormatting(boolean isStyle) {
|
||||
return getStringFromConfig(true, isStyle, "list-numbers");
|
||||
}
|
||||
|
||||
public String getDotsColor() {
|
||||
return getStringFromConfig(true, false, "dots");
|
||||
}
|
||||
|
||||
//returns the config value for a color or style option in string-format, or null if no value was found
|
||||
private String getStringFromConfig(boolean topStat, boolean isStyle, String pathName){
|
||||
ConfigurationSection section = getRelevantSection(topStat, isStyle);
|
||||
return section != null ? section.getString(pathName) : null;
|
||||
}
|
||||
|
||||
//returns the config section that contains the relevant color or style option
|
||||
private ConfigurationSection getRelevantSection(boolean topStat, boolean isStyle) {
|
||||
ConfigurationSection section;
|
||||
try {
|
||||
if (!topStat) {
|
||||
if (!isStyle) section = config.getConfigurationSection("individual-statistics-color");
|
||||
else section = config.getConfigurationSection("individual-statistics-style");
|
||||
}
|
||||
else {
|
||||
if (!isStyle) section = config.getConfigurationSection("top-list-color");
|
||||
else section = config.getConfigurationSection("top-list-style");
|
||||
}
|
||||
return section;
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are "none" for Style,
|
||||
and "#55AAFF" or "#ADE7FF" for Color (for the top or individual/server color). */
|
||||
public String getStatNumberFormatting(Query selection, boolean isStyle) {
|
||||
String def;
|
||||
if (selection == Query.TOP) {
|
||||
def = "#55AAFF";
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException exception) {
|
||||
plugin.getLogger().warning(exception.toString());
|
||||
return null;
|
||||
else {
|
||||
def = "#ADE7FF";
|
||||
}
|
||||
return getStringFromConfig(selection, isStyle, def,"stat-numbers");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are "none" for Style,
|
||||
and "yellow" or "gold" for Color (for top/server). */
|
||||
public String getTitleFormatting(Query selection, boolean isStyle) {
|
||||
String def;
|
||||
if (selection == Query.TOP) {
|
||||
def = "yellow";
|
||||
}
|
||||
else {
|
||||
def = "gold";
|
||||
}
|
||||
return getStringFromConfig(selection, isStyle, def, "title");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are "none" for Style,
|
||||
and "gold" for Color. */
|
||||
public String getTitleNumberFormatting(boolean isStyle) {
|
||||
return getStringFromConfig(Query.TOP, isStyle, "gold", "title-number");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are "none" for Style,
|
||||
and "#FFB80E" for Color. */
|
||||
public String getServerNameFormatting(boolean isStyle) {
|
||||
return getStringFromConfig(Query.SERVER, isStyle, "#FFB80E", "server-name");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are "none" for Style,
|
||||
and "gold" for Color. */
|
||||
public String getRankNumberFormatting(boolean isStyle) {
|
||||
return getStringFromConfig(Query.TOP, isStyle, "gold", "rank-numbers");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are "none" for Style,
|
||||
and "dark_gray" for Color. */
|
||||
public String getDotsFormatting(boolean isStyle) {
|
||||
return getStringFromConfig(Query.TOP, isStyle, "dark_gray", "dots");
|
||||
}
|
||||
|
||||
/** Returns the config value for a color or style option in string-format, the supplied default value, or null if no configSection was found. */
|
||||
private @Nullable String getStringFromConfig(Query selection, boolean isStyle, String def, String pathName){
|
||||
String path = isStyle ? pathName + "-style" : pathName;
|
||||
String defaultValue = isStyle ? "none" : def;
|
||||
|
||||
ConfigurationSection section = getRelevantSection(selection);
|
||||
return section != null ? section.getString(path, defaultValue) : null;
|
||||
}
|
||||
|
||||
/** Returns the config section that contains the relevant color or style option. */
|
||||
private @Nullable ConfigurationSection getRelevantSection(Query selection) {
|
||||
switch (selection) {
|
||||
case TOP -> {
|
||||
return config.getConfigurationSection("top-list");
|
||||
}
|
||||
case PLAYER -> {
|
||||
return config.getConfigurationSection("individual-statistics");
|
||||
}
|
||||
case SERVER -> {
|
||||
return config.getConfigurationSection("total-server");
|
||||
}
|
||||
default -> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//create a config file if none exists yet (from the config.yml in the plugin's resources)
|
||||
/** Create a config file if none exists yet (from the config.yml in the plugin's resources). */
|
||||
private void saveDefaultConfig() {
|
||||
config = plugin.getConfig();
|
||||
plugin.saveDefaultConfig();
|
||||
|
@ -0,0 +1,161 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.filehandlers;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TestFileHandler {
|
||||
|
||||
private File testFile;
|
||||
private FileConfiguration testConf;
|
||||
private ConfigurationSection number;
|
||||
private final Main plugin;
|
||||
|
||||
private String onEnable;
|
||||
private String reload;
|
||||
private String debugging;
|
||||
private String topStat;
|
||||
|
||||
public TestFileHandler(Main p) {
|
||||
plugin = p;
|
||||
onEnable = "onEnable";
|
||||
reload = "reload";
|
||||
debugging = "exception-debugging";
|
||||
topStat = "top-stat";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new config section for the given threshold. Only needs to be called once, unless threshold changes.
|
||||
* @param count amount of players to calculate statistics with
|
||||
* @param threshold how small the subTasks have to become
|
||||
*/
|
||||
public void saveThreshold(int count, int threshold) {
|
||||
loadFile(count);
|
||||
String path = threshold + " threshold";
|
||||
try {
|
||||
number = testConf.getConfigurationSection(path);
|
||||
if (number == null) {
|
||||
number = testConf.createSection(path);
|
||||
number.createSection(onEnable);
|
||||
number.createSection(reload);
|
||||
number.createSection(debugging);
|
||||
number.createSection(topStat);
|
||||
}
|
||||
else {
|
||||
number = testConf.getConfigurationSection(path);
|
||||
}
|
||||
saveFile();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void logRunCount(boolean errorEncountered) {
|
||||
try {
|
||||
ConfigurationSection section = number.getConfigurationSection(debugging);
|
||||
if (section != null) {
|
||||
int runs = section.getInt("runs");
|
||||
section.set("runs", runs +1);
|
||||
|
||||
if (errorEncountered) {
|
||||
int errors = section.getInt("errors");
|
||||
section.set("errors", errors + 1);
|
||||
|
||||
String path = "error-" + (errors + 1) + "-during-run";
|
||||
int lastError = section.getInt("error-" + errors + "-during-run");
|
||||
|
||||
int runsUntilError = runs - lastError;
|
||||
String path2 = "until-error-" + (errors + 1);
|
||||
|
||||
section.set(path2, runsUntilError);
|
||||
section.set(path, runs);
|
||||
}
|
||||
saveFile();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs how long a certain method took for the earlier set threshold. Always make sure saveThreshold has been
|
||||
* called once before this method is called.
|
||||
* @param time how long the given action took
|
||||
* @param scenario describes which section to get. 1 means onEnable, 2 means reload, and 3 means top-stat
|
||||
*/
|
||||
public void saveTimeTaken(long time, int scenario) {
|
||||
String path = "";
|
||||
if (scenario == 1) path = onEnable;
|
||||
else if (scenario == 2) path = reload;
|
||||
else if (scenario == 3) path = topStat;
|
||||
|
||||
try {
|
||||
ConfigurationSection section = number.getConfigurationSection(path);
|
||||
if (section != null) {
|
||||
saveTimeToSection(time, section);
|
||||
saveFile();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTimeToSection(long time, ConfigurationSection section) {
|
||||
if (section.contains("average")) {
|
||||
long average = section.getLong("average");
|
||||
long newAverage = ((average * (section.getKeys(false).size() -1)) + time)/section.getKeys(false).size();
|
||||
section.set(section.getKeys(false).size() + "", time);
|
||||
section.set("average", newAverage);
|
||||
}
|
||||
|
||||
else {
|
||||
section.set("average", time);
|
||||
section.set("1", time);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFile(int players) {
|
||||
String fileName = "test_" + players + ".yml";
|
||||
testFile = new File(plugin.getDataFolder(), fileName);
|
||||
if (!testFile.exists()) {
|
||||
plugin.getLogger().info("Attempting to create testFile...");
|
||||
createFile();
|
||||
}
|
||||
|
||||
testConf = new YamlConfiguration();
|
||||
try {
|
||||
testConf.load(testFile);
|
||||
}
|
||||
catch (IOException | InvalidConfigurationException exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
saveFile();
|
||||
}
|
||||
|
||||
private void createFile() {
|
||||
testFile.getParentFile().mkdirs();
|
||||
try {
|
||||
testFile.createNewFile();
|
||||
plugin.getLogger().info("Even though this would return false, secretly a file has been created anyway");
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveFile() {
|
||||
try {
|
||||
testConf.save(testFile);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.reload;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.UnixTimeHandler;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RecursiveAction;
|
||||
|
||||
public class ReloadAction extends RecursiveAction {
|
||||
|
||||
private final int threshold;
|
||||
|
||||
private final OfflinePlayer[] players;
|
||||
private final int start;
|
||||
private final int end;
|
||||
|
||||
private final boolean whitelistOnly;
|
||||
private final boolean excludeBanned;
|
||||
private final int lastPlayedLimit;
|
||||
private final ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||
|
||||
public ReloadAction(int threshold, OfflinePlayer[] players,
|
||||
boolean whitelistOnly, boolean excludeBanned, int lastPlayedLimit,
|
||||
ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
|
||||
|
||||
this(threshold, players, 0, players.length,
|
||||
whitelistOnly, excludeBanned, lastPlayedLimit, offlinePlayerUUIDs);
|
||||
}
|
||||
|
||||
protected ReloadAction(int threshold, OfflinePlayer[] players, int start, int end,
|
||||
boolean whitelistOnly, boolean excludeBanned, int lastPlayedLimit,
|
||||
ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
|
||||
|
||||
this.threshold = threshold;
|
||||
this.players = players;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
|
||||
this.whitelistOnly = whitelistOnly;
|
||||
this.excludeBanned = excludeBanned;
|
||||
this.lastPlayedLimit = lastPlayedLimit;
|
||||
this.offlinePlayerUUIDs = offlinePlayerUUIDs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void compute() {
|
||||
final int length = end - start;
|
||||
if (length < threshold) {
|
||||
process();
|
||||
}
|
||||
else {
|
||||
final int split = length / 2;
|
||||
final ReloadAction subTask1 = new ReloadAction(threshold, players, start, (start + split),
|
||||
whitelistOnly, excludeBanned, lastPlayedLimit, offlinePlayerUUIDs);
|
||||
final ReloadAction subTask2 = new ReloadAction(threshold, players, (start + split), end,
|
||||
whitelistOnly, excludeBanned, lastPlayedLimit, offlinePlayerUUIDs);
|
||||
|
||||
//queue and compute all subtasks in the right order
|
||||
invokeAll(subTask1, subTask2);
|
||||
}
|
||||
}
|
||||
|
||||
private void process() {
|
||||
for (int i = start; i < end; i++) {
|
||||
OfflinePlayer player = players[i];
|
||||
if (player.getName() != null &&
|
||||
(!whitelistOnly || player.isWhitelisted()) &&
|
||||
(!excludeBanned || !player.isBanned()) &&
|
||||
(lastPlayedLimit == 0 || UnixTimeHandler.hasPlayedSince(lastPlayedLimit, player.getLastPlayed()))) {
|
||||
offlinePlayerUUIDs.put(player.getName(), player.getUniqueId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.reload;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.TestFileHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MessageFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
|
||||
public class ReloadThread extends Thread {
|
||||
|
||||
private final int threshold;
|
||||
|
||||
private final BukkitAudiences adventure;
|
||||
private static ConfigHandler config;
|
||||
private static TestFileHandler testFile;
|
||||
private final MessageFactory messageFactory;
|
||||
private final Main plugin;
|
||||
|
||||
private final StatThread statThread;
|
||||
private final CommandSender sender;
|
||||
private final boolean firstTimeLoading;
|
||||
|
||||
public ReloadThread(int threshold, BukkitAudiences b, ConfigHandler c, TestFileHandler t, MessageFactory m, Main p, @Nullable StatThread s, @Nullable CommandSender se, boolean firstTime) {
|
||||
this.threshold = threshold;
|
||||
adventure = b;
|
||||
config = c;
|
||||
testFile = t;
|
||||
messageFactory = m;
|
||||
plugin = p;
|
||||
|
||||
statThread = s;
|
||||
sender = se;
|
||||
firstTimeLoading = firstTime;
|
||||
|
||||
plugin.getLogger().info("ReloadThread created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
if (!firstTimeLoading) {
|
||||
if (statThread != null && statThread.isAlive()) {
|
||||
try {
|
||||
plugin.getLogger().info("Waiting for statThread to finish up...");
|
||||
statThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
plugin.getLogger().warning(e.toString());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
plugin.getLogger().info("Reloading!");
|
||||
if (config.reloadConfig()) {
|
||||
|
||||
try {
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(getPlayerMap(false));
|
||||
} catch (ConcurrentModificationException e) {
|
||||
plugin.getLogger().warning("The request could not be fully executed due to a ConcurrentModificationException");
|
||||
if (sender != null) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.partiallyReloaded());
|
||||
}
|
||||
}
|
||||
|
||||
testFile.saveTimeTaken(System.currentTimeMillis() - time, 2);
|
||||
plugin.getLogger().info("Amount of relevant players: " + OfflinePlayerHandler.getOfflinePlayerCount());
|
||||
plugin.logTimeTaken("ReloadThread", "loading offline players", time);
|
||||
if (sender != null) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.reloadedConfig());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
plugin.getLogger().info("Loading offline players...");
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(getPlayerMap(true));
|
||||
|
||||
testFile.saveThreshold(OfflinePlayerHandler.getOfflinePlayerCount(), threshold);
|
||||
testFile.saveTimeTaken(System.currentTimeMillis() - time, 1);
|
||||
plugin.getLogger().info("Amount of relevant players: " + OfflinePlayerHandler.getOfflinePlayerCount());
|
||||
plugin.logTimeTaken("ReloadThread", "loading offline players", time);
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
}
|
||||
}
|
||||
|
||||
private ConcurrentHashMap<String, UUID> getPlayerMap(boolean firstTimeLoading) {
|
||||
OfflinePlayer[] offlinePlayers = Bukkit.getOfflinePlayers();
|
||||
int size = firstTimeLoading ? offlinePlayers.length : OfflinePlayerHandler.getOfflinePlayerCount();
|
||||
|
||||
ConcurrentHashMap<String, UUID> playerMap = new ConcurrentHashMap<>(size);
|
||||
|
||||
ReloadAction task = new ReloadAction(threshold, offlinePlayers, config.whitelistOnly(), config.excludeBanned(), config.lastPlayedLimit(), playerMap);
|
||||
ForkJoinPool commonPool = ForkJoinPool.commonPool();
|
||||
|
||||
try {
|
||||
commonPool.invoke(task);
|
||||
} catch (ConcurrentModificationException e) {
|
||||
throw new ConcurrentModificationException(e.toString());
|
||||
}
|
||||
return playerMap;
|
||||
}
|
||||
|
||||
private ConcurrentHashMap<String, UUID> generateFakeExtraPlayers(ConcurrentHashMap<String, UUID> realPlayers, int loops) {
|
||||
ConcurrentHashMap<String, UUID> newPlayerMap = new ConcurrentHashMap<>(realPlayers.size() * loops);
|
||||
for (int i = 0; i < loops; i++) {
|
||||
for (String key : realPlayers.keySet()) {
|
||||
newPlayerMap.put(key + i, realPlayers.get(key));
|
||||
}
|
||||
}
|
||||
return newPlayerMap;
|
||||
}
|
||||
}
|
@ -1,45 +1,94 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Query;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StatRequest {
|
||||
|
||||
private final CommandSender sender;
|
||||
private String statName;
|
||||
private Statistic.Type statType;
|
||||
private String subStatEntry;
|
||||
private String playerName;
|
||||
private Query selection;
|
||||
private boolean playerFlag;
|
||||
private boolean topFlag;
|
||||
|
||||
private Statistic statEnum;
|
||||
private Statistic.Type statType;
|
||||
private EntityType entity;
|
||||
private Material block;
|
||||
private Material item;
|
||||
|
||||
//playerFlag and topFlag are false by default, will be set to true if "player" or "top" is in the args
|
||||
public StatRequest(@NotNull CommandSender s) {
|
||||
sender = s;
|
||||
playerFlag = false;
|
||||
topFlag = false;
|
||||
}
|
||||
|
||||
//sets the statName, and automatically tries to set the correct statType and get the corresponding item/block/entity if there is a subStatEntry
|
||||
public void setStatName(String statName) {
|
||||
this.statName = statName;
|
||||
setStatType(statName);
|
||||
}
|
||||
|
||||
private void setStatType(String statName) {
|
||||
if (statName != null) {
|
||||
try {
|
||||
statType = EnumHandler.getStatType(statName);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
setStatEnumAndType();
|
||||
if (subStatEntry != null) {
|
||||
extractSubStat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatEnumAndType() throws IllegalArgumentException {
|
||||
try {
|
||||
statEnum = EnumHandler.getStatEnum(statName);
|
||||
statType = statEnum.getType();
|
||||
} catch (IllegalArgumentException e) {
|
||||
Bukkit.getLogger().warning(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
//sets the subStatEntry, and automatically tries to get the corresponding item/block/entity if there is a valid statType present
|
||||
//if the subStatEntry is set to null, any present item/block/entity is set to null again
|
||||
public void setSubStatEntry(String subStatEntry) {
|
||||
this.subStatEntry = subStatEntry;
|
||||
if (subStatEntry != null && statType != null) {
|
||||
extractSubStat();
|
||||
}
|
||||
else if (subStatEntry == null) {
|
||||
entity = null;
|
||||
item = null;
|
||||
block = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractSubStat() {
|
||||
switch (statType) {
|
||||
case ENTITY -> {
|
||||
try {
|
||||
entity = EnumHandler.getEntityType(subStatEntry);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Bukkit.getLogger().warning(e.toString());
|
||||
}
|
||||
}
|
||||
case ITEM -> {
|
||||
try {
|
||||
item = EnumHandler.getItem(subStatEntry);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Bukkit.getLogger().warning(e.toString());
|
||||
}
|
||||
}
|
||||
case BLOCK -> {
|
||||
try {
|
||||
block = EnumHandler.getBlock(subStatEntry);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Bukkit.getLogger().warning(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPlayerName(String playerName) {
|
||||
@ -52,8 +101,8 @@ public class StatRequest {
|
||||
this.playerFlag = playerFlag;
|
||||
}
|
||||
|
||||
public void setTopFlag(boolean topFlag) {
|
||||
this.topFlag = topFlag;
|
||||
public void setSelection(Query selection) {
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
public CommandSender getCommandSender() {
|
||||
@ -69,10 +118,26 @@ public class StatRequest {
|
||||
return statType;
|
||||
}
|
||||
|
||||
public Statistic getStatEnum() {
|
||||
return statEnum;
|
||||
}
|
||||
|
||||
public String getSubStatEntry() {
|
||||
return subStatEntry;
|
||||
}
|
||||
|
||||
public EntityType getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
public Material getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
public Material getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
@ -81,8 +146,7 @@ public class StatRequest {
|
||||
return playerFlag;
|
||||
}
|
||||
|
||||
public boolean topFlag() {
|
||||
return topFlag;
|
||||
public @Nullable Query getSelection() {
|
||||
return selection;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,44 +1,46 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.ReloadThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Query;
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.TestFileHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MessageFactory;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class StatThread extends Thread {
|
||||
|
||||
private final int threshold;
|
||||
private final StatRequest request;
|
||||
private final ReloadThread reloadThread;
|
||||
|
||||
private final BukkitAudiences adventure;
|
||||
private final ConfigHandler config;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static ConfigHandler config;
|
||||
private static TestFileHandler testFile;
|
||||
private final MessageFactory messageFactory;
|
||||
private final Main plugin;
|
||||
|
||||
//constructor (called on thread creation)
|
||||
public StatThread(StatRequest s, @Nullable ReloadThread r, BukkitAudiences b, ConfigHandler c, OfflinePlayerHandler of, MessageFactory o, Main p) {
|
||||
public StatThread(int threshold, StatRequest s, @Nullable ReloadThread r, BukkitAudiences b, ConfigHandler c, TestFileHandler t, MessageFactory o, Main p) {
|
||||
this.threshold = threshold;
|
||||
request = s;
|
||||
reloadThread = r;
|
||||
|
||||
adventure = b;
|
||||
config = c;
|
||||
offlinePlayerHandler = of;
|
||||
testFile = t;
|
||||
messageFactory = o;
|
||||
plugin = p;
|
||||
plugin.getLogger().info("StatThread created!");
|
||||
@ -47,8 +49,6 @@ public class StatThread extends Thread {
|
||||
//what the thread will do once started
|
||||
@Override
|
||||
public void run() throws IllegalStateException, NullPointerException {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
if (messageFactory == null || plugin == null) {
|
||||
throw new IllegalStateException("Not all classes off the plugin are running!");
|
||||
}
|
||||
@ -70,120 +70,112 @@ public class StatThread extends Thread {
|
||||
String playerName = request.getPlayerName();
|
||||
String statName = request.getStatName();
|
||||
String subStatEntry = request.getSubStatEntry();
|
||||
boolean topFlag = request.topFlag();
|
||||
Query selection = request.getSelection();
|
||||
|
||||
if (playerName != null) {
|
||||
try {
|
||||
adventure.sender(sender).sendMessage(
|
||||
messageFactory.formatPlayerStat(
|
||||
playerName, statName, subStatEntry, getStatistic(
|
||||
statName, subStatEntry, playerName)));
|
||||
plugin.logTimeTaken("StatThread", "calculating individual stat", time);
|
||||
|
||||
} catch (Exception e) {
|
||||
sender.sendMessage(messageFactory.formatExceptions(e.toString()));
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else if (topFlag) {
|
||||
if (ThreadManager.getLastRecordedCalcTime() > 30000) {
|
||||
if (selection == Query.TOP || selection == Query.SERVER) {
|
||||
if (ThreadManager.getLastRecordedCalcTime() > 20000) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.waitAMoment(true));
|
||||
}
|
||||
else if (ThreadManager.getLastRecordedCalcTime() > 2000) {
|
||||
else if (ThreadManager.getLastRecordedCalcTime() > 1500) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.waitAMoment(false));
|
||||
}
|
||||
|
||||
try {
|
||||
adventure.sender(sender).sendMessage(messageFactory.formatTopStats(
|
||||
getTopStatistics(statName, subStatEntry), statName, subStatEntry));
|
||||
|
||||
plugin.logTimeTaken("StatThread", "calculating top stat", time);
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
if (selection == Query.TOP) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.formatTopStats(
|
||||
getTopStats(), statName, subStatEntry));
|
||||
}
|
||||
else {
|
||||
adventure.sender(sender).sendMessage(messageFactory.formatServerStat(
|
||||
statName, subStatEntry, getServerTotal()));
|
||||
}
|
||||
|
||||
} catch (ConcurrentModificationException e) {
|
||||
testFile.logRunCount(true);
|
||||
adventure.sender(sender).sendMessage(messageFactory.unknownError());
|
||||
} catch (Exception e) {
|
||||
sender.sendMessage(messageFactory.formatExceptions(e.toString()));
|
||||
e.printStackTrace();
|
||||
adventure.sender(sender).sendMessage(messageFactory.formatExceptions(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
else if (selection == Query.PLAYER) {
|
||||
try {
|
||||
long time = System.currentTimeMillis();
|
||||
adventure.sender(sender).sendMessage(
|
||||
messageFactory.formatPlayerStat(
|
||||
playerName, statName, subStatEntry, getIndividualStat()));
|
||||
plugin.logTimeTaken("StatThread", "calculating individual stat", time);
|
||||
|
||||
} catch (UnsupportedOperationException | NullPointerException e) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.formatExceptions(e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//returns the integer associated with a certain statistic for a player
|
||||
private int getStatistic(String statName, String subStatEntryName, String playerName) throws IllegalArgumentException, NullPointerException {
|
||||
try {
|
||||
Statistic stat = EnumHandler.getStatEnum(statName);
|
||||
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(playerName);
|
||||
return getPlayerStat(player, stat, subStatEntryName);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
}
|
||||
private LinkedHashMap<String, Integer> getTopStats() throws ConcurrentModificationException, NullPointerException {
|
||||
return getAllStats().entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||
.limit(config.getTopListMaxSize()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, Integer> getTopStatistics(String statName, String subStatEntry) {
|
||||
private int getServerTotal() {
|
||||
List<Integer> numbers = getAllStats().values().stream().toList();
|
||||
return numbers.parallelStream().mapToInt(Integer::intValue).sum();
|
||||
}
|
||||
|
||||
//invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players in the list)
|
||||
private @NotNull ConcurrentHashMap<String, Integer> getAllStats() throws ConcurrentModificationException, NullPointerException {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
ConcurrentHashMap<String, Integer> playerStats = new ConcurrentHashMap<>((int) (OfflinePlayerHandler.getOfflinePlayerCount() * 1.05));
|
||||
ImmutableList<String> playerNames = ImmutableList.copyOf(OfflinePlayerHandler.getOfflinePlayerNames());
|
||||
TopStatAction task = new TopStatAction(threshold, playerNames,
|
||||
request, playerStats);
|
||||
|
||||
ForkJoinPool commonPool = ForkJoinPool.commonPool();
|
||||
try {
|
||||
Statistic stat = EnumHandler.getStatEnum(statName);
|
||||
HashMap<String, Integer> playerStats = new HashMap<>((int) (getOfflinePlayerCount() * 1.05));
|
||||
offlinePlayerHandler.getOfflinePlayerNames().forEach(playerName -> {
|
||||
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(playerName);
|
||||
try {
|
||||
int statistic = getPlayerStat(player, stat, subStatEntry);
|
||||
if (statistic > 0) {
|
||||
playerStats.put(playerName, statistic);
|
||||
commonPool.invoke(task);
|
||||
} catch (ConcurrentModificationException e) {
|
||||
plugin.getLogger().warning("The request could not be executed due to a ConcurrentModificationException. " +
|
||||
"This likely happened because Bukkit hasn't fully initialized all players yet. Try again and it should be fine!");
|
||||
throw new ConcurrentModificationException(e.toString());
|
||||
}
|
||||
|
||||
testFile.saveTimeTaken(System.currentTimeMillis() - time, 3);
|
||||
testFile.logRunCount(false);
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
plugin.logTimeTaken("StatThread", "calculating all stats", time);
|
||||
|
||||
return playerStats;
|
||||
}
|
||||
|
||||
//gets the actual statistic data for an individual player
|
||||
private int getIndividualStat() throws UnsupportedOperationException, NullPointerException {
|
||||
OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(request.getPlayerName());
|
||||
if (player != null) {
|
||||
switch (request.getStatType()) {
|
||||
case UNTYPED -> {
|
||||
return player.getStatistic(request.getStatEnum());
|
||||
}
|
||||
case ENTITY -> {
|
||||
return player.getStatistic(request.getStatEnum(), request.getEntity());
|
||||
}
|
||||
case BLOCK -> {
|
||||
return player.getStatistic(request.getStatEnum(), request.getBlock());
|
||||
}
|
||||
case ITEM -> {
|
||||
return player.getStatistic(request.getStatEnum(), request.getItem());
|
||||
}
|
||||
default -> {
|
||||
if (request.getStatType() != null) {
|
||||
throw new UnsupportedOperationException("PlayerStats is not familiar with this statistic type - please check if you are using the latest version of the plugin!");
|
||||
}
|
||||
else {
|
||||
throw new NullPointerException("Trying to calculate a statistic of which the type is null - is this a valid statistic?");
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
});
|
||||
return playerStats.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||
.limit(config.getTopListMaxSize()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
//gets the actual statistic data for a given player
|
||||
private int getPlayerStat(@NotNull OfflinePlayer player, @NotNull Statistic stat, String subStatEntryName) throws IllegalArgumentException {
|
||||
switch (stat.getType()) {
|
||||
case UNTYPED -> {
|
||||
return player.getStatistic(stat);
|
||||
}
|
||||
case BLOCK -> {
|
||||
try {
|
||||
return player.getStatistic(stat, EnumHandler.getBlock(subStatEntryName));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
}
|
||||
}
|
||||
case ENTITY -> {
|
||||
try {
|
||||
return player.getStatistic(stat, EnumHandler.getEntityType(subStatEntryName));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
}
|
||||
}
|
||||
case ITEM -> {
|
||||
try {
|
||||
return player.getStatistic(stat, EnumHandler.getItem(subStatEntryName));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
}
|
||||
}
|
||||
default ->
|
||||
throw new IllegalArgumentException("This statistic does not seem to be of type:untyped/block/entity/item, I think we should panic");
|
||||
}
|
||||
}
|
||||
|
||||
//returns the amount of offline players, attempts to update the list if none are found, and otherwise throws an error
|
||||
private int getOfflinePlayerCount() {
|
||||
try {
|
||||
return offlinePlayerHandler.getOfflinePlayerCount();
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
throw new RuntimeException("No offline players were found to calculate statistics for!");
|
||||
}
|
||||
throw new NullPointerException("The player you are trying to request either does not exist, or is not on the list for statistic lookups!");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RecursiveAction;
|
||||
|
||||
|
||||
public class TopStatAction extends RecursiveAction {
|
||||
|
||||
private final int threshold;
|
||||
|
||||
private final ImmutableList<String> playerNames;
|
||||
private final StatRequest request;
|
||||
private final ConcurrentHashMap<String, Integer> playerStats;
|
||||
|
||||
/**
|
||||
* Gets the statistic numbers for all players whose name is on the list, puts them in a ConcurrentHashMap
|
||||
* using the default ForkJoinPool, and returns the ConcurrentHashMap when everything is done
|
||||
* @param playerNames List of playerNames of players that should be included in the stat calculations
|
||||
* @param statRequest a validated statRequest
|
||||
* @param playerStats the ConcurrentHashMap to put the results on
|
||||
*/
|
||||
|
||||
public TopStatAction(int threshold, ImmutableList<String> playerNames, StatRequest statRequest, ConcurrentHashMap<String, Integer> playerStats) {
|
||||
this.threshold = threshold;
|
||||
this.playerNames = playerNames;
|
||||
|
||||
this.request = statRequest;
|
||||
this.playerStats = playerStats;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void compute() {
|
||||
if (playerNames.size() < threshold) {
|
||||
getStatsDirectly();
|
||||
}
|
||||
else {
|
||||
final TopStatAction subTask1 = new TopStatAction(threshold, playerNames.subList(0, playerNames.size()/2), request, playerStats);
|
||||
final TopStatAction subTask2 = new TopStatAction(threshold, playerNames.subList(playerNames.size()/2, playerNames.size()), request, playerStats);
|
||||
|
||||
//queue and compute all subtasks in the right order
|
||||
invokeAll(subTask1, subTask2);
|
||||
}
|
||||
}
|
||||
|
||||
private void getStatsDirectly() {
|
||||
try {
|
||||
Iterator<String> iterator = playerNames.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
do {
|
||||
String playerName = iterator.next();
|
||||
OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(playerName);
|
||||
if (player != null) {
|
||||
int statistic = 0;
|
||||
switch (request.getStatType()) {
|
||||
case UNTYPED -> statistic = player.getStatistic(request.getStatEnum());
|
||||
case ENTITY -> statistic = player.getStatistic(request.getStatEnum(), request.getEntity());
|
||||
case BLOCK -> statistic = player.getStatistic(request.getStatEnum(), request.getBlock());
|
||||
case ITEM -> statistic = player.getStatistic(request.getStatEnum(), request.getItem());
|
||||
}
|
||||
if (statistic > 0) {
|
||||
playerStats.put(playerName, statistic);
|
||||
}
|
||||
}
|
||||
} while (iterator.hasNext());
|
||||
}
|
||||
} catch (NoSuchElementException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Query;
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
@ -9,9 +9,10 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.util.Index;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.map.MinecraftFont;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ -20,57 +21,67 @@ import static net.kyori.adventure.text.Component.*;
|
||||
public class MessageFactory {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private final Main plugin;
|
||||
|
||||
private static final TextColor msgColor = TextColor.fromHexString("#55aaff");
|
||||
private static final String pluginPrefix = ChatColor.GRAY + "[" + ChatColor.GOLD + "PlayerStats" + ChatColor.GRAY + "] " + ChatColor.RESET;
|
||||
private static final TextColor hoverBaseColor = TextColor.fromHexString("#55C6FF");
|
||||
private static final TextColor hoverAccentColor1 = TextColor.fromHexString("#FFB80E");
|
||||
private static final TextColor hoverAccentColor2 = TextColor.fromHexString("#FFD52B");
|
||||
|
||||
public MessageFactory(ConfigHandler c, Main p) {
|
||||
plugin = p;
|
||||
|
||||
public MessageFactory(ConfigHandler c) {
|
||||
config = c;
|
||||
}
|
||||
|
||||
public static String getPluginPrefix() {
|
||||
return pluginPrefix;
|
||||
private static TextComponent getPluginPrefix() {
|
||||
return text("[")
|
||||
.append(text("PlayerStats").color(NamedTextColor.GOLD))
|
||||
.append(text("]")
|
||||
.append(space()))
|
||||
.color(NamedTextColor.GRAY);
|
||||
}
|
||||
|
||||
public TextComponent reloadedConfig() {
|
||||
return getPluginPrefix().append(text("Config reloaded!").color(NamedTextColor.GREEN));
|
||||
}
|
||||
|
||||
public TextComponent stillReloading() {
|
||||
return text(getPluginPrefix()).append(text("The plugin is still (re)loading, your request will be processed when it is done!").color(msgColor));
|
||||
return getPluginPrefix().append(text("The plugin is still (re)loading, your request will be processed when it is done!").color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent partiallyReloaded() {
|
||||
return getPluginPrefix().append(
|
||||
text("The reload process was interrupted. If you notice unexpected behavior, please reload PlayerStats again to fix it!").color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent waitAMoment(boolean longWait) {
|
||||
return longWait ? text(getPluginPrefix()).append(text("Calculating statistics, this may take a minute...").color(msgColor))
|
||||
: text(getPluginPrefix()).append(text("Calculating statistics, this may take a few moments...").color(msgColor));
|
||||
return longWait ? getPluginPrefix().append(text("Calculating statistics, this may take a minute...").color(msgColor))
|
||||
: getPluginPrefix().append(text("Calculating statistics, this may take a few moments...").color(msgColor));
|
||||
}
|
||||
|
||||
public String formatExceptions(String exception) {
|
||||
return getPluginPrefix() + exception;
|
||||
public TextComponent formatExceptions(String exception) {
|
||||
return getPluginPrefix().append(text(exception).color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent missingStatName() {
|
||||
return text(getPluginPrefix()).append(text("Please provide a valid statistic name!").color(msgColor));
|
||||
return getPluginPrefix().append(text("Please provide a valid statistic name!").color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent missingSubStatName(Statistic.Type statType) {
|
||||
String subStat = getSubStatTypeName(statType) == null ? "sub-statistic" : getSubStatTypeName(statType);
|
||||
return text(getPluginPrefix())
|
||||
return getPluginPrefix()
|
||||
.append(text("Please add a valid ")
|
||||
.append(text(subStat))
|
||||
.append(text(" to look up this statistic!")))
|
||||
.color(msgColor);
|
||||
}
|
||||
|
||||
public TextComponent missingTarget() {
|
||||
return text(getPluginPrefix()).append(text("Please add \"me\", \"player\" or \"top\"").color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent missingPlayerName() {
|
||||
return text(getPluginPrefix()).append(text("Please specify a valid player-name!").color(msgColor));
|
||||
return getPluginPrefix().append(text("Please specify a valid player-name!").color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent wrongSubStatType(Statistic.Type statType, String subStatEntry) {
|
||||
String subStat = getSubStatTypeName(statType) == null ? "sub-statistic for this statistic" : getSubStatTypeName(statType);
|
||||
return text(getPluginPrefix())
|
||||
return getPluginPrefix()
|
||||
.append(text("\"")
|
||||
.append(text(subStatEntry))
|
||||
.append(text("\""))
|
||||
@ -80,7 +91,9 @@ public class MessageFactory {
|
||||
}
|
||||
|
||||
public TextComponent unknownError() {
|
||||
return text(getPluginPrefix()).append(text("Something went wrong with your input. Check /statistic for a usage explanation").color(msgColor));
|
||||
return getPluginPrefix()
|
||||
.append(text("Something went wrong with your request, please try again or see /statistic for a usage explanation!")
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent helpMsg() {
|
||||
@ -88,12 +101,10 @@ public class MessageFactory {
|
||||
TextComponent underscores = text("____________").color(TextColor.fromHexString("#6E3485"));
|
||||
TextComponent arrow = text("→ ").color(NamedTextColor.GOLD);
|
||||
TextColor arguments = NamedTextColor.YELLOW;
|
||||
TextColor hoverBaseColor = TextColor.fromHexString("#55C6FF");
|
||||
TextColor hoverAccentColor1 = TextColor.fromHexString("#FFB80E");
|
||||
TextColor hoverAccentColor2 = TextColor.fromHexString("#FFD52B");
|
||||
|
||||
|
||||
return Component.newline()
|
||||
.append(underscores).append(spaces).append(text(MessageFactory.getPluginPrefix())).append(spaces).append(underscores)
|
||||
.append(underscores).append(spaces).append(getPluginPrefix()).append(spaces).append(underscores)
|
||||
.append(newline())
|
||||
.append(text("Hover over the arguments for more information!").color(NamedTextColor.GRAY).decorate(TextDecoration.ITALIC))
|
||||
.append(newline())
|
||||
@ -127,6 +138,10 @@ public class MessageFactory {
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("Choose any player that has played on your server").color(hoverBaseColor))))
|
||||
.append(text(" | ").color(arguments))
|
||||
.append(text("server").color(arguments)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("See the combined total for everyone on your server").color(hoverBaseColor))))
|
||||
.append(text(" | ").color(arguments))
|
||||
.append(text("top").color(arguments)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("See the top ").color(hoverBaseColor)
|
||||
@ -142,28 +157,23 @@ public class MessageFactory {
|
||||
|
||||
public TextComponent formatPlayerStat(String playerName, String statName, String subStatEntryName, int stat) {
|
||||
TextComponent.Builder singleStat = Component.text();
|
||||
String subStat = subStatEntryName != null ?
|
||||
" (" + subStatEntryName.toLowerCase().replace("_", " ") + ")" : "";
|
||||
|
||||
singleStat.append(playerNameComponent(playerName + ": ", false))
|
||||
.append(statNumberComponent(stat, false)).append(space())
|
||||
.append(statNameComponent(statName.toLowerCase().replace("_", " "), false))
|
||||
.append(subStatNameComponent(subStat, false));
|
||||
singleStat.append(playerNameComponent(Query.PLAYER, playerName + ": "))
|
||||
.append(statNumberComponent(Query.PLAYER, stat)).append(space())
|
||||
.append(statNameComponent(Query.PLAYER, statName))
|
||||
.append(subStatNameComponent(Query.PLAYER, subStatEntryName));
|
||||
|
||||
return singleStat.build();
|
||||
}
|
||||
|
||||
public TextComponent formatTopStats(LinkedHashMap<String, Integer> topStats, String statName, String subStatEntryName) {
|
||||
long time = System.currentTimeMillis();
|
||||
TextComponent.Builder topList = Component.text();
|
||||
String subStat = subStatEntryName != null ?
|
||||
"(" + subStatEntryName.toLowerCase().replace("_", " ") + ")" : "";
|
||||
|
||||
topList.append(newline()).append(text(getPluginPrefix()))
|
||||
.append(statNameComponent("Top", true)).append(space())
|
||||
.append(listNumberComponent(topStats.size() + "")).append(space())
|
||||
.append(statNameComponent(statName.toLowerCase().replace("_", " "), true)).append(space())
|
||||
.append(subStatNameComponent(subStat, true));
|
||||
topList.append(newline()).append(getPluginPrefix())
|
||||
.append(titleComponent(Query.TOP, "Top")).append(space())
|
||||
.append(titleNumberComponent(topStats.size())).append(space())
|
||||
.append(statNameComponent(Query.TOP, statName)).append(space())
|
||||
.append(subStatNameComponent(Query.TOP, subStatEntryName));
|
||||
|
||||
boolean useDots = config.useDots();
|
||||
Set<String> playerNames = topStats.keySet();
|
||||
@ -174,8 +184,8 @@ public class MessageFactory {
|
||||
count = count+1;
|
||||
|
||||
topList.append(newline())
|
||||
.append(listNumberComponent(count + ". "))
|
||||
.append(playerNameComponent(playerName, true));
|
||||
.append(rankingNumberComponent(count + ". "))
|
||||
.append(playerNameComponent(Query.TOP, playerName));
|
||||
|
||||
if (useDots) {
|
||||
topList.append(space());
|
||||
@ -189,14 +199,28 @@ public class MessageFactory {
|
||||
}
|
||||
}
|
||||
else {
|
||||
topList.append(playerNameComponent(":", true));
|
||||
topList.append(playerNameComponent(Query.TOP, ":"));
|
||||
}
|
||||
topList.append(space()).append(statNumberComponent(topStats.get(playerName), true));
|
||||
topList.append(space()).append(statNumberComponent(Query.TOP, topStats.get(playerName)));
|
||||
}
|
||||
plugin.logTimeTaken("MessageFactory", "applying colors", time);
|
||||
return topList.build();
|
||||
}
|
||||
|
||||
public TextComponent formatServerStat(String statName, String subStatEntry, int stat) {
|
||||
TextComponent.Builder serverStat = Component.text();
|
||||
serverStat.append(titleComponent(Query.SERVER, "Total for"))
|
||||
.append(space())
|
||||
.append(serverNameComponent())
|
||||
.append(space())
|
||||
.append(statNumberComponent(Query.SERVER, stat))
|
||||
.append(space())
|
||||
.append(statNameComponent(Query.SERVER, statName))
|
||||
.append(space())
|
||||
.append(subStatNameComponent(Query.SERVER, subStatEntry));
|
||||
|
||||
return serverStat.build();
|
||||
}
|
||||
|
||||
//returns the type of the substatistic in String-format, or null if this statistic is not of type block, item or entity
|
||||
private String getSubStatTypeName(Statistic.Type statType) {
|
||||
String subStat;
|
||||
@ -215,40 +239,70 @@ public class MessageFactory {
|
||||
return subStat;
|
||||
}
|
||||
|
||||
//try to get the hex color or NamedTextColor from config String, substitute a default ChatColor if both fail, and try to apply style where necessary
|
||||
private TextComponent playerNameComponent(String playerName, boolean topStat) {
|
||||
NamedTextColor defaultColor = topStat ? NamedTextColor.GREEN : NamedTextColor.GOLD;
|
||||
TextComponent.Builder player = applyColor(
|
||||
config.getPlayerNameFormatting(topStat, false), playerName, defaultColor);
|
||||
return applyStyle(config.getPlayerNameFormatting(topStat, true), player).build();
|
||||
private TextComponent playerNameComponent(Query selection, String playerName) {
|
||||
return getComponent(playerName,
|
||||
getColorFromString(config.getPlayerNameFormatting(selection, false)),
|
||||
getStyleFromString(config.getPlayerNameFormatting(selection, true)));
|
||||
}
|
||||
|
||||
private TextComponent statNameComponent(String statName, boolean topStat) {
|
||||
TextComponent.Builder stat = applyColor(
|
||||
config.getStatNameFormatting(topStat, false), statName, NamedTextColor.YELLOW);
|
||||
return applyStyle(config.getStatNameFormatting(topStat, true), stat).build();
|
||||
private TextComponent statNameComponent(Query selection, String statName) {
|
||||
return getComponent(statName.toLowerCase().replace("_", " "),
|
||||
getColorFromString(config.getStatNameFormatting(selection, false)),
|
||||
getStyleFromString(config.getStatNameFormatting(selection, true)));
|
||||
}
|
||||
|
||||
private TextComponent subStatNameComponent(String subStatName, boolean topStat) {
|
||||
TextComponent.Builder subStat = applyColor(
|
||||
config.getSubStatNameFormatting(topStat, false), subStatName, NamedTextColor.YELLOW);
|
||||
return applyStyle(config.getSubStatNameFormatting(topStat, true), subStat).build();
|
||||
private TextComponent subStatNameComponent(Query selection, String subStatName) {
|
||||
if (subStatName == null) {
|
||||
return empty();
|
||||
}
|
||||
else {
|
||||
return getComponent("(" + subStatName.toLowerCase().replace("_", " ") + ")",
|
||||
getColorFromString(config.getSubStatNameFormatting(selection, false)),
|
||||
getStyleFromString(config.getSubStatNameFormatting(selection, true)))
|
||||
.append(space());
|
||||
}
|
||||
}
|
||||
|
||||
private TextComponent statNumberComponent(int statNumber, boolean topStat) {
|
||||
TextComponent.Builder number = applyColor(
|
||||
config.getStatNumberFormatting(topStat, false), statNumber + "", NamedTextColor.LIGHT_PURPLE);
|
||||
return applyStyle(config.getStatNumberFormatting(topStat, true), number).build();
|
||||
private TextComponent statNumberComponent(Query selection, int number) {
|
||||
return getComponent(number + "",
|
||||
getColorFromString(config.getStatNumberFormatting(selection, false)),
|
||||
getStyleFromString(config.getStatNumberFormatting(selection, true)));
|
||||
}
|
||||
|
||||
private TextComponent listNumberComponent(String listNumber) {
|
||||
TextComponent.Builder list = applyColor(config.getListNumberFormatting(false), listNumber + "", NamedTextColor.GOLD);
|
||||
return applyStyle(config.getListNumberFormatting(true), list).build();
|
||||
private TextComponent titleComponent(Query selection, String content) {
|
||||
return getComponent(content,
|
||||
getColorFromString(config.getTitleFormatting(selection, false)),
|
||||
getStyleFromString(config.getTitleFormatting(selection, true)));
|
||||
}
|
||||
|
||||
private TextComponent titleNumberComponent(int number) {
|
||||
return getComponent(number + "",
|
||||
getColorFromString(config.getTitleNumberFormatting(false)),
|
||||
getStyleFromString(config.getTitleNumberFormatting(true)));
|
||||
}
|
||||
|
||||
private TextComponent serverNameComponent() {
|
||||
TextComponent colon = text(":").color(getColorFromString(config.getServerNameFormatting(false)));
|
||||
return getComponent(config.getServerName(),
|
||||
getColorFromString(config.getServerNameFormatting(false)),
|
||||
getStyleFromString(config.getServerNameFormatting(true)))
|
||||
.append(colon);
|
||||
}
|
||||
|
||||
private TextComponent rankingNumberComponent(String number) {
|
||||
return getComponent(number,
|
||||
getColorFromString(config.getRankNumberFormatting(false)),
|
||||
getStyleFromString(config.getRankNumberFormatting(true)));
|
||||
}
|
||||
|
||||
private TextComponent dotsComponent(String dots) {
|
||||
return text(dots).color(getColorFromString(config.getDotsColor())).colorIfAbsent(NamedTextColor.DARK_GRAY);
|
||||
//return applyColor(config.getDotsColor(), dots, NamedTextColor.DARK_GRAY).build();
|
||||
return getComponent(dots,
|
||||
getColorFromString(config.getDotsFormatting(false)),
|
||||
getStyleFromString(config.getDotsFormatting(true)));
|
||||
}
|
||||
|
||||
private TextComponent getComponent(String content, TextColor color, @Nullable TextDecoration style) {
|
||||
return style == null ? text(content).color(color) : text(content).color(color).decoration(style, TextDecoration.State.TRUE);
|
||||
}
|
||||
|
||||
private TextColor getColorFromString(String configString) {
|
||||
@ -262,57 +316,27 @@ public class MessageFactory {
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException exception) {
|
||||
plugin.getLogger().warning(exception.toString());
|
||||
Bukkit.getLogger().warning(exception.toString());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TextComponent.Builder applyColor(String configString, String content, NamedTextColor defaultColor) {
|
||||
TextComponent.Builder component = Component.text();
|
||||
|
||||
if (configString != null) {
|
||||
try {
|
||||
if (configString.contains("#")) {
|
||||
return component.content(content).color(TextColor.fromHexString(configString));
|
||||
}
|
||||
else {
|
||||
return component.content(content).color(getTextColorByName(configString));
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException exception) {
|
||||
plugin.getLogger().warning(exception.toString());
|
||||
}
|
||||
}
|
||||
return component.content(content).colorIfAbsent(defaultColor);
|
||||
}
|
||||
|
||||
private TextColor getTextColorByName(String textColor) {
|
||||
Index<String, NamedTextColor> names = NamedTextColor.NAMES;
|
||||
return names.value(textColor);
|
||||
}
|
||||
|
||||
private TextComponent.Builder applyStyle(String configString, TextComponent.Builder component) {
|
||||
if (configString != null) {
|
||||
if (configString.equalsIgnoreCase("none")) {
|
||||
return component;
|
||||
}
|
||||
else if (configString.equalsIgnoreCase("bold")) {
|
||||
return component.decoration(TextDecoration.BOLD, TextDecoration.State.TRUE);
|
||||
}
|
||||
else if (configString.equalsIgnoreCase("italic")) {
|
||||
return component.decoration(TextDecoration.ITALIC, TextDecoration.State.TRUE);
|
||||
}
|
||||
else if (configString.equalsIgnoreCase("magic")) {
|
||||
return component.decoration(TextDecoration.OBFUSCATED, TextDecoration.State.TRUE);
|
||||
}
|
||||
else if (configString.equalsIgnoreCase("strikethrough")) {
|
||||
return component.decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.TRUE);
|
||||
}
|
||||
else if (configString.equalsIgnoreCase("underlined")) {
|
||||
return component.decoration(TextDecoration.UNDERLINED, TextDecoration.State.TRUE);
|
||||
}
|
||||
private @Nullable TextDecoration getStyleFromString(String configString) {
|
||||
if (configString.equalsIgnoreCase("none")) {
|
||||
return null;
|
||||
}
|
||||
else if (configString.equalsIgnoreCase("magic")) {
|
||||
return TextDecoration.OBFUSCATED;
|
||||
}
|
||||
else {
|
||||
Index<String, TextDecoration> styles = TextDecoration.NAMES;
|
||||
return styles.value(configString);
|
||||
}
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +1,53 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class OfflinePlayerHandler {
|
||||
|
||||
private final ConfigHandler config;
|
||||
private HashMap<String, UUID> offlinePlayerUUIDs;
|
||||
private static ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||
private static ArrayList<String> playerNames;
|
||||
|
||||
public OfflinePlayerHandler(ConfigHandler c) {
|
||||
config = c;
|
||||
private OfflinePlayerHandler() {
|
||||
}
|
||||
|
||||
public boolean isOfflinePlayerName(String playerName) {
|
||||
public static boolean isOfflinePlayerName(String playerName) {
|
||||
return offlinePlayerUUIDs.containsKey(playerName);
|
||||
}
|
||||
|
||||
public int getOfflinePlayerCount() throws NullPointerException {
|
||||
public static int getOfflinePlayerCount() throws NullPointerException {
|
||||
if (offlinePlayerUUIDs != null && offlinePlayerUUIDs.size() > 0) return offlinePlayerUUIDs.size();
|
||||
else throw new NullPointerException("No players found!");
|
||||
}
|
||||
|
||||
public Set<String> getOfflinePlayerNames() {
|
||||
return offlinePlayerUUIDs.keySet();
|
||||
public static ArrayList<String> getOfflinePlayerNames() {
|
||||
return playerNames;
|
||||
}
|
||||
|
||||
public void updateOfflinePlayerList() {
|
||||
updateOfflinePlayerList(config.whitelistOnly(), config.excludeBanned(), config.lastPlayedLimit());
|
||||
/**
|
||||
* Get a new HashMap that stores the players to include in stat calculations.
|
||||
* This HashMap is stored as a private variable in OfflinePlayerHandler (keys: playerNames, values: UUIDs).
|
||||
*/
|
||||
public static void updateOfflinePlayerList(ConcurrentHashMap<String, UUID> playerList) {
|
||||
offlinePlayerUUIDs = playerList;
|
||||
playerNames = Collections.list(offlinePlayerUUIDs.keys());
|
||||
}
|
||||
|
||||
//stores a private HashMap of all relevant offline players with keys:playerName and values:UUID
|
||||
private void updateOfflinePlayerList(boolean whitelistOnly, boolean excludeBanned, int lastPlayedLimit) {
|
||||
if (offlinePlayerUUIDs == null) offlinePlayerUUIDs = new HashMap<>();
|
||||
else if (!offlinePlayerUUIDs.isEmpty()) {
|
||||
offlinePlayerUUIDs.clear();
|
||||
/**
|
||||
* Uses the playerName to get the player's UUID from a private HashMap, and uses the UUID to get the corresponding OfflinePlayer Object.
|
||||
* @param playerName name of the target player
|
||||
* @return OfflinePlayer (if this player is on the list, otherwise null)
|
||||
*/
|
||||
public static @Nullable OfflinePlayer getOfflinePlayer(String playerName) {
|
||||
if (offlinePlayerUUIDs.get(playerName) != null) {
|
||||
return Bukkit.getOfflinePlayer(offlinePlayerUUIDs.get(playerName));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
||||
Arrays.stream(Bukkit.getOfflinePlayers()).filter(offlinePlayer ->
|
||||
offlinePlayer.getName() != null &&
|
||||
(!excludeBanned || !offlinePlayer.isBanned()) &&
|
||||
(!whitelistOnly || offlinePlayer.isWhitelisted()) &&
|
||||
(lastPlayedLimit == 0 || UnixTimeHandler.hasPlayedSince(lastPlayedLimit, offlinePlayer.getLastPlayed())))
|
||||
.forEach(offlinePlayer -> offlinePlayerUUIDs.put((offlinePlayer.getName()), offlinePlayer.getUniqueId()));
|
||||
}
|
||||
|
||||
public OfflinePlayer getOfflinePlayer(String playerName) {
|
||||
return Bukkit.getOfflinePlayer(offlinePlayerUUIDs.get(playerName));
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
# ------------------------------
|
||||
# PlayerStats Configuration
|
||||
# ------------------------------
|
||||
# --------------------------------
|
||||
# PlayerStats Configuration
|
||||
# --------------------------------
|
||||
|
||||
# ------ General Options -------
|
||||
# ----------- General ------------
|
||||
# Filtering options to control which players should be included in statistic calculations
|
||||
include-whitelist-only: false
|
||||
exclude-banned-players: false
|
||||
@ -11,43 +11,77 @@ exclude-banned-players: false
|
||||
# Leave this on 0 to include all players
|
||||
number-of-days-since-last-joined: 0
|
||||
|
||||
# ------ Format Options --------
|
||||
|
||||
# ----------- Format -------------
|
||||
# The name you want displayed for a total-on-this-server statistic
|
||||
your-server-name: 'this server'
|
||||
|
||||
# The maximum number of results displayed in the top list
|
||||
top-list-max-size: 10
|
||||
|
||||
# If true, the top list will be aligned with lines of dots so that the stat numbers are all underneath each other
|
||||
use-dots: true
|
||||
|
||||
# ------ Color Options ---------
|
||||
|
||||
# -------- Color & Style ---------
|
||||
# The colors below can be chat color names or hex codes (format: '#xxxxxx' <-- including quotation marks!)
|
||||
# The style options include: bold, italic, underlined, strikethrough, and magic
|
||||
individual-statistics-color:
|
||||
player-names: gold
|
||||
# The style options include: bold, italic, underlined, strikethrough (and for some reason I decided to also include magic)
|
||||
|
||||
top-list:
|
||||
title: yellow
|
||||
title-style: none
|
||||
|
||||
title-number: gold
|
||||
title-number-style: none
|
||||
|
||||
stat-names: yellow
|
||||
stat-names-style: none
|
||||
|
||||
sub-stat-names: '#FFD52B'
|
||||
stat-numbers: '#ADE7FF'
|
||||
sub-stat-names-style: none
|
||||
|
||||
individual-statistics-style:
|
||||
player-names: none
|
||||
stat-names: none
|
||||
sub-stat-names: none
|
||||
stat-numbers: none
|
||||
rank-numbers: gold
|
||||
rank-numbers-style: none
|
||||
|
||||
top-list-color:
|
||||
player-names: green
|
||||
stat-names: yellow
|
||||
sub-stat-names: '#FFD52B'
|
||||
stat-numbers: '#55aaff'
|
||||
list-numbers: gold
|
||||
player-names-style: none
|
||||
|
||||
stat-numbers: '#55AAFF'
|
||||
stat-numbers-style: none
|
||||
|
||||
dots: dark_gray
|
||||
dots-style: none
|
||||
|
||||
top-list-style:
|
||||
player-names: none
|
||||
stat-names: none
|
||||
sub-stat-names: none
|
||||
stat-numbers: none
|
||||
list-numbers: none
|
||||
|
||||
|
||||
individual-statistics:
|
||||
player-names: gold
|
||||
player-names-style: none
|
||||
|
||||
stat-names: yellow
|
||||
stat-names-style: none
|
||||
|
||||
sub-stat-names: '#FFD52B'
|
||||
sub-stat-names-style: none
|
||||
|
||||
stat-numbers: '#ADE7FF'
|
||||
stat-numbers-style: none
|
||||
|
||||
|
||||
total-server:
|
||||
title: gold
|
||||
title-style: none
|
||||
|
||||
server-name: gold
|
||||
server-name-style: italic
|
||||
|
||||
stat-names: yellow
|
||||
stat-names-style: none
|
||||
|
||||
sub-stat-names: '#FFD52B'
|
||||
sub-stat-names-style: none
|
||||
|
||||
stat-numbers: '#ADE7FF'
|
||||
stat-numbers-style: none
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user