diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/ThreadManager.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/ThreadManager.java index f961790..68e0292 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/ThreadManager.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/ThreadManager.java @@ -35,7 +35,7 @@ public class ThreadManager { } public void startReloadThread(CommandSender sender, boolean firstTimeLoading) { - reloadThread = new ReloadThread(threshold, config, testFile, plugin, statThread, sender, firstTimeLoading); + reloadThread = new ReloadThread(threshold, adventure, config, testFile, messageFactory, plugin, statThread, sender, firstTimeLoading); reloadThread.start(); } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/commands/StatCommand.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/commands/StatCommand.java index 73b0cc8..f0ae2d5 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/commands/StatCommand.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/commands/StatCommand.java @@ -97,6 +97,9 @@ public class StatCommand implements CommandExecutor { else if (arg.equalsIgnoreCase("top")) { request.setTopFlag(true); } + else if (arg.equalsIgnoreCase("server")) { + request.setServerFlag(true); + } else if (arg.equalsIgnoreCase("me") && sender instanceof Player) { request.setPlayerName(sender.getName()); } @@ -113,9 +116,12 @@ public class StatCommand implements CommandExecutor { removeUnnecessarySubStat(request); if (request.getStatName() != null) { - if (!request.topFlag() && request.getPlayerName() == null) { + if (!(request.topFlag() || request.serverFlag()) && request.getPlayerName() == null) { assumeTopAsDefault(request); } + else if (request.topFlag() && request.serverFlag()) { + assumeServerFlag(request); + } return EnumHandler.isValidStatEntry(request.getStatType(), request.getSubStatEntry()); } return false; @@ -135,8 +141,13 @@ public class StatCommand implements CommandExecutor { } } - //if no playerName was provided, and there is no topFlag, substitute a top flag + //if no playerName was provided, and there is no topFlag or serverFlag, substitute a top flag private void assumeTopAsDefault(StatRequest request) { request.setTopFlag(true); } + + //if both a topFlag and serverFlag are present, keep the serverFlag and reset the topFlag + private void assumeServerFlag(StatRequest request) { + request.setTopFlag(false); + } } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/commands/TabCompleter.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/commands/TabCompleter.java index 63d1311..0f1d03c 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/commands/TabCompleter.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/commands/TabCompleter.java @@ -19,6 +19,7 @@ public class TabCompleter implements org.bukkit.command.TabCompleter { commandOptions = new ArrayList<>(); commandOptions.add("top"); commandOptions.add("player"); + commandOptions.add("server"); commandOptions.add("me"); } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/filehandlers/ConfigHandler.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/filehandlers/ConfigHandler.java index be049d1..665800e 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/filehandlers/ConfigHandler.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/filehandlers/ConfigHandler.java @@ -115,8 +115,16 @@ public class ConfigHandler { return getStringFromConfig(topStat, isStyle, "stat-numbers"); } - public String getListNumberFormatting(boolean isStyle) { - return getStringFromConfig(true, isStyle, "list-numbers"); + public String getListTitleFormatting(boolean isStyle) { + return getStringFromConfig(true, isStyle, "list-title"); + } + + public String getListTitleNumberFormatting(boolean isStyle) { + return getStringFromConfig(true, isStyle, "list-title-number"); + } + + public String getRankingNumberFormatting(boolean isStyle) { + return getStringFromConfig(true, isStyle, "ranking-numbers"); } public String getDotsColor() { diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadAction.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadAction.java index 02217ed..f4714e7 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadAction.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadAction.java @@ -58,30 +58,20 @@ public class ReloadAction extends RecursiveAction { final ReloadAction subTask2 = new ReloadAction(threshold, players, (start + split), end, whitelistOnly, excludeBanned, lastPlayedLimit, offlinePlayerUUIDs); - try { - //queue and compute all subtasks in the right order - invokeAll(subTask1, subTask2); - } - catch (ConcurrentModificationException e) { - e.printStackTrace(); - } + //queue and compute all subtasks in the right order + invokeAll(subTask1, subTask2); } } private void process() { - try { - 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()); - } + 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()); } - } catch (ConcurrentModificationException e) { - Bukkit.getLogger().warning("ReloadAction has thrown a ConcurrentModificationException because of " + e.getCause()); - e.printStackTrace(); } } } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadThread.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadThread.java index 78ecd45..cdfc42f 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadThread.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadThread.java @@ -7,33 +7,37 @@ 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.ChatColor; 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.CopyOnWriteArrayList; 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, ConfigHandler c, TestFileHandler t, Main p, @Nullable StatThread s, @Nullable CommandSender se, boolean firstTime) { + 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; @@ -59,13 +63,21 @@ public class ReloadThread extends Thread { } plugin.getLogger().info("Reloading!"); if (config.reloadConfig()) { - OfflinePlayerHandler.updateOfflinePlayerList(getPlayerMap(false)); + + 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) { - sender.sendMessage(MessageFactory.getPluginPrefix() + ChatColor.GREEN + "Config reloaded!"); + adventure.sender(sender).sendMessage(messageFactory.reloadedConfig()); } } } @@ -89,19 +101,22 @@ public class ReloadThread extends Thread { ReloadAction task = new ReloadAction(threshold, offlinePlayers, config.whitelistOnly(), config.excludeBanned(), config.lastPlayedLimit(), playerMap); ForkJoinPool commonPool = ForkJoinPool.commonPool(); - commonPool.invoke(task); - ConcurrentHashMap newPlayerMap = new ConcurrentHashMap<>(playerMap.size()); + try { + commonPool.invoke(task); + } catch (ConcurrentModificationException e) { + throw new ConcurrentModificationException(e.toString()); + } + return generateFakeExtraPlayers(playerMap, 10); + } - /* - for (int i = 0; i < 11; i++) { - for (String key : playerMap.keySet()) { - newPlayerMap.put(key + i, playerMap.get(key)); + private ConcurrentHashMap generateFakeExtraPlayers(ConcurrentHashMap realPlayers, int loops) { + ConcurrentHashMap 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)); } } - */ - - newPlayerMap.putAll(playerMap); return newPlayerMap; } } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatRequest.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatRequest.java index 03fcb0f..553c69f 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatRequest.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatRequest.java @@ -16,6 +16,7 @@ public class StatRequest { private String playerName; private boolean playerFlag; private boolean topFlag; + private boolean serverFlag; private Statistic statEnum; private Statistic.Type statType; @@ -28,6 +29,7 @@ public class StatRequest { sender = s; playerFlag = false; topFlag = false; + serverFlag = false; } //sets the statName, and automatically tries to set the correct statType and get the corresponding item/block/entity if there is a subStatEntry @@ -41,7 +43,7 @@ public class StatRequest { } } - private void setStatEnumAndType() { + private void setStatEnumAndType() throws IllegalArgumentException { try { statEnum = EnumHandler.getStatEnum(statName); statType = statEnum.getType(); @@ -105,6 +107,10 @@ public class StatRequest { this.topFlag = topFlag; } + public void setServerFlag(boolean serverFlag) { + this.serverFlag = serverFlag; + } + public CommandSender getCommandSender() { return sender; } @@ -150,4 +156,7 @@ public class StatRequest { return topFlag; } + public boolean serverFlag() { + return serverFlag; + } } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatThread.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatThread.java index 7ec0590..4435a73 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatThread.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatThread.java @@ -9,18 +9,12 @@ 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.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Comparator; -import java.util.ConcurrentModificationException; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; @@ -53,8 +47,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!"); } @@ -77,8 +69,9 @@ public class StatThread extends Thread { String statName = request.getStatName(); String subStatEntry = request.getSubStatEntry(); boolean topFlag = request.topFlag(); + boolean serverFlag = request.serverFlag(); - if (topFlag) { + if (topFlag || serverFlag) { if (ThreadManager.getLastRecordedCalcTime() > 30000) { adventure.sender(sender).sendMessage(messageFactory.waitAMoment(true)); } @@ -87,53 +80,53 @@ public class StatThread extends Thread { } try { - adventure.sender(sender).sendMessage(messageFactory.formatTopStats( - getTopStatistics(), statName, subStatEntry)); - - testFile.saveTimeTaken(System.currentTimeMillis() - time, 3); - testFile.logRunCount(false); - plugin.logTimeTaken("StatThread", "calculating top stat", time); - ThreadManager.recordCalcTime(System.currentTimeMillis() - time); + if (topFlag) { + 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(); } } else if (playerName != null) { try { + long time = System.currentTimeMillis(); adventure.sender(sender).sendMessage( messageFactory.formatPlayerStat( - playerName, statName, subStatEntry, getStatistic())); + playerName, statName, subStatEntry, getIndividualStat())); plugin.logTimeTaken("StatThread", "calculating individual stat", time); - } catch (Exception e) { - sender.sendMessage(messageFactory.formatExceptions(e.toString())); - e.printStackTrace(); + } catch (UnsupportedOperationException | NullPointerException e) { + sender.sendMessage(messageFactory.formatExceptions(e.getMessage())); } } } - //returns the integer associated with a certain statistic for a player - private int getStatistic() throws IllegalArgumentException, NullPointerException { - try { - return getPlayerStat(OfflinePlayerHandler.getOfflinePlayer(request.getPlayerName())); - } - catch (Exception e) { - Bukkit.getLogger().warning(e.toString()); - throw new IllegalArgumentException(e.toString()); - } + private LinkedHashMap 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 int getServerTotal() { + List 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 LinkedHashMap getTopStatistics() throws ConcurrentModificationException { - ConcurrentHashMap playerStats = new ConcurrentHashMap<>((int) (getOfflinePlayerCount() * 1.05)); - //ConcurrentLinkedDeque playerNames = new ConcurrentLinkedDeque<>(OfflinePlayerHandler.getOfflinePlayerNames()); - //String[] playerNames = OfflinePlayerHandler.getOfflinePlayerNames().toArray(new String[0]); + private ConcurrentHashMap getAllStats() throws ConcurrentModificationException, NullPointerException { + long time = System.currentTimeMillis(); + + ConcurrentHashMap playerStats = new ConcurrentHashMap<>((int) (OfflinePlayerHandler.getOfflinePlayerCount() * 1.05)); ImmutableList playerNames = ImmutableList.copyOf(OfflinePlayerHandler.getOfflinePlayerNames()); TopStatAction task = new TopStatAction(threshold, playerNames, request, playerStats); @@ -142,57 +135,44 @@ public class StatThread extends Thread { try { commonPool.invoke(task); } catch (ConcurrentModificationException e) { - e.printStackTrace(); - try { - if (!task.cancel(true)) { - plugin.getLogger().severe("Tried to cancel task, but failed. You might need to shut down the server and reboot"); - throw new ConcurrentModificationException(e.toString()); - } else { - plugin.getLogger().warning("Canceling task because of a ConcurrentModificationException..."); - } - } catch (ConcurrentModificationException ex) { - ex.printStackTrace(); - throw new ConcurrentModificationException(ex.toString()); - } + 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()); } - 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)); + 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 getPlayerStat(@NotNull OfflinePlayer player) throws IllegalArgumentException { - try { - 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 -> - throw new Exception("This statistic does not seem to be of type:untyped/block/entity/item, I strongly suggest we panic"); - } - } catch (Exception e) { - Bukkit.getLogger().warning(e.toString()); - throw new IllegalArgumentException(e.toString()); - } - } + private int getIndividualStat() throws UnsupportedOperationException, NullPointerException { + OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(request.getPlayerName()); - //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!"); + 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?"); + } + } } } } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/TopStatAction.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/TopStatAction.java index fe45df3..f3e7a6d 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/TopStatAction.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/TopStatAction.java @@ -2,7 +2,6 @@ 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.Bukkit; import org.bukkit.OfflinePlayer; import java.util.*; @@ -48,30 +47,26 @@ public class TopStatAction extends RecursiveAction { } } - private void getStatsDirectly() throws IllegalArgumentException, ConcurrentModificationException { + private void getStatsDirectly() { try { Iterator iterator = playerNames.iterator(); - while (iterator.hasNext()) { - String playerName = iterator.next(); - OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(playerName); - 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); - } + if (iterator.hasNext()) { + do { + String playerName = iterator.next(); + OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(playerName); + 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 e) { - e.printStackTrace(); - } catch (IllegalArgumentException ignored) { - } catch (ConcurrentModificationException e) { - Bukkit.getLogger().warning("A ConcurrentModificationException has occurred" + e.getCause()); - e.printStackTrace(); - throw new ConcurrentModificationException(e.toString()); + } catch (NoSuchElementException ignored) { } } } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MessageFactory.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MessageFactory.java index 0341dfe..e815959 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MessageFactory.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MessageFactory.java @@ -1,6 +1,5 @@ package com.gmail.artemis.the.gr8.playerstats.utils; -import com.gmail.artemis.the.gr8.playerstats.Main; import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; @@ -10,7 +9,6 @@ import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.util.Index; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.Statistic; import org.bukkit.map.MinecraftFont; @@ -23,23 +21,39 @@ public class MessageFactory { private static ConfigHandler config; 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) { 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) { @@ -47,12 +61,12 @@ public class MessageFactory { } 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!"))) @@ -60,16 +74,16 @@ public class MessageFactory { } public TextComponent missingTarget() { - return text(getPluginPrefix()).append(text("Please add \"me\", \"player\" or \"top\"").color(msgColor)); + return 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("\"")) @@ -79,7 +93,7 @@ public class MessageFactory { } public TextComponent unknownError() { - return text(getPluginPrefix()).append(text("Something went wrong with your request, please try again!").color(msgColor)); + return getPluginPrefix().append(text("Something went wrong with your request, please try again!").color(msgColor)); } public TextComponent helpMsg() { @@ -87,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()) @@ -126,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 the server").color(hoverBaseColor)))) + .append(text(" | ").color(arguments)) .append(text("top").color(arguments) .hoverEvent(HoverEvent.showText( text("See the top ").color(hoverBaseColor) @@ -141,27 +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)); + .append(statNameComponent(statName, false)) + .append(subStatNameComponent(subStatEntryName, false)); return singleStat.build(); } public TextComponent formatTopStats(LinkedHashMap topStats, String statName, String subStatEntryName) { 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("Top")).append(space()) + .append(titleNumberComponent(topStats.size())).append(space()) + .append(statNameComponent(statName, true)).append(space()) + .append(subStatNameComponent(subStatEntryName, true)); boolean useDots = config.useDots(); Set playerNames = topStats.keySet(); @@ -172,7 +184,7 @@ public class MessageFactory { count = count+1; topList.append(newline()) - .append(listNumberComponent(count + ". ")) + .append(rankingNumberComponent(count + ". ")) .append(playerNameComponent(playerName, true)); if (useDots) { @@ -194,6 +206,20 @@ public class MessageFactory { return topList.build(); } + public TextComponent formatServerStat(String statName, String subStatEntry, int stat) { + TextComponent.Builder serverStat = Component.text(); + serverStat.append(titleComponent("All")) + .append(space()) + .append(statNameComponent(statName, true)) + .append(space()) + .append(subStatNameComponent(subStatEntry, true)) + .append(titleComponent("on this server:")) + .append(space()) + .append(statNumberComponent(stat, true)); + + 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; @@ -213,6 +239,8 @@ public class MessageFactory { } //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( @@ -222,13 +250,16 @@ public class MessageFactory { private TextComponent statNameComponent(String statName, boolean topStat) { TextComponent.Builder stat = applyColor( - config.getStatNameFormatting(topStat, false), statName, NamedTextColor.YELLOW); + config.getStatNameFormatting(topStat, false), statName.toLowerCase().replace("_", " "), NamedTextColor.YELLOW); return applyStyle(config.getStatNameFormatting(topStat, true), stat).build(); } private TextComponent subStatNameComponent(String subStatName, boolean topStat) { + String subStatString = subStatName != null ? + "(" + subStatName.toLowerCase().replace("_", " ") + ") " : ""; + TextComponent.Builder subStat = applyColor( - config.getSubStatNameFormatting(topStat, false), subStatName, NamedTextColor.YELLOW); + config.getSubStatNameFormatting(topStat, false), subStatString, NamedTextColor.YELLOW); return applyStyle(config.getSubStatNameFormatting(topStat, true), subStat).build(); } @@ -238,14 +269,23 @@ public class MessageFactory { return applyStyle(config.getStatNumberFormatting(topStat, true), number).build(); } - 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(String content) { + TextComponent.Builder server = applyColor(config.getListTitleFormatting(false), content, NamedTextColor.YELLOW); + return applyStyle(config.getListTitleFormatting(true), server).build(); + } + + private TextComponent titleNumberComponent(int number) { + TextComponent.Builder titleNumber = applyColor(config.getListTitleNumberFormatting(false), number + "", NamedTextColor.GOLD); + return applyStyle(config.getListTitleNumberFormatting(true), titleNumber).build(); + } + + private TextComponent rankingNumberComponent(String number) { + TextComponent.Builder list = applyColor(config.getRankingNumberFormatting(false), number + "", NamedTextColor.GOLD); + return applyStyle(config.getRankingNumberFormatting(true), list).build(); } 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(); } private TextColor getColorFromString(String configString) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 24d736a..19eebf9 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -37,8 +37,10 @@ top-list-color: player-names: green stat-names: yellow sub-stat-names: '#FFD52B' - stat-numbers: '#55aaff' - list-numbers: gold + stat-numbers: '#55AAFF' + list-title: yellow + list-title-number: gold + ranking-numbers: gold dots: dark_gray top-list-style: @@ -46,7 +48,9 @@ top-list-style: stat-names: none sub-stat-names: none stat-numbers: none - list-numbers: none + list-title: none + list-title-number: none + ranking-numbers: none