diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/Main.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/Main.java index 50b511a..27b64ca 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/Main.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/Main.java @@ -6,7 +6,7 @@ import com.gmail.artemis.the.gr8.playerstats.commands.TabCompleter; import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; import com.gmail.artemis.the.gr8.playerstats.listeners.JoinListener; import com.gmail.artemis.the.gr8.playerstats.msg.LanguageKeyHandler; -import com.gmail.artemis.the.gr8.playerstats.msg.MessageFactory; +import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter; import com.gmail.artemis.the.gr8.playerstats.msg.PrideMessageFactory; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.Bukkit; @@ -36,15 +36,15 @@ public class Main extends JavaPlugin { LanguageKeyHandler language = new LanguageKeyHandler(); //for now always use the PrideMessageFactory (it'll use the regular formatting when needed) - MessageFactory messageFactory = new PrideMessageFactory(config, language); + MessageWriter messageWriter = new PrideMessageFactory(config, language); //initialize the threadManager - ThreadManager threadManager = new ThreadManager(adventure(), config, messageFactory, this); + ThreadManager threadManager = new ThreadManager(adventure(), config, messageWriter, this); //register all commands and the tabCompleter PluginCommand statcmd = this.getCommand("statistic"); if (statcmd != null) { - statcmd.setExecutor(new StatCommand(adventure(), messageFactory, threadManager)); + statcmd.setExecutor(new StatCommand(adventure(), messageWriter, threadManager)); statcmd.setTabCompleter(new TabCompleter()); } PluginCommand reloadcmd = this.getCommand("statisticreload"); 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 c4335d1..fee8fe5 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 @@ -1,10 +1,10 @@ package com.gmail.artemis.the.gr8.playerstats; import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; +import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter; 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.msg.MessageFactory; import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.command.CommandSender; @@ -19,16 +19,16 @@ public class ThreadManager { private final Main plugin; private final BukkitAudiences adventure; private static ConfigHandler config; - private static MessageFactory messageFactory; + private static MessageWriter messageWriter; private ReloadThread reloadThread; private StatThread statThread; private static long lastRecordedCalcTime; - public ThreadManager(BukkitAudiences a, ConfigHandler c, MessageFactory m, Main p) { + public ThreadManager(BukkitAudiences a, ConfigHandler c, MessageWriter m, Main p) { adventure = a; config = c; - messageFactory = m; + messageWriter = m; plugin = p; statThreadID = 0; @@ -41,7 +41,7 @@ public class ThreadManager { if (reloadThread == null || !reloadThread.isAlive()) { reloadThreadID += 1; - reloadThread = new ReloadThread(adventure, config, messageFactory, plugin, threshold, reloadThreadID, statThread, sender); + reloadThread = new ReloadThread(adventure, config, messageWriter, plugin, threshold, reloadThreadID, statThread, sender); reloadThread.start(); } else { @@ -52,7 +52,7 @@ public class ThreadManager { public void startStatThread(StatRequest request) { statThreadID += 1; - statThread = new StatThread(adventure, config, messageFactory, plugin, statThreadID, threshold, request, reloadThread); + statThread = new StatThread(adventure, config, messageWriter, plugin, statThreadID, threshold, request, reloadThread); statThread.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 35a1c71..a06cdfe 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 @@ -5,7 +5,7 @@ import com.gmail.artemis.the.gr8.playerstats.enums.Target; 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; -import com.gmail.artemis.the.gr8.playerstats.msg.MessageFactory; +import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.TextComponent; import org.bukkit.Material; @@ -23,23 +23,23 @@ import org.jetbrains.annotations.Nullable; public class StatCommand implements CommandExecutor { private final BukkitAudiences adventure; - private final MessageFactory messageFactory; + private final MessageWriter messageWriter; private final ThreadManager threadManager; - public StatCommand(BukkitAudiences a, MessageFactory m, ThreadManager t) { + public StatCommand(BukkitAudiences a, MessageWriter m, ThreadManager t) { adventure = a; - messageFactory = m; + messageWriter = m; threadManager = t; } @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { if (args.length == 0 || args[0].equalsIgnoreCase("help")) { //in case of less than 1 argument or "help", display the help message - adventure.sender(sender).sendMessage(messageFactory.helpMsg(sender instanceof ConsoleCommandSender)); + adventure.sender(sender).sendMessage(messageWriter.helpMsg(sender instanceof ConsoleCommandSender)); } else if (args[0].equalsIgnoreCase("examples") || args[0].equalsIgnoreCase("example")) { //in case of "statistic examples", show examples - adventure.sender(sender).sendMessage(messageFactory.usageExamples(sender instanceof ConsoleCommandSender)); + adventure.sender(sender).sendMessage(messageWriter.usageExamples(sender instanceof ConsoleCommandSender)); } else { StatRequest request = generateRequest(sender, args); @@ -143,17 +143,17 @@ public class StatCommand implements CommandExecutor { private @Nullable TextComponent checkRequest(StatRequest request) { boolean isConsoleSender = request.getCommandSender() instanceof ConsoleCommandSender; if (request.getStatistic() == null) { - return messageFactory.missingStatName(isConsoleSender); + return messageWriter.missingStatName(isConsoleSender); } Statistic.Type type = request.getStatistic().getType(); if (request.getSubStatEntry() == null && type != Statistic.Type.UNTYPED) { - return messageFactory.missingSubStatName(type, isConsoleSender); + return messageWriter.missingSubStatName(type, isConsoleSender); } else if (!matchingSubStat(request)) { - return messageFactory.wrongSubStatType(type, request.getSubStatEntry(), isConsoleSender); + return messageWriter.wrongSubStatType(type, request.getSubStatEntry(), isConsoleSender); } else if (request.getSelection() == Target.PLAYER && request.getPlayerName() == null) { - return messageFactory.missingPlayerName(isConsoleSender); + return messageWriter.missingPlayerName(isConsoleSender); } else { return null; diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/enums/PluginColor.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/enums/PluginColor.java new file mode 100644 index 0000000..008f15c --- /dev/null +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/enums/PluginColor.java @@ -0,0 +1,33 @@ +package com.gmail.artemis.the.gr8.playerstats.enums; + +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; + +/** This enum represents the colorscheme PlayerStats uses in its output messages. +

GRAY: ChatColor Gray

+

DARK_PURPLE: #6E3485 (used for default sub-titles, title-underscores and brackets)

+

MEDIUM_BLUE: #55AAFF (used for all plain feedback and error messages)

+

LIGHT_BLUE: #55C6FF (used for default hover-text)

+

DARK_GOLD: ChatColor Gold (used for first parts of usage messages and for first parts of hover-text accent)

+

MEDIUM_GOLD: #FFB80E (used for second parts of usage messages and for second parts of hover-text accent)

+

YELLOW: ChatColor Yellow (used for third parts of usage messages)

+ */ +public enum PluginColor { + GRAY (NamedTextColor.GRAY), + DARK_PURPLE (TextColor.fromHexString("#6E3485")), + MEDIUM_BLUE(TextColor.fromHexString("#55AAFF")), + LIGHT_BLUE (TextColor.fromHexString("#55C6FF")), + DARK_GOLD (NamedTextColor.GOLD), + MEDIUM_GOLD (TextColor.fromHexString("#FFD52B")), + YELLOW (NamedTextColor.YELLOW); + + private final TextColor color; + + PluginColor(TextColor color) { + this.color = color; + } + + public TextColor getColor() { + return color; + } +} diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/ComponentFactory.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/ComponentFactory.java new file mode 100644 index 0000000..7f5ca38 --- /dev/null +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/ComponentFactory.java @@ -0,0 +1,323 @@ +package com.gmail.artemis.the.gr8.playerstats.msg; + +import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; +import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor; +import com.gmail.artemis.the.gr8.playerstats.enums.Target; +import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest; +import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger; +import com.gmail.artemis.the.gr8.playerstats.utils.NumberFormatter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.event.HoverEvent; +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.Bukkit; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static net.kyori.adventure.text.Component.*; +import static net.kyori.adventure.text.Component.text; + +public class ComponentFactory { + + private static ConfigHandler config; + private final LanguageKeyHandler language; + + public ComponentFactory(ConfigHandler c, LanguageKeyHandler l) { + config = c; + language = l; + } + + /** Returns [PlayerStats] followed by a single space. */ + public TextComponent pluginPrefix(boolean isConsoleSender) { + return text("[") + .color(PluginColor.GRAY.getColor()) + .append(text("PlayerStats").color(PluginColor.DARK_GOLD.getColor())) + .append(text("]")) + .append(space()); + } + + /** Returns [PlayerStats] surrounded by underscores on both sides. */ + public TextComponent prefixTitle(boolean isConsoleSender) { + String underscores = "____________"; //12 underscores for both console and in-game + TextColor underscoreColor = PluginColor.DARK_PURPLE.getColor(); + + if (isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")) { + underscoreColor = NamedTextColor.DARK_PURPLE; + } + + return text(underscores).color(underscoreColor) + .append(text(" ")) //4 spaces + .append(pluginPrefix(isConsoleSender)) + .append(text(" ")) //3 spaces (since prefix already has one) + .append(text(underscores)); + } + + /** Returns a TextComponent with the input String as content, with color Gray and decoration Italic.*/ + public TextComponent subTitle(String content) { + return text(content).color(PluginColor.GRAY.getColor()).decorate(TextDecoration.ITALIC); + } + + /** Returns a TextComponents that represents a full message, with [PlayerStats] prepended. */ + public TextComponent msg(String msg, boolean isConsoleSender) { + return pluginPrefix(isConsoleSender) + .append(text(msg) + .color(PluginColor.MEDIUM_BLUE.getColor())); + } + + /** Returns a plain TextComponent that represents a single message line. + Each message part has its own designated color. + @param part1 color DARK_GOLD + @param part2 color MEDIUM_GOLD + @param part3 color YELLOW + @param part4 color GRAY + */ + public TextComponent msgPart(@Nullable String part1, @Nullable String part2, @Nullable String part3, @Nullable String part4) { + TextComponent.Builder msg = Component.text(); + if (part1 != null) { + msg.append(text(part1) + .color(PluginColor.DARK_GOLD.getColor())); + if (part2 != null || part3 != null || part4 != null) { + msg.append(space()); + } + } + if (part2 != null) { + msg.append(text(part2) + .color(PluginColor.MEDIUM_GOLD.getColor())); + if (part3 != null || part4 != null) { + msg.append(space()); + } + } + if (part3 != null) { + msg.append(text(part3) + .color(PluginColor.YELLOW.getColor())); + if (part4 != null) { + msg.append(space()); + } + } + if (part4 != null) { + msg.append(text(part4) + .color(PluginColor.GRAY.getColor())); + } + return msg.build(); + } + + /** Returns a TextComponent with hover-text that can consist of three different parts, + divided over two different lines. Each part has its own designated color. If all the + input Strings are null, it will return an empty Component. + @param plainText the non-hovering part + @param color the color for the non-hovering part + @param hoverLineOne text on the first line, with color LIGHT_BLUE + @param hoverLineTwoA text on the second line, with color DARK_GOLD + @param hoverLineTwoB text on the second part of the second line, with color MEDIUM_GOLD + */ + public TextComponent hoverMsgPart(@NotNull String plainText, @NotNull PluginColor color, @Nullable String hoverLineOne, @Nullable String hoverLineTwoA, @Nullable String hoverLineTwoB) { + TextComponent base = Component.text(plainText).color(color.getColor()); + TextComponent.Builder hoverText = Component.text(); + if (hoverLineOne != null) { + hoverText.append(text(hoverLineOne) + .color(PluginColor.LIGHT_BLUE.getColor())); + if (hoverLineTwoA != null || hoverLineTwoB != null) { + hoverText.append(newline()); + } + } + if (hoverLineTwoA != null) { + hoverText.append(text(hoverLineTwoA) + .color(PluginColor.DARK_GOLD.getColor())); + if (hoverLineTwoB != null) { + hoverText.append(space()); + } + } + if (hoverLineTwoB != null) { + hoverText.append(text(hoverLineTwoB).color(PluginColor.MEDIUM_GOLD.getColor())); + } + return base.hoverEvent(HoverEvent.showText(hoverText.build())); + } + + + public TextComponent playerName(Target selection, String playerName) { + return createComponent(playerName, + getColorFromString(config.getPlayerNameFormatting(selection, false)), + getStyleFromString(config.getPlayerNameFormatting(selection, true))); + } + + public TranslatableComponent statName(@NotNull StatRequest request) { + String statName = request.getStatistic().name(); + String subStatName = request.getSubStatEntry(); + if (!config.useTranslatableComponents()) { + statName = getPrettyName(statName); + subStatName = getPrettyName(subStatName); + } + else { + statName = language.getStatKey(request.getStatistic()); + switch (request.getStatistic().getType()) { + case BLOCK -> subStatName = language.getBlockKey(request.getBlock()); + case ENTITY -> subStatName = language.getEntityKey(request.getEntity()); + case ITEM -> subStatName = language.getItemKey(request.getItem()); + case UNTYPED -> { + } + } + } + return statName(request.getSelection(), statName, subStatName); + } + + private TranslatableComponent statName(@NotNull Target selection, @NotNull String statKey, @Nullable String subStatKey) { + TranslatableComponent.Builder totalName; + TextComponent subStat = subStatName(selection, subStatKey); + TextColor statNameColor = getColorFromString(config.getStatNameFormatting(selection, false)); + TextDecoration statNameStyle = getStyleFromString(config.getStatNameFormatting(selection, true)); + + if (statKey.equalsIgnoreCase("stat_type.minecraft.killed") && subStat != null) { + totalName = killEntity(subStat); + } + else if (statKey.equalsIgnoreCase("stat_type.minecraft.killed_by") && subStat != null) { + totalName = entityKilledBy(subStat); + } + else { + totalName = translatable().key(statKey); + if (subStat != null) totalName.append(space()).append(subStat); + } + + if (statNameStyle != null) totalName.decoration(statNameStyle, TextDecoration.State.TRUE); + return totalName + .color(statNameColor) + .build(); + } + + private @Nullable TextComponent subStatName(Target selection, @Nullable String subStatName) { + if (subStatName != null) { + TextDecoration style = getStyleFromString(config.getSubStatNameFormatting(selection, true)); + TextComponent.Builder subStat = text() + .append(text("(")) + .append(translatable() + .key(subStatName)) + .append(text(")")) + .color(getColorFromString(config.getSubStatNameFormatting(selection, false))); + + subStat.decorations(TextDecoration.NAMES.values(), false); + if (style != null) subStat.decoration(style, TextDecoration.State.TRUE); + return subStat.build(); + } + else { + return null; + } + } + + /** Construct a custom translation for kill_entity with the language key for commands.kill.success.single ("Killed %s"). + @return a TranslatableComponent Builder with the subStat Component as args.*/ + private TranslatableComponent.Builder killEntity(@NotNull TextComponent subStat) { + return translatable() + .key("commands.kill.success.single") //"Killed %s" + .args(subStat); + } + + /** Construct a custom translation for entity_killed_by with the language keys for stat.minecraft.deaths + ("Number of Deaths") and book.byAuthor ("by %s"). + @return a TranslatableComponent Builder with stat.minecraft.deaths as key, with a ChildComponent + with book.byAuthor as key and the subStat Component as args.*/ + private TranslatableComponent.Builder entityKilledBy(@NotNull TextComponent subStat) { + return translatable() + .key("stat.minecraft.deaths") //"Number of Deaths" + .append(space()) + .append(translatable() + .key("book.byAuthor") //"by %s" + .args(subStat)); + } + + public TextComponent statNumber(Target selection, long number) { + return createComponent(NumberFormatter.format(number), + getColorFromString(config.getStatNumberFormatting(selection, false)), + getStyleFromString(config.getStatNumberFormatting(selection, true))); + } + + public TextComponent title(Target selection, String content) { + return createComponent(content, + getColorFromString(config.getTitleFormatting(selection, false)), + getStyleFromString(config.getTitleFormatting(selection, true))); + } + + public TextComponent titleNumber(int number) { + return createComponent(number + "", + getColorFromString(config.getTitleNumberFormatting(false)), + getStyleFromString(config.getTitleNumberFormatting(true))); + } + + public TextComponent serverName() { + TextComponent colon = text(":").color(getColorFromString(config.getServerNameFormatting(false))); + return createComponent(config.getServerName(), + getColorFromString(config.getServerNameFormatting(false)), + getStyleFromString(config.getServerNameFormatting(true))) + .append(colon); + } + + public TextComponent rankingNumber(String number) { + return createComponent(number, + getColorFromString(config.getRankNumberFormatting(false)), + getStyleFromString(config.getRankNumberFormatting(true))); + } + + public TextComponent dots(String dots) { + return createComponent(dots, + getColorFromString(config.getDotsFormatting(false)), + getStyleFromString(config.getDotsFormatting(true))); + } + + private TextComponent createComponent(String content, TextColor color, @Nullable TextDecoration style) { + return style == null ? text(content).color(color) : text(content).color(color).decoration(style, TextDecoration.State.TRUE); + } + + /** Replace "_" with " " and capitalize each first letter of the input. + @param input String to prettify, case-insensitive*/ + private String getPrettyName(String input) { + if (input == null) return null; + StringBuilder capitals = new StringBuilder(input.toLowerCase()); + capitals.setCharAt(0, Character.toUpperCase(capitals.charAt(0))); + while (capitals.indexOf("_") != -1) { + MyLogger.replacingUnderscores(); + + int index = capitals.indexOf("_"); + capitals.setCharAt(index + 1, Character.toUpperCase(capitals.charAt(index + 1))); + capitals.setCharAt(index, ' '); + } + return capitals.toString(); + } + + private TextColor getColorFromString(String configString) { + if (configString != null) { + try { + if (configString.contains("#")) { + return TextColor.fromHexString(configString); + } + else { + return getTextColorByName(configString); + } + } + catch (IllegalArgumentException | NullPointerException exception) { + Bukkit.getLogger().warning(exception.toString()); + } + } + return null; + } + + private TextColor getTextColorByName(String textColor) { + Index names = NamedTextColor.NAMES; + return names.value(textColor); + } + + private @Nullable TextDecoration getStyleFromString(@NotNull String configString) { + if (configString.equalsIgnoreCase("none")) { + return null; + } + else if (configString.equalsIgnoreCase("magic")) { + return TextDecoration.OBFUSCATED; + } + else { + Index styles = TextDecoration.NAMES; + return styles.value(configString); + } + } + +} diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/MessageFactory.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/MessageFactory.java deleted file mode 100644 index 19ae412..0000000 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/MessageFactory.java +++ /dev/null @@ -1,556 +0,0 @@ -package com.gmail.artemis.the.gr8.playerstats.msg; - -import com.gmail.artemis.the.gr8.playerstats.enums.Target; -import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; -import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest; -import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger; -import com.gmail.artemis.the.gr8.playerstats.utils.NumberFormatter; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.TranslatableComponent; -import net.kyori.adventure.text.event.HoverEvent; -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.Bukkit; -import org.bukkit.Statistic; -import org.bukkit.map.MinecraftFont; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -import static net.kyori.adventure.text.Component.*; - -public class MessageFactory { - - private static ConfigHandler config; - private final LanguageKeyHandler language; - - private final TextColor msgColor; //my favorite shade of light blue, somewhere between blue and aqua - private final TextColor hoverBaseColor; //light blue - one shade lighter than msgColor - private final TextColor accentColor1; //gold - one shade lighter than standard gold - private final TextColor accentColor2; //yellow - a few shades darker than standard yellow - - public MessageFactory(ConfigHandler c, LanguageKeyHandler l) { - config = c; - language = l; - - msgColor = TextColor.fromHexString("#55AAFF"); - hoverBaseColor = TextColor.fromHexString("#55C6FF"); - accentColor1 = TextColor.fromHexString("#FFB80E"); - accentColor2 = TextColor.fromHexString("#FFD52B"); - } - - protected TextComponent pluginPrefix(boolean isConsoleSender) { - return text("[") - .color(NamedTextColor.GRAY) - .append(text("PlayerStats").color(NamedTextColor.GOLD)) - .append(text("]")) - .append(space()); - } - - public TextComponent reloadedConfig(boolean isConsoleSender) { - return pluginPrefix(isConsoleSender) - .append(text("Config reloaded!") - .color(msgColor)); - } - - public TextComponent stillReloading(boolean isConsoleSender) { - return pluginPrefix(isConsoleSender) - .append(text("The plugin is still (re)loading, your request will be processed when it is done!") - .color(msgColor)); - } - - public TextComponent partiallyReloaded(boolean isConsoleSender) { - return pluginPrefix(isConsoleSender) - .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, boolean isConsoleSender) { - return longWait ? pluginPrefix(isConsoleSender) - .append(text("Calculating statistics, this may take a minute...") - .color(msgColor)) - : pluginPrefix(isConsoleSender) - .append(text("Calculating statistics, this may take a few moments...") - .color(msgColor)); - } - - public TextComponent formatExceptions(@NotNull String exception, boolean isConsoleSender) { - return pluginPrefix(isConsoleSender) - .append(text(exception) - .color(msgColor)); - } - - public TextComponent missingStatName(boolean isConsoleSender) { - return pluginPrefix(isConsoleSender) - .append(text("Please provide a valid statistic name!") - .color(msgColor)); - } - - public TextComponent missingSubStatName(Statistic.Type statType, boolean isConsoleSender) { - return pluginPrefix(isConsoleSender) - .append(text("Please add a valid ") - .append(text(getSubStatTypeName(statType))) - .append(text(" to look up this statistic!")) - .color(msgColor)); - } - - public TextComponent missingPlayerName(boolean isConsoleSender) { - return pluginPrefix(isConsoleSender) - .append(text("Please specify a valid player-name!") - .color(msgColor)); - } - - public TextComponent wrongSubStatType(Statistic.Type statType, String subStatEntry, boolean isConsoleSender) { - return pluginPrefix(isConsoleSender) - .append(text("\"") - .append(text(subStatEntry)) - .append(text("\"")) - .append(text(" is not a valid ")) - .append(text(getSubStatTypeName(statType))) - .append(text("!")) - .color(msgColor)); - } - - public TextComponent unknownError(boolean isConsoleSender) { - return pluginPrefix(isConsoleSender) - .append(text("Something went wrong with your request, please try again or see /statistic for a usage explanation!") - .color(msgColor)); - } - - public TextComponent helpMsg(boolean isConsoleSender) { - if (!isConsoleSender) { - return config.useHoverText() ? helpMsgHover() : helpMsgPlain(false); - } - else { - return helpMsgPlain(true); - } - } - - public TextComponent usageExamples(boolean isConsoleSender) { - TextComponent spaces = text(" "); //4 spaces - TextComponent arrow = text("→ ").color(NamedTextColor.GOLD); - TextColor accentColor = TextColor.fromHexString("#FFE339"); - - if (isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")) { - arrow = text("-> ").color(NamedTextColor.GOLD); - accentColor = NamedTextColor.YELLOW; - } - - return Component.newline() - .append(getPrefixAsTitle(isConsoleSender)) - .append(newline()) - .append(text("Examples: ").color(NamedTextColor.GOLD)) - .append(newline()) - .append(spaces).append(arrow) - .append(text("/statistic animals_bred top").color(accentColor)) - .append(newline()) - .append(spaces).append(arrow) - .append(text("/statistic mine_block diorite me").color(accentColor)) - .append(newline()) - .append(spaces).append(arrow) - .append(text("/statistic deaths player Artemis_the_gr8").color(accentColor)) - .append(newline()); - } - - public TextComponent formatPlayerStat(int stat, @NotNull StatRequest request) { - if (!request.isValid()) return unknownError(request.isConsoleSender()); - return Component.text() - .append(playerNameComponent(Target.PLAYER, request.getPlayerName() + ": ")) - .append(statNumberComponent(Target.PLAYER, stat)) - .append(space()) - .append(statNameComponent(request)) - .append(space()) - .build(); - } - - public TextComponent formatTopStats(@NotNull LinkedHashMap topStats, @NotNull StatRequest request) { - if (!request.isValid()) return unknownError(request.isConsoleSender()); - - TextComponent.Builder topList = Component.text(); - topList.append(getTopStatTitle(topStats.size(), request)); - - boolean useDots = config.useDots(); - Set playerNames = topStats.keySet(); - MinecraftFont font = new MinecraftFont(); - - int count = 0; - for (String playerName : playerNames) { - count = count+1; - - topList.append(newline()) - .append(rankingNumberComponent(count + ". ")) - .append(playerNameComponent(Target.TOP, playerName)); - - if (useDots) { - topList.append(space()); - - int dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/2); - if (request.isConsoleSender()) { - dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/6) + 7; - } - else if (config.playerNameIsBold()) { - dots = (int) Math.round((130.0 - font.getWidth(count + ". ") - (font.getWidth(playerName) * 1.19))/2); - } - if (dots >= 1) { - topList.append(dotsComponent(".".repeat(dots))); - } - } - else { - topList.append(playerNameComponent(Target.TOP, ":")); - } - topList.append(space()).append(statNumberComponent(Target.TOP, topStats.get(playerName))); - } - return topList.build(); - } - - public TextComponent formatServerStat(long stat, @NotNull StatRequest request) { - if (!request.isValid()) return unknownError(request.isConsoleSender()); - return Component.text() - .append(titleComponent(Target.SERVER, config.getServerTitle())) - .append(space()) - .append(serverNameComponent()) - .append(space()) - .append(statNumberComponent(Target.SERVER, stat)) - .append(space()) - .append(statNameComponent(request)) - .append(space()) - .build(); - } - - protected TextComponent getPrefixAsTitle(boolean isConsoleSender) { - String underscores = "____________"; //12 underscores for both console and in-game - TextColor underscoreColor = TextColor.fromHexString("#6E3485"); //a dark shade of purple - - if (isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")) { - underscoreColor = NamedTextColor.DARK_PURPLE; - } - - return text(underscores).color(underscoreColor) - .append(text(" ")) //4 spaces - .append(pluginPrefix(isConsoleSender)) - .append(text(" ")) //3 spaces (since prefix already has one) - .append(text(underscores)); - } - - protected TextComponent getTopStatTitle(int topLength, @NotNull StatRequest request) { - return Component.text() - .append(newline()) - .append(pluginPrefix(request.isConsoleSender())) - .append(titleComponent(Target.TOP, config.getTopStatsTitle())) - .append(space()) - .append(titleNumberComponent(topLength)) - .append(space()) - .append(statNameComponent(request)) - .append(space()) - .build(); - } - - protected TextComponent playerNameComponent(Target selection, String playerName) { - return getComponent(playerName, - getColorFromString(config.getPlayerNameFormatting(selection, false)), - getStyleFromString(config.getPlayerNameFormatting(selection, true))); - } - - protected TranslatableComponent statNameComponent(@NotNull StatRequest request) { - if (request.getStatistic() == null) return null; - TextColor statNameColor = getColorFromString(config.getStatNameFormatting(request.getSelection(), false)); - TextDecoration statNameStyle = getStyleFromString(config.getStatNameFormatting(request.getSelection(), true)); - - String statName = request.getStatistic().name(); - String subStatName = request.getSubStatEntry(); - if (!config.useTranslatableComponents()) { - statName = getPrettyName(statName); - subStatName = getPrettyName(subStatName); - } - else { - statName = language.getStatKey(request.getStatistic()); - switch (request.getStatistic().getType()) { - case BLOCK -> subStatName = language.getBlockKey(request.getBlock()); - case ENTITY -> subStatName = language.getEntityKey(request.getEntity()); - case ITEM -> subStatName = language.getItemKey(request.getItem()); - case UNTYPED -> { - } - } - } - TextComponent subStat = subStatNameComponent(request.getSelection(), subStatName); - TranslatableComponent.Builder totalName; - - if (statName.equalsIgnoreCase("stat_type.minecraft.killed") && subStat != null) { - totalName = killEntityComponent(subStat); - } - else if (statName.equalsIgnoreCase("stat_type.minecraft.killed_by") && subStat != null) { - totalName = entityKilledByComponent(subStat); - } - else { - totalName = translatable().key(statName); - if (subStat != null) totalName.append(space()).append(subStat); - } - - if (statNameStyle != null) totalName.decoration(statNameStyle, TextDecoration.State.TRUE); - return totalName - .color(statNameColor) - .build(); - } - - protected @Nullable TextComponent subStatNameComponent(Target selection, @Nullable String subStatName) { - if (subStatName != null) { - TextDecoration style = getStyleFromString(config.getSubStatNameFormatting(selection, true)); - TextComponent.Builder subStat = text() - .append(text("(")) - .append(translatable() - .key(subStatName)) - .append(text(")")) - .color(getColorFromString(config.getSubStatNameFormatting(selection, false))); - - subStat.decorations(TextDecoration.NAMES.values(), false); - if (style != null) subStat.decoration(style, TextDecoration.State.TRUE); - return subStat.build(); - } - else { - return null; - } - } - - /** Construct a custom translation for kill_entity with the language key for commands.kill.success.single ("Killed %s"). - @return a TranslatableComponent Builder with the subStat Component as args.*/ - private TranslatableComponent.Builder killEntityComponent(@NotNull TextComponent subStat) { - return translatable() - .key("commands.kill.success.single") //"Killed %s" - .args(subStat); - } - - /** Construct a custom translation for entity_killed_by with the language keys for stat.minecraft.deaths - ("Number of Deaths") and book.byAuthor ("by %s"). - @return a TranslatableComponent Builder with stat.minecraft.deaths as key, with a ChildComponent - with book.byAuthor as key and the subStat Component as args.*/ - private TranslatableComponent.Builder entityKilledByComponent(@NotNull TextComponent subStat) { - return translatable() - .key("stat.minecraft.deaths") //"Number of Deaths" - .append(space()) - .append(translatable() - .key("book.byAuthor") //"by %s" - .args(subStat)); - } - - protected TextComponent statNumberComponent(Target selection, long number) { - return getComponent(NumberFormatter.format(number), - getColorFromString(config.getStatNumberFormatting(selection, false)), - getStyleFromString(config.getStatNumberFormatting(selection, true))); - } - - protected TextComponent titleComponent(Target selection, String content) { - return getComponent(content, - getColorFromString(config.getTitleFormatting(selection, false)), - getStyleFromString(config.getTitleFormatting(selection, true))); - } - - protected TextComponent titleNumberComponent(int number) { - return getComponent(number + "", - getColorFromString(config.getTitleNumberFormatting(false)), - getStyleFromString(config.getTitleNumberFormatting(true))); - } - - protected TextComponent serverNameComponent() { - TextComponent colon = text(":").color(getColorFromString(config.getServerNameFormatting(false))); - return getComponent(config.getServerName(), - getColorFromString(config.getServerNameFormatting(false)), - getStyleFromString(config.getServerNameFormatting(true))) - .append(colon); - } - - protected TextComponent rankingNumberComponent(String number) { - return getComponent(number, - getColorFromString(config.getRankNumberFormatting(false)), - getStyleFromString(config.getRankNumberFormatting(true))); - } - - protected TextComponent dotsComponent(String dots) { - 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); - } - - /** Replace "_" with " " and capitalize each first letter of the input. - @param input String to prettify, case-insensitive*/ - private String getPrettyName(String input) { - if (input == null) return null; - StringBuilder capitals = new StringBuilder(input.toLowerCase()); - capitals.setCharAt(0, Character.toUpperCase(capitals.charAt(0))); - while (capitals.indexOf("_") != -1) { - MyLogger.replacingUnderscores(); - - int index = capitals.indexOf("_"); - capitals.setCharAt(index + 1, Character.toUpperCase(capitals.charAt(index + 1))); - capitals.setCharAt(index, ' '); - } - return capitals.toString(); - } - - private TextColor getColorFromString(String configString) { - if (configString != null) { - try { - if (configString.contains("#")) { - return TextColor.fromHexString(configString); - } - else { - return getTextColorByName(configString); - } - } - catch (IllegalArgumentException | NullPointerException exception) { - Bukkit.getLogger().warning(exception.toString()); - } - } - return null; - } - - private TextColor getTextColorByName(String textColor) { - Index names = NamedTextColor.NAMES; - return names.value(textColor); - } - - private @Nullable TextDecoration getStyleFromString(@NotNull String configString) { - if (configString.equalsIgnoreCase("none")) { - return null; - } - else if (configString.equalsIgnoreCase("magic")) { - return TextDecoration.OBFUSCATED; - } - else { - Index styles = TextDecoration.NAMES; - return styles.value(configString); - } - } - - /** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */ - private String getSubStatTypeName(Statistic.Type statType) { - String subStat = "sub-statistic"; - if (statType == null) return subStat; - switch (statType) { - case BLOCK -> subStat = "block"; - case ENTITY -> subStat = "entity"; - case ITEM -> subStat = "item"; - } - return subStat; - } - - /** Returns the usage-explanation with hovering text */ - private @NotNull TextComponent helpMsgHover() { - TextComponent spaces = text(" "); //4 spaces - TextComponent arrow = text("→ ").color(NamedTextColor.GOLD); //alt + 26 - TextColor arguments = NamedTextColor.YELLOW; - - return Component.newline() - .append(getPrefixAsTitle(false)) - .append(newline()) - .append(text("Hover over the arguments for more information!").color(NamedTextColor.GRAY).decorate(TextDecoration.ITALIC)) - .append(newline()) - .append(text("Usage: ").color(NamedTextColor.GOLD)).append(text("/statistic").color(arguments)) - .append(newline()) - .append(spaces).append(arrow) - .append(text("name").color(arguments) - .hoverEvent(HoverEvent.showText(text("The name that describes the statistic").color(hoverBaseColor) - .append(newline()) - .append(text("Example: ").color(accentColor1)) - .append(text("\"animals_bred\"").color(accentColor2))))) - .append(newline()) - .append(spaces).append(arrow) - .append(text("sub-statistic").color(arguments) - .hoverEvent(HoverEvent.showText( - text("Some statistics need an item, block or entity as extra input").color(hoverBaseColor) - .append(newline()) - .append(text("Example: ").color(accentColor1) - .append(text("\"mine_block diorite\"").color(accentColor2)))))) - .append(newline()) - .append(spaces) - .append(text("→").color(NamedTextColor.GOLD) - .hoverEvent(HoverEvent.showText( - text("Choose one").color(TextColor.fromHexString("#6E3485"))))) - .append(space()) - .append(text("me").color(arguments) - .hoverEvent(HoverEvent.showText( - text("See your own statistic").color(hoverBaseColor)))) - .append(text(" | ").color(arguments)) - .append(text("player").color(arguments) - .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) - .append(text(config.getTopListMaxSize()).color(hoverBaseColor))))) - .append(newline()) - .append(spaces).append(arrow) - .append(text("player-name").color(arguments) - .hoverEvent(HoverEvent.showText( - text("In case you typed ").color(hoverBaseColor) - .append(text("\"player\"").color(accentColor2) - .append(text(", add the player's name").color(hoverBaseColor)))))); - } - - /** Returns the usage-explanation without any hovering text. - If BukkitVersion is CraftBukkit, this doesn't use unicode symbols or hex colors */ - private @NotNull TextComponent helpMsgPlain(boolean isConsoleSender) { - TextComponent spaces = text(" "); //4 spaces - TextComponent arrow = text("→ ").color(NamedTextColor.GOLD); //alt + 26; - TextComponent bullet = text("• ").color(NamedTextColor.GOLD); //alt + 7 - TextColor arguments = NamedTextColor.YELLOW; - TextColor accentColor = accentColor2; - - if (isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")) { - arrow = text("-> ").color(NamedTextColor.GOLD); - bullet = text("* ").color(NamedTextColor.GOLD); - accentColor = NamedTextColor.GOLD; - } - - return Component.newline() - .append(getPrefixAsTitle(isConsoleSender)) - .append(newline()) - .append(text("Type \"/statistic examples\" to see examples!").color(NamedTextColor.GRAY).decorate(TextDecoration.ITALIC)) - .append(newline()) - .append(text("Usage: ").color(NamedTextColor.GOLD)) - .append(text("/statistic").color(arguments)) - .append(newline()) - .append(spaces).append(arrow) - .append(text("name").color(arguments)) - .append(newline()) - .append(spaces).append(arrow) - .append(text("{sub-statistic}").color(arguments)) - .append(space()) - .append(text("(a block, item or entity)").color(NamedTextColor.GRAY)) - .append(newline()) - .append(spaces).append(arrow) - .append(text("me | player | server | top").color(arguments)) - .append(newline()) - .append(spaces).append(spaces).append(bullet) - .append(text("me:").color(accentColor)) - .append(space()).append(text("your own statistic").color(NamedTextColor.GRAY)) - .append(newline()) - .append(spaces).append(spaces).append(bullet) - .append(text("player:").color(accentColor)) - .append(space()).append(text("choose a player").color(NamedTextColor.GRAY)) - .append(newline()) - .append(spaces).append(spaces).append(bullet) - .append(text("server:").color(accentColor)) - .append(space()).append(text("everyone on the server combined").color(NamedTextColor.GRAY)) - .append(newline()) - .append(spaces).append(spaces).append(bullet) - .append(text("top:").color(accentColor)) - .append(space()).append(text("the top").color(NamedTextColor.GRAY) - .append(space()).append(text(config.getTopListMaxSize()))) - .append(newline()) - .append(spaces).append(arrow) - .append(text("{player-name}").color(arguments)); - } -} \ No newline at end of file diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/MessageWriter.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/MessageWriter.java new file mode 100644 index 0000000..f2893e2 --- /dev/null +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/MessageWriter.java @@ -0,0 +1,308 @@ +package com.gmail.artemis.the.gr8.playerstats.msg; + +import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor; +import com.gmail.artemis.the.gr8.playerstats.enums.Target; +import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; +import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Bukkit; +import org.bukkit.Statistic; +import org.bukkit.map.MinecraftFont; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +import static net.kyori.adventure.text.Component.*; + +/** Composes messages to send to Players or Console. This class is responsible + for constructing a final component with the text content of the desired message. + The component parts (with appropriate formatting) are supplied by a ComponentFactory.*/ +public class MessageWriter { + + private static ConfigHandler config; + private static ComponentFactory componentFactory; + + private final TextColor hoverBaseColor; //light blue - one shade lighter than msgColor + private final TextColor accentColor1; //gold - one shade lighter than standard gold + private final TextColor accentColor2; //yellow - a few shades darker than standard yellow + + public MessageWriter(ConfigHandler c, LanguageKeyHandler l) { + config = c; + componentFactory = new ComponentFactory(c, l); + + hoverBaseColor = TextColor.fromHexString("#55C6FF"); + accentColor1 = TextColor.fromHexString("#FFB80E"); + accentColor2 = TextColor.fromHexString("#FFD52B"); + } + + public TextComponent reloadedConfig(boolean isConsoleSender) { + return componentFactory.msg( + "Config reloaded!", isConsoleSender); + } + + public TextComponent stillReloading(boolean isConsoleSender) { + return componentFactory.msg( + "The plugin is still (re)loading, " + + "your request will be processed when it is done!", isConsoleSender); + } + + public TextComponent partiallyReloaded(boolean isConsoleSender) { + return componentFactory.msg( + "The reload process was interrupted. " + + "If you notice unexpected behavior, " + + "please reload PlayerStats again to fix it!", isConsoleSender); + } + + public TextComponent waitAMoment(boolean longWait, boolean isConsoleSender) { + String msg = longWait ? "Calculating statistics, this may take a minute..." : + "Calculating statistics, this may take a few moments..."; + return componentFactory.msg(msg, isConsoleSender); + } + + public TextComponent formatExceptions(@NotNull String exception, boolean isConsoleSender) { + return componentFactory.msg(exception, isConsoleSender); + } + + public TextComponent missingStatName(boolean isConsoleSender) { + return componentFactory.msg( + "Please provide a valid statistic name!", isConsoleSender); + } + + public TextComponent missingSubStatName(Statistic.Type statType, boolean isConsoleSender) { + return componentFactory.msg( + "Please add a valid " + + getSubStatTypeName(statType) + + " to look up this statistic!", isConsoleSender); + } + + public TextComponent missingPlayerName(boolean isConsoleSender) { + return componentFactory.msg( + "Please specify a valid player-name!", isConsoleSender); + } + + public TextComponent wrongSubStatType(Statistic.Type statType, String subStatEntry, boolean isConsoleSender) { + return componentFactory.msg( + "\"" + subStatEntry + "\" is not a valid " + getSubStatTypeName(statType) + "!", isConsoleSender); + } + + public TextComponent unknownError(boolean isConsoleSender) { + return componentFactory.msg( + "Something went wrong with your request, " + + "please try again or see /statistic for a usage explanation!", isConsoleSender); + } + + + public TextComponent formatPlayerStat(int stat, @NotNull StatRequest request) { + if (!request.isValid()) return unknownError(request.isConsoleSender()); + return Component.text() + .append(componentFactory.playerName(Target.PLAYER, request.getPlayerName() + ": ")) + .append(componentFactory.statNumber(Target.PLAYER, stat)) + .append(space()) + .append(componentFactory.statName(request)) + .append(space()) + .build(); + } + + public TextComponent formatTopStats(@NotNull LinkedHashMap topStats, @NotNull StatRequest request) { + if (!request.isValid()) return unknownError(request.isConsoleSender()); + + TextComponent.Builder topList = Component.text() + .append(newline()) + .append(componentFactory.pluginPrefix(request.isConsoleSender())) + .append(componentFactory.title(Target.TOP, config.getTopStatsTitle())) + .append(space()) + .append(componentFactory.titleNumber(topStats.size())) + .append(space()) + .append(componentFactory.statName(request)); + + boolean useDots = config.useDots(); + boolean boldNames = config.playerNameIsBold(); + + Set playerNames = topStats.keySet(); + MinecraftFont font = new MinecraftFont(); + + int count = 0; + for (String playerName : playerNames) { + count = count+1; + + topList.append(newline()) + .append(componentFactory.rankingNumber(count + ". ")) + .append(componentFactory.playerName(Target.TOP, playerName)); + + if (useDots) { + topList.append(space()); + + int dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/2); + if (request.isConsoleSender()) { + dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/6) + 7; + } + else if (boldNames) { + dots = (int) Math.round((130.0 - font.getWidth(count + ". ") - (font.getWidth(playerName) * 1.19))/2); + } + if (dots >= 1) { + topList.append(componentFactory.dots(".".repeat(dots))); + } + } + else { + topList.append(componentFactory.playerName(Target.TOP, ":")); + } + topList.append(space()).append(componentFactory.statNumber(Target.TOP, topStats.get(playerName))); + } + return topList.build(); + } + + public TextComponent formatServerStat(long stat, @NotNull StatRequest request) { + if (!request.isValid()) return unknownError(request.isConsoleSender()); + return Component.text() + .append(componentFactory.title(Target.SERVER, config.getServerTitle())) + .append(space()) + .append(componentFactory.serverName()) + .append(space()) + .append(componentFactory.statNumber(Target.SERVER, stat)) + .append(space()) + .append(componentFactory.statName(request)) + .append(space()) + .build(); + } + + /** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */ + private String getSubStatTypeName(Statistic.Type statType) { + String subStat = "sub-statistic"; + if (statType == null) return subStat; + switch (statType) { + case BLOCK -> subStat = "block"; + case ENTITY -> subStat = "entity"; + case ITEM -> subStat = "item"; + } + return subStat; + } + + public TextComponent usageExamples(boolean isConsoleSender) { + TextComponent spaces = text(" "); //4 spaces + TextComponent arrow = text("→ ").color(NamedTextColor.GOLD); + TextColor accentColor = TextColor.fromHexString("#FFE339"); + + if (isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")) { + arrow = text("-> ").color(NamedTextColor.GOLD); + accentColor = NamedTextColor.YELLOW; + } + + return Component.newline() + .append(componentFactory.prefixTitle(isConsoleSender)) + .append(newline()) + .append(text("Examples: ").color(NamedTextColor.GOLD)) + .append(newline()) + .append(spaces).append(arrow) + .append(text("/statistic animals_bred top").color(accentColor)) + .append(newline()) + .append(spaces).append(arrow) + .append(text("/statistic mine_block diorite me").color(accentColor)) + .append(newline()) + .append(spaces).append(arrow) + .append(text("/statistic deaths player Artemis_the_gr8").color(accentColor)) + .append(newline()); + } + + public TextComponent helpMsg(boolean isConsoleSender) { + if (isConsoleSender || !config.useHoverText()) { + return helpMsgPlain(isConsoleSender); + } + else { + return helpMsgHover(); + } + } + + /** Returns the usage-explanation with hovering text */ + private TextComponent helpMsgHover() { + String arrow = " → "; //4 spaces, alt + 26, 1 space + + return Component.newline() + .append(componentFactory.prefixTitle(false)) + .append(newline()) + .append(componentFactory.subTitle("Hover over the arguments for more information!")) + .append(newline()) + .append(componentFactory.msgPart("Usage:", null, "/statistic", null)) + .append(newline()) + .append(componentFactory.msgPart(arrow, null, null, null) + .append(componentFactory.hoverMsgPart("name", PluginColor.YELLOW, + "The name that describes the statistic", + "Example:", + "\"animals_bred\""))) + .append(newline()) + .append(spaces).append(arrow) + .append(text("sub-statistic").color(arguments) + .hoverEvent(HoverEvent.showText( + text("Some statistics need an item, block or entity as extra input").color(hoverBaseColor) + .append(newline()) + .append(text("Example: ").color(accentColor1) + .append(text("\"mine_block diorite\"").color(accentColor2)))))) + .append(newline()) + .append(spaces) + .append(text("→").color(NamedTextColor.GOLD) + .hoverEvent(HoverEvent.showText( + text("Choose one").color(TextColor.fromHexString("#6E3485"))))) + .append(space()) + .append(text("me").color(arguments) + .hoverEvent(HoverEvent.showText( + text("See your own statistic").color(hoverBaseColor)))) + .append(text(" | ").color(arguments)) + .append(text("player").color(arguments) + .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) + .append(text(config.getTopListMaxSize()).color(hoverBaseColor))))) + .append(newline()) + .append(spaces).append(arrow) + .append(text("player-name").color(arguments) + .hoverEvent(HoverEvent.showText( + text("In case you typed ").color(hoverBaseColor) + .append(text("\"player\"").color(accentColor2) + .append(text(", add the player's name").color(hoverBaseColor)))))); + } + + //TODO create ConsoleComponentFactory for Bukkit + + /** Returns the usage-explanation without any hovering text. + If BukkitVersion is CraftBukkit, this doesn't use unicode symbols or hex colors */ + private TextComponent helpMsgPlain(boolean isConsoleSender) { + String arrow = " →"; //4 spaces, alt + 26 + String bullet = " •"; //8 spaces, alt + 7 + if (isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")) { + arrow = " ->"; + bullet = " *"; + } + return Component.newline() + .append(componentFactory.prefixTitle(isConsoleSender)) + .append(newline()) + .append(componentFactory.subTitle("Type \"statistic examples\" to see examples!")) + .append(newline()) + .append(componentFactory.msgPart("Usage:", null, "/statistic", null)) + .append(newline()) + .append(componentFactory.msgPart(arrow, null, "name", null)) + .append(newline()) + .append(componentFactory.msgPart(arrow, null, "{sub-statistic}", "(a block, item or entity)")) + .append(newline()) + .append(componentFactory.msgPart(arrow, null, "me | player | server | top", null)) + .append(newline()) + .append(componentFactory.msgPart(bullet, "me:", null, "your own statistic")) + .append(newline()) + .append(componentFactory.msgPart(bullet, "player:", null, "choose a player")) + .append(newline()) + .append(componentFactory.msgPart(bullet, "server:", null, "everyone on the server combined")) + .append(newline()) + .append(componentFactory.msgPart(bullet, "top:", null, "the top " + config.getTopListMaxSize())) + .append(newline()) + .append(componentFactory.msgPart(arrow, null, "{player-name", null)); + } +} \ No newline at end of file diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/PrideMessageFactory.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/PrideMessageFactory.java index 3845ed8..2773bb3 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/PrideMessageFactory.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/PrideMessageFactory.java @@ -12,7 +12,7 @@ import java.time.Month; import static net.kyori.adventure.text.Component.*; -public class PrideMessageFactory extends MessageFactory { +public class PrideMessageFactory extends MessageWriter { private static ConfigHandler config; 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 80700be..f872567 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 @@ -3,8 +3,8 @@ 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.config.ConfigHandler; +import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter; import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread; -import com.gmail.artemis.the.gr8.playerstats.msg.MessageFactory; import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger; import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler; import net.kyori.adventure.platform.bukkit.BukkitAudiences; @@ -29,19 +29,19 @@ public class ReloadThread extends Thread { private final BukkitAudiences adventure; private static ConfigHandler config; - private static MessageFactory messageFactory; + private static MessageWriter messageWriter; private final Main plugin; private final StatThread statThread; private final CommandSender sender; - public ReloadThread(BukkitAudiences a, ConfigHandler c, MessageFactory m, Main p, int threshold, int ID, @Nullable StatThread s, @Nullable CommandSender se) { + public ReloadThread(BukkitAudiences a, ConfigHandler c, MessageWriter m, Main p, int threshold, int ID, @Nullable StatThread s, @Nullable CommandSender se) { this.threshold = threshold; reloadThreadID = ID; adventure = a; config = c; - messageFactory = m; + messageWriter = m; plugin = p; statThread = s; @@ -76,13 +76,13 @@ public class ReloadThread extends Thread { catch (ConcurrentModificationException e) { MyLogger.logException(e, "ReloadThread", "run(), trying to update OfflinePlayerList during a reload"); if (sender != null) { - adventure.sender(sender).sendMessage(messageFactory.partiallyReloaded(sender instanceof ConsoleCommandSender)); + adventure.sender(sender).sendMessage(messageWriter.partiallyReloaded(sender instanceof ConsoleCommandSender)); } } MyLogger.logTimeTakenDefault("ReloadThread", ("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time); if (sender != null) { - adventure.sender(sender).sendMessage(messageFactory.reloadedConfig(sender instanceof ConsoleCommandSender)); + adventure.sender(sender).sendMessage(messageWriter.reloadedConfig(sender instanceof ConsoleCommandSender)); } } } 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 45e393e..b40f80d 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 @@ -2,16 +2,15 @@ package com.gmail.artemis.the.gr8.playerstats.statistic; import com.gmail.artemis.the.gr8.playerstats.Main; import com.gmail.artemis.the.gr8.playerstats.enums.Target; +import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter; import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread; import com.gmail.artemis.the.gr8.playerstats.ThreadManager; import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger; import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler; -import com.gmail.artemis.the.gr8.playerstats.msg.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.bukkit.command.ConsoleCommandSender; import org.jetbrains.annotations.NotNull; @@ -31,11 +30,11 @@ public class StatThread extends Thread { private final BukkitAudiences adventure; private static ConfigHandler config; - private static MessageFactory messageFactory; + private static MessageWriter messageWriter; private final Main plugin; //constructor (called on thread creation) - public StatThread(BukkitAudiences a, ConfigHandler c, MessageFactory m, Main p, int ID, int threshold, StatRequest s, @Nullable ReloadThread r) { + public StatThread(BukkitAudiences a, ConfigHandler c, MessageWriter m, Main p, int ID, int threshold, StatRequest s, @Nullable ReloadThread r) { this.threshold = threshold; request = s; @@ -43,7 +42,7 @@ public class StatThread extends Thread { adventure = a; config = c; - messageFactory = m; + messageWriter = m; plugin = p; this.setName("StatThread-" + ID); @@ -55,7 +54,7 @@ public class StatThread extends Thread { public void run() throws IllegalStateException, NullPointerException { MyLogger.threadStart(this.getName()); - if (messageFactory == null || plugin == null) { + if (messageWriter == null || plugin == null) { throw new IllegalStateException("Not all classes off the plugin are running!"); } if (request == null) { @@ -65,7 +64,7 @@ public class StatThread extends Thread { try { MyLogger.waitingForOtherThread(this.getName(), reloadThread.getName()); adventure.sender(request.getCommandSender()) - .sendMessage(messageFactory + .sendMessage(messageWriter .stillReloading(request.getCommandSender() instanceof ConsoleCommandSender)); reloadThread.join(); } catch (InterruptedException e) { @@ -80,26 +79,26 @@ public class StatThread extends Thread { if (selection == Target.TOP || selection == Target.SERVER) { if (ThreadManager.getLastRecordedCalcTime() > 20000) { - adventure.sender(sender).sendMessage(messageFactory.waitAMoment(true, isConsoleSencer)); + adventure.sender(sender).sendMessage(messageWriter.waitAMoment(true, isConsoleSencer)); } else if (ThreadManager.getLastRecordedCalcTime() > 2000) { - adventure.sender(sender).sendMessage(messageFactory.waitAMoment(false, isConsoleSencer)); + adventure.sender(sender).sendMessage(messageWriter.waitAMoment(false, isConsoleSencer)); } try { if (selection == Target.TOP) { - adventure.sender(sender).sendMessage(messageFactory.formatTopStats(getTopStats(), request)); + adventure.sender(sender).sendMessage(messageWriter.formatTopStats(getTopStats(), request)); } else { - adventure.sender(sender).sendMessage(messageFactory.formatServerStat(getServerTotal(), request)); + adventure.sender(sender).sendMessage(messageWriter.formatServerStat(getServerTotal(), request)); } } catch (ConcurrentModificationException e) { if (!isConsoleSencer) { - adventure.sender(sender).sendMessage(messageFactory.unknownError(false)); + adventure.sender(sender).sendMessage(messageWriter.unknownError(false)); } } catch (Exception e) { - adventure.sender(sender).sendMessage(messageFactory.formatExceptions(e.toString(), isConsoleSencer)); + adventure.sender(sender).sendMessage(messageWriter.formatExceptions(e.toString(), isConsoleSencer)); MyLogger.logException(e, "StatThread", "run(), trying to calculate or format a top or server statistic"); } } @@ -107,10 +106,10 @@ public class StatThread extends Thread { else if (selection == Target.PLAYER) { try { adventure.sender(sender).sendMessage( - messageFactory.formatPlayerStat(getIndividualStat(), request)); + messageWriter.formatPlayerStat(getIndividualStat(), request)); } catch (UnsupportedOperationException | NullPointerException e) { - adventure.sender(sender).sendMessage(messageFactory.formatExceptions(e.toString(), isConsoleSencer)); + adventure.sender(sender).sendMessage(messageWriter.formatExceptions(e.toString(), isConsoleSencer)); } } } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MyLogger.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MyLogger.java index d26417d..842b3cf 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MyLogger.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MyLogger.java @@ -79,7 +79,7 @@ public class MyLogger { } } - /** If DebugLevel is MEDIUM or HIGH, logs when the while loop in MessageFactory, getLanguageKey is being run. */ + /** If DebugLevel is MEDIUM or HIGH, logs when the while loop in MessageWriter, getLanguageKey is being run. */ public static void replacingUnderscores() { if (debugLevel != DebugLevel.LOW) { logger.info("Replacing underscores and capitalizing names...");