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 ea131d5..d570331 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 @@ -5,9 +5,7 @@ import com.gmail.artemis.the.gr8.playerstats.commands.StatCommand; import com.gmail.artemis.the.gr8.playerstats.commands.TabCompleter; import com.gmail.artemis.the.gr8.playerstats.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.MessageWriter; -import com.gmail.artemis.the.gr8.playerstats.msg.PrideComponentFactory; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.Bukkit; import org.bukkit.command.PluginCommand; 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 9ea2f08..fa0a9c2 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 @@ -1,7 +1,6 @@ package com.gmail.artemis.the.gr8.playerstats.commands; import com.gmail.artemis.the.gr8.playerstats.ThreadManager; -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.utils.EnumHandler; import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest; @@ -9,7 +8,6 @@ import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler; import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.format.TextColor; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Statistic; @@ -22,8 +20,6 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static net.kyori.adventure.text.Component.text; - public class StatCommand implements CommandExecutor { @@ -47,16 +43,6 @@ public class StatCommand implements CommandExecutor { args[0].equalsIgnoreCase("example")) { //in case of "statistic examples", show examples adventure.sender(sender).sendMessage(messageWriter.usageExamples(isBukkitConsole)); } - else if (args[0].equalsIgnoreCase("test")) { - TextComponent msg = text("Tier 1").color(PluginColor.GOLD.getColor()) - .append(text("Tier 2").color(PluginColor.MEDIUM_GOLD.getColor()) - .append(text("Tier 3").color(TextColor.fromHexString("#FFEA40")) - .append(text("Tier 4").color(PluginColor.LIGHT_GOLD.getColor())) - .append(text("Tier 3?"))) - .append(text("Tier 2?"))) - .append(text("Tier 1?")); - adventure.sender(sender).sendMessage(msg); - } else { StatRequest request = generateRequest(sender, args); TextComponent issues = checkRequest(request, isBukkitConsole); 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 56c3c54..c21f223 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 @@ -15,7 +15,7 @@ public class TabCompleter implements org.bukkit.command.TabCompleter { private final List commandOptions; - + //TODO add "example" to the list public TabCompleter() { commandOptions = new ArrayList<>(); commandOptions.add("top"); diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/config/ConfigHandler.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/config/ConfigHandler.java index 3fa03d3..68b9eb1 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/config/ConfigHandler.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/config/ConfigHandler.java @@ -23,7 +23,7 @@ public class ConfigHandler { saveDefaultConfig(); config = YamlConfiguration.loadConfiguration(configFile); - configVersion = 4; + configVersion = 5; checkConfigVersion(); MyLogger.setDebugLevel(getDebugLevel()); @@ -91,18 +91,60 @@ public class ConfigHandler { return config.getInt("number-of-days-since-last-joined", 0); } - /** Whether to use TranslatableComponents for statistic, block, item and entity names. + /** Whether to use TranslatableComponents wherever possible. + Currently supported: statistic, block, item and entity names.

Default: true

*/ public boolean useTranslatableComponents() { return config.getBoolean("translate-to-client-language", true); } - /** Whether to use HoverComponents in the usage explanation. + /** Whether to use HoverComponents for additional information.

Default: true

*/ public boolean useHoverText() { return config.getBoolean("enable-hover-text", true); } + public String getDistanceUnit(boolean isHoverText) { + return getUnitString(isHoverText, "blocks", "km", "distance-unit"); + } + + public String getDamageUnit(boolean isHoverText) { + return getUnitString(isHoverText, "hearts", "hp", "damage-unit"); + } + + public boolean autoDetectTimeUnit(boolean isHoverText) { + String path = "auto-detect-biggest-time-unit"; + if (isHoverText) { + path = path + "-for-hover-text"; + } + boolean defaultValue = !isHoverText; + return config.getBoolean(path, defaultValue); + } + + public int getNumberOfExtraTimeUnits(boolean isHoverText) { + String path = "number-of-extra-units"; + if (isHoverText) { + path = path + "-for-hover-text"; + } + int defaultValue = isHoverText ? 0 : 1; + return config.getInt(path, defaultValue); + } + + /** By default, getTimeUnit will return the maxUnit. If the optional minUnit flag is specified, + the minimum unit will be returned instead. */ + public String getTimeUnit(boolean isHoverText) { + return getTimeUnit(isHoverText, false); + } + + /** By default, getTimeUnit will return the maxUnit. If the optional smallUnit flag is specified, + the minimum unit will be returned instead. */ + public String getTimeUnit(boolean isHoverText, boolean smallUnit) { + if (smallUnit) { + return getUnitString(isHoverText, "hours", "seconds", "smallest-time-unit"); + } + return getUnitString(isHoverText, "days", "hours", "biggest-time-unit"); + } + /** Whether to use festive formatting, such as pride colors.

Default: true

*/ public boolean useFestiveFormatting() { @@ -149,7 +191,7 @@ public class ConfigHandler {

Style: "none"

Color Top: "green"

Color Individual/Server: "gold"

*/ - public String getPlayerNameFormatting(Target selection, boolean isStyle) { + public String getPlayerNameDecoration(Target selection, boolean getStyle) { String def; if (selection == Target.TOP) { def = "green"; @@ -157,7 +199,7 @@ public class ConfigHandler { else { def = "gold"; } - return getStringFromConfig(selection, isStyle, def, "player-names"); + return getDecorationString(selection, getStyle, def, "player-names"); } /** Returns true if playerNames Style is "bold", false if it is not. @@ -175,22 +217,22 @@ public class ConfigHandler { /** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:

Style: "none"

Color: "yellow"

*/ - public String getStatNameFormatting(Target selection, boolean isStyle) { - return getStringFromConfig(selection, isStyle, "yellow", "stat-names"); + public String getStatNameDecoration(Target selection, boolean getStyle) { + return getDecorationString(selection, getStyle, "yellow", "stat-names"); } /** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:

Style: "none"

Color: "#FFD52B"

*/ - public String getSubStatNameFormatting(Target selection, boolean isStyle) { - return getStringFromConfig(selection, isStyle, "#FFD52B", "sub-stat-names"); + public String getSubStatNameDecoration(Target selection, boolean getStyle) { + return getDecorationString(selection, getStyle, "#FFD52B", "sub-stat-names"); } /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:

Style: "none"

Color Top: "#55AAFF"

Color Individual/Server: "#ADE7FF"

*/ - public String getStatNumberFormatting(Target selection, boolean isStyle) { + public String getStatNumberDecoration(Target selection, boolean getStyle) { String def; if (selection == Target.TOP) { def = "#55AAFF"; @@ -198,14 +240,14 @@ public class ConfigHandler { else { def = "#ADE7FF"; } - return getStringFromConfig(selection, isStyle, def,"stat-numbers"); + return getDecorationString(selection, getStyle, def,"stat-numbers"); } /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:

Style: "none"

Color Top: "yellow"

Color Server: "gold"

*/ - public String getTitleFormatting(Target selection, boolean isStyle) { + public String getTitleDecoration(Target selection, boolean getStyle) { String def; if (selection == Target.TOP) { def = "yellow"; @@ -213,41 +255,62 @@ public class ConfigHandler { else { def = "gold"; } - return getStringFromConfig(selection, isStyle, def, "title"); + return getDecorationString(selection, getStyle, def, "title"); } /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:

Style: "none"

Color: "gold"

*/ - public String getTitleNumberFormatting(boolean isStyle) { - return getStringFromConfig(Target.TOP, isStyle, "gold", "title-number"); + public String getTitleNumberDecoration(boolean getStyle) { + return getDecorationString(Target.TOP, getStyle, "gold", "title-number"); } /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:

Style: "none"

Color: "#FFB80E"

*/ - public String getServerNameFormatting(boolean isStyle) { - return getStringFromConfig(Target.SERVER, isStyle, "#FFB80E", "server-name"); + public String getServerNameDecoration(boolean getStyle) { + return getDecorationString(Target.SERVER, getStyle, "#FFB80E", "server-name"); } /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:

Style: "none"

Color: "gold"

*/ - public String getRankNumberFormatting(boolean isStyle) { - return getStringFromConfig(Target.TOP, isStyle, "gold", "rank-numbers"); + public String getRankNumberDecoration(boolean getStyle) { + return getDecorationString(Target.TOP, getStyle, "gold", "rank-numbers"); } /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:

Style: "none"

Color: "dark_gray"

*/ - public String getDotsFormatting(boolean isStyle) { - return getStringFromConfig(Target.TOP, isStyle, "dark_gray", "dots"); + public String getDotsDecoration(boolean getStyle) { + return getDecorationString(Target.TOP, getStyle, "dark_gray", "dots"); } - /** Returns the config value for a color or style option in string-format, the supplied default value, or null if no configSection was found. */ - private @Nullable String getStringFromConfig(Target selection, boolean isStyle, String def, String pathName){ - String path = isStyle ? pathName + "-style" : pathName; - String defaultValue = isStyle ? "none" : def; + /** Returns a String representing the Unit that should be used for a certain Unit.Type. + If no String can be retrieved from the config, the supplied defaultValue will be returned. + If the defaultValue is different for hoverText, an optional String defaultHoverValue can be supplied. + @param isHoverText if true, the unit for hovering text is returned, otherwise the unit for plain text + @param defaultValue the default unit for plain text + @param defaultHoverValue the default unit for hovering text + @param pathName the config path to retrieve the value from*/ + private String getUnitString(boolean isHoverText, String defaultValue, String defaultHoverValue, String pathName) { + String path = isHoverText ? pathName + "-for-hover-text" : pathName; + String def = defaultValue; + if (isHoverText && defaultHoverValue != null) { + def = defaultHoverValue; + } + return config.getString(path, def); + } + + /** Returns the config value for a color or style option in string-format, the supplied default value, + or null if no configSection was found. + @param selection the Target this decoration is meant for (Player, Server or Top) + @param getStyle if true, the result will be a style String, otherwise a color String + @param defaultColor the default color to return if the config value cannot be found (for style, the default is always "none") + @param pathName the config path to retrieve the value from*/ + private @Nullable String getDecorationString(Target selection, boolean getStyle, String defaultColor, String pathName){ + String path = getStyle ? pathName + "-style" : pathName; + String defaultValue = getStyle ? "none" : defaultColor; ConfigurationSection section = getRelevantSection(selection); return section != null ? section.getString(path, defaultValue) : null; diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/enums/Unit.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/enums/Unit.java new file mode 100644 index 0000000..8c7de2f --- /dev/null +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/enums/Unit.java @@ -0,0 +1,199 @@ +package com.gmail.artemis.the.gr8.playerstats.enums; + +import org.bukkit.Statistic; +import org.jetbrains.annotations.NotNull; + + +public enum Unit { + NUMBER (Type.UNTYPED, "Times"), + KM (Type.DISTANCE, "km"), + MILE (Type.DISTANCE, "Miles"), + BLOCK (Type.DISTANCE, "Blocks"), + CM (Type.DISTANCE, "cm"), + HP (Type.DAMAGE, "HP"), + HEART (Type.DAMAGE, "Hearts"), + DAY (Type.TIME, "days"), + HOUR (Type.TIME, "hours"), + MINUTE (Type.TIME, "minutes"), + SECOND (Type.TIME, "seconds"), + TICK (Type.TIME, "ticks"); + + private final Type type; + private final String label; + + Unit(Type type, String label) { + this.type = type; + this.label = label; + } + + /** Returns a pretty name belonging to this enum constant. If the Unit is + NUMBER, it will return null. */ + public String getLabel() { + return this.label; + } + + public Type getType() { + return this.type; + } + + public Unit getSmallerUnit(int stepsSmaller) { + switch (this) { + case DAY -> { + if (stepsSmaller >= 3) { + return Unit.SECOND; + } else if (stepsSmaller == 2) { + return Unit.MINUTE; + } else if (stepsSmaller == 1) { + return Unit.HOUR; + } else { + return this; + } + } + case HOUR -> { + if (stepsSmaller >= 2) { + return Unit.SECOND; + } else if (stepsSmaller == 1) { + return Unit.MINUTE; + } else { + return this; + } + } + case MINUTE -> { + if (stepsSmaller >= 1) { + return Unit.SECOND; + } else { + return this; + } + } + case KM -> { + if (stepsSmaller >= 2) { + return Unit.CM; + } else if (stepsSmaller == 1) { + return Unit.BLOCK; + } else { + return this; + } + } + case BLOCK -> { + if (stepsSmaller >= 1) { + return Unit.CM; + } else { + return this; + } + } + case HEART -> { + if (stepsSmaller >= 1) { + return Unit.HP; + } else { + return this; + } + } + default -> { + return this; + } + } + } + + public double getSeconds() { + switch (this) { + case DAY -> { + return 86400; + } + case HOUR -> { + return 3600; + } + case MINUTE -> { + return 60; + } + case SECOND -> { + return 1; + } + case TICK -> { + return 1 / 20.0; + } + default -> { + return -1; + } + } + } + + /** Returns the Unit corresponding to the given String. This String does NOT need to + match exactly (it can be "day" or "days", for example), and is case-insensitive. + @param unitName an approximation of the name belonging to the desired Unit, case-insensitive */ + public static @NotNull Unit fromString(@NotNull String unitName) { + Unit unit; + switch (unitName.toLowerCase()) { + case "cm" -> unit = Unit.CM; + case "m", "block", "blocks" -> unit = Unit.BLOCK; + case "mile", "miles" -> unit = Unit.MILE; + case "km" -> unit = Unit.KM; + case "hp" -> unit = Unit.HP; + case "heart", "hearts" -> unit = Unit.HEART; + case "day", "days" -> unit = Unit.DAY; + case "hour", "hours" -> unit = Unit.HOUR; + case "minute", "minutes", "min" -> unit = Unit.MINUTE; + case "second", "seconds", "sec" -> unit = Unit.SECOND; + case "tick", "ticks" -> unit = Unit.TICK; + default -> unit = Unit.NUMBER; + } + return unit; + } + + /** Returns the Unit.Type of this Statistic, which can be Untyped, Distance, Damage, or Time. + @param statistic the Statistic enum constant*/ + public static @NotNull Type getTypeFromStatistic(Statistic statistic) { + String name = statistic.toString().toLowerCase(); + if (name.contains("one_cm")) { + return Type.DISTANCE; + } else if (name.contains("damage")) { + return Type.DAMAGE; + } else if (name.contains("time") || name.contains("one_minute")) { + return Type.TIME; + } else { + return Type.UNTYPED; + } + } + + /** Returns the most suitable timeUnit for this number. + @param type the Unit.Type of the statistic this number belongs to + @param number the statistic number as returned by Player.getStatistic()*/ + public static Unit getMostSuitableUnit(Unit.Type type, long number) { + switch (type) { + case TIME -> { + long statNumber = number / 20; + if (statNumber >= 86400) { + return Unit.DAY; + } else if (statNumber >= 3600) { + return Unit.HOUR; + } else if (statNumber >= 60) { + return Unit.MINUTE; + } else { + return Unit.SECOND; + } + } + case DISTANCE -> { + if (number >= 100000) { + return Unit.KM; + } else { + return Unit.BLOCK; + } + } + case DAMAGE -> { + return Unit.HEART; + } + default -> { + return Unit.NUMBER; + } + } + } + + public enum Type{ + DAMAGE, //7 statistics + DISTANCE, //15 statistics + TIME, //5 statistics + UNTYPED; + + Type() { + } + } +} 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 index ccc2dc0..1e01f97 100644 --- 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 @@ -3,9 +3,6 @@ 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; @@ -13,6 +10,7 @@ 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.HSVLike; import net.kyori.adventure.util.Index; import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; @@ -32,219 +30,116 @@ public class ComponentFactory { config = c; } - /** Returns [PlayerStats] followed by a single space. */ - public TextComponent pluginPrefix(boolean isBukkitConsole) { + /** Returns [PlayerStats]. */ + public TextComponent pluginPrefixComponent(boolean isBukkitConsole) { return text("[") .color(PluginColor.GRAY.getColor()) .append(text("PlayerStats").color(PluginColor.GOLD.getColor())) - .append(text("]")) - .append(space()); + .append(text("]")); } /** Returns [PlayerStats] surrounded by underscores on both sides. */ - public TextComponent prefixTitle(boolean isBukkitConsole) { + public TextComponent prefixTitleComponent(boolean isBukkitConsole) { String underscores = "____________"; //12 underscores for both console and in-game TextColor underscoreColor = isBukkitConsole ? PluginColor.DARK_PURPLE.getConsoleColor() : PluginColor.DARK_PURPLE.getColor(); return text(underscores).color(underscoreColor) .append(text(" ")) //4 spaces - .append(pluginPrefix(isBukkitConsole)) - .append(text(" ")) //3 spaces (since prefix already has one) + .append(pluginPrefixComponent(isBukkitConsole)) + .append(text(" ")) //4 spaces .append(text(underscores)); } /** Returns a TextComponent with the input String as content, with color Gray and decoration Italic.*/ - public TextComponent subTitle(String content) { + public TextComponent subTitleComponent(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 isBukkitConsole) { - return pluginPrefix(isBukkitConsole) - .append(text(msg) - .color(PluginColor.MEDIUM_BLUE.getColor())); + public TextComponent messageComponent() { + return text().color(PluginColor.MEDIUM_BLUE.getColor()).build(); } - /** Returns a plain TextComponent that represents a single message line. - A space will be inserted after part1, part2 and part3. - 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) { - return msgPart(part1, part2, part3, part4, false); + public TextComponent.Builder playerNameBuilder(String playerName, Target selection) { + return getComponentBuilder(playerName, + getColorFromString(config.getPlayerNameDecoration(selection, false)), + getStyleFromString(config.getPlayerNameDecoration(selection, true))); } - /** Returns a plain TextComponent that represents a single message line. - A space will be inserted after part1, part2 and part3. - Each message part has its own designated color. - if isBukkitConsole is true, the colors will be the nearest ChatColor to the below colors. - @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, boolean isBukkitConsole) { - TextComponent.Builder msg = Component.text(); - if (part1 != null) { - TextColor pluginColor = isBukkitConsole ? PluginColor.GOLD.getConsoleColor() : PluginColor.GOLD.getColor(); - msg.append(text(part1) - .color(pluginColor)) - .append(space()); + /** @param prettyStatName a statName with underscores removed and each word capitalized + @param prettySubStatName if present, a subStatName with underscores removed and each word capitalized*/ + public TextComponent statNameTextComponent(String prettyStatName, @Nullable String prettySubStatName, Target selection) { + TextComponent.Builder totalStatNameBuilder = getComponentBuilder(prettyStatName, + getColorFromString(config.getStatNameDecoration(selection, false)), + getStyleFromString(config.getStatNameDecoration(selection, true))); + TextComponent subStat = subStatNameTextComponent(prettySubStatName, selection); + + if (!subStat.equals(Component.empty())) { + totalStatNameBuilder + .append(space().decorations(TextDecoration.NAMES.values(), false)) + .append(subStatNameTextComponent(prettySubStatName, selection)); } - if (part2 != null) { - TextColor pluginColor = isBukkitConsole ? PluginColor.MEDIUM_GOLD.getConsoleColor() : PluginColor.MEDIUM_GOLD.getColor(); - msg.append(text(part2) - .color(pluginColor)) - .append(space()); - } - if (part3 != null) { - TextColor pluginColor = isBukkitConsole ? PluginColor.LIGHT_GOLD.getConsoleColor() : PluginColor.LIGHT_GOLD.getColor(); - msg.append(text(part3) - .color(pluginColor)) - .append(space()); - } - if (part4 != null) { - TextColor pluginColor = isBukkitConsole ? PluginColor.GRAY.getConsoleColor() : PluginColor.GRAY.getColor(); - msg.append(text(part4) - .color(pluginColor)); - } - return msg.build(); + return totalStatNameBuilder.build(); } - /** Returns a TextComponent with a single line of hover-text in the specified color. - @param plainText the base message - @param hoverText the hovering text - @param hoverColor color of the hovering text */ - public TextComponent simpleHoverPart(String plainText, String hoverText, PluginColor hoverColor) { - return simpleHoverPart(plainText, null, hoverText, hoverColor); + /** Returns a TextComponent for the subStatName, or an empty component.*/ + private TextComponent subStatNameTextComponent(@Nullable String prettySubStatName, Target selection) { + if (prettySubStatName == null) { + return Component.empty(); + } else { + return getComponentBuilder(null, + getColorFromString(config.getSubStatNameDecoration(selection, false)), + getStyleFromString(config.getSubStatNameDecoration(selection, true))) + .append(text("(")) + .append(text(prettySubStatName)) + .append(text(")")) + .build(); + } } - /** Returns a TextComponent with a single line of hover-text in the specified color. - If a PluginColor is provided for the plainText, the base color is set as well. - @param plainText the base message - @param plainColor color of the base message - @param hoverText the hovering text - @param hoverColor color of the hovering text */ - public TextComponent simpleHoverPart(String plainText, @Nullable PluginColor plainColor, String hoverText, PluginColor hoverColor) { - TextComponent.Builder msg = Component.text() - .append(text(plainText)) - .hoverEvent(HoverEvent.showText( - text(hoverText) - .color(hoverColor.getColor()))); - if (plainColor != null) { - msg.color(plainColor.getColor()); - } - return msg.build(); - } + /** Returns a TextComponent with TranslatableComponent as a child.*/ + public TextComponent statNameTransComponent(String statKey, String subStatKey, Target selection) { + TextComponent.Builder totalStatNameBuilder = getComponentBuilder(null, + getColorFromString(config.getStatNameDecoration(selection, false)), + getStyleFromString(config.getStatNameDecoration(selection, true))); - /** 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 GOLD - @param hoverLineTwoB text on the second part of the second line, with color LIGHT_GOLD - */ - public TextComponent complexHoverPart(@NotNull String plainText, @NotNull PluginColor color, String hoverLineOne, String hoverLineTwoA, 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()); - } + TextComponent subStat = subStatNameTransComponent(subStatKey, selection); + if (statKey.equalsIgnoreCase("stat_type.minecraft.killed")) { + return totalStatNameBuilder.append(killEntityBuilder(subStat)).build(); } - if (hoverLineTwoA != null) { - hoverText.append(text(hoverLineTwoA) - .color(PluginColor.GOLD.getColor())); - if (hoverLineTwoB != null) { - hoverText.append(space()); - } - } - if (hoverLineTwoB != null) { - hoverText.append(text(hoverLineTwoB).color(PluginColor.LIGHT_GOLD.getColor())); - } - return base.hoverEvent(HoverEvent.showText(hoverText.build())); - } - - - public TextComponent playerName(String playerName, Target selection) { - 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 if (statKey.equalsIgnoreCase("stat_type.minecraft.killed_by")) { + return totalStatNameBuilder.append(entityKilledByBuilder(subStat)).build(); } else { - statName = LanguageKeyHandler.getStatKey(request.getStatistic()); - switch (request.getStatistic().getType()) { - case BLOCK -> subStatName = LanguageKeyHandler.getBlockKey(request.getBlock()); - case ENTITY -> subStatName = LanguageKeyHandler.getEntityKey(request.getEntity()); - case ITEM -> subStatName = LanguageKeyHandler.getItemKey(request.getItem()); - case UNTYPED -> { - } + totalStatNameBuilder.append(translatable().key(statKey)); + if (!subStat.equals(Component.empty())) { + totalStatNameBuilder.append( + space().decorations(TextDecoration.NAMES.values(), false) + .append(subStat)); } + return totalStatNameBuilder.build(); } - return statName(statName, subStatName, request.getSelection()); } - private TranslatableComponent statName(@NotNull String statKey, String subStatKey, @NotNull Target selection) { - TranslatableComponent.Builder totalName; - TextComponent subStat = subStatName(subStatKey, selection); - 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(@Nullable String subStatName, Target selection) { - if (subStatName != null) { - TextDecoration style = getStyleFromString(config.getSubStatNameFormatting(selection, true)); - TextComponent.Builder subStat = text() + /** Returns a TranslatableComponent for the subStatName, or an empty component.*/ + private TextComponent subStatNameTransComponent(String subStatKey, Target selection) { + if (subStatKey != null) { + return getComponentBuilder(null, + getColorFromString(config.getSubStatNameDecoration(selection, false)), + getStyleFromString(config.getSubStatNameDecoration(selection, true))) .append(text("(")) .append(translatable() - .key(subStatName)) + .key(subStatKey)) .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; + .build(); } + return Component.empty(); } /** 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) { + private TranslatableComponent.Builder killEntityBuilder(@NotNull TextComponent subStat) { return translatable() .key("commands.kill.success.single") //"Killed %s" .args(subStat); @@ -254,7 +149,7 @@ public class ComponentFactory { ("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) { + private TranslatableComponent.Builder entityKilledByBuilder(@NotNull TextComponent subStat) { return translatable() .key("stat.minecraft.deaths") //"Number of Deaths" .append(space()) @@ -263,62 +158,96 @@ public class ComponentFactory { .args(subStat)); } - public TextComponent statNumber(long number, Target selection) { - return createComponent(NumberFormatter.format(number), - getColorFromString(config.getStatNumberFormatting(selection, false)), - getStyleFromString(config.getStatNumberFormatting(selection, true))); + //TODO Add hoverComponent with full number + public TextComponent.Builder statNumberBuilder(String prettyNumber, Target selection) { + return getComponentBuilder(prettyNumber, + getColorFromString(config.getStatNumberDecoration(selection, false)), + getStyleFromString(config.getStatNumberDecoration(selection, true))); } - public TextComponent title(String content, Target selection) { - return createComponent(content, - getColorFromString(config.getTitleFormatting(selection, false)), - getStyleFromString(config.getTitleFormatting(selection, true))); + public TextComponent statNumberHoverComponent(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, Target selection) { + TextColor baseColor = getColorFromString(config.getStatNumberDecoration(selection, false)); + TextDecoration style = getStyleFromString(config.getStatNumberDecoration(selection, true)); + + TextComponent.Builder hoverText = getComponentBuilder(hoverNumber, getLighterColor(baseColor), style); + if (hoverUnitKey != null) { + hoverText.append(space()) + .append(translatable().key(hoverUnitKey)); + } + else if (hoverUnitName != null) { + hoverText.append(space()) + .append(text(hoverUnitName)); + } + return getComponent(mainNumber, baseColor, style).hoverEvent(HoverEvent.showText(hoverText)); } - public TextComponent titleNumber(int number) { - return createComponent(number + "", - getColorFromString(config.getTitleNumberFormatting(false)), - getStyleFromString(config.getTitleNumberFormatting(true))); + //TODO Make this dark gray (or at least darker than statNumber, and at least for time statistics) + public TextComponent statUnitComponent(String unitName, String unitKey, Target selection) { + if (!(unitName == null && unitKey == null)) { + TextComponent.Builder statUnitBuilder = getComponentBuilder(null, + getColorFromString(config.getSubStatNameDecoration(selection, false)), + getStyleFromString(config.getSubStatNameDecoration(selection, true))) + .append(text("[")); + if (unitKey != null) { + statUnitBuilder.append(translatable() + .key(unitKey)); + } else { + statUnitBuilder.append(text(unitName)); + } + return statUnitBuilder.append(text("]")).build(); + } + else { + return Component.empty(); + } } - public TextComponent serverName(String serverName) { - TextComponent colon = text(":").color(getColorFromString(config.getServerNameFormatting(false))); - return createComponent(serverName, - getColorFromString(config.getServerNameFormatting(false)), - getStyleFromString(config.getServerNameFormatting(true))) + public TextComponent titleComponent(String content, Target selection) { + return getComponent(content, + getColorFromString(config.getTitleDecoration(selection, false)), + getStyleFromString(config.getTitleDecoration(selection, true))); + } + + public TextComponent titleNumberComponent(int number) { + return getComponent(number + "", + getColorFromString(config.getTitleNumberDecoration(false)), + getStyleFromString(config.getTitleNumberDecoration(true))); + } + + public TextComponent serverNameComponent(String serverName) { + TextComponent colon = text(":").color(getColorFromString(config.getServerNameDecoration(false))); + return getComponent(serverName, + getColorFromString(config.getServerNameDecoration(false)), + getStyleFromString(config.getServerNameDecoration(true))) .append(colon); } - public TextComponent rankingNumber(String number) { - return createComponent(number, - getColorFromString(config.getRankNumberFormatting(false)), - getStyleFromString(config.getRankNumberFormatting(true))); + public TextComponent rankingNumberComponent(String number) { + return getComponent(number, + getColorFromString(config.getRankNumberDecoration(false)), + getStyleFromString(config.getRankNumberDecoration(true))); } - public TextComponent dots(String dots) { - return createComponent(dots, - getColorFromString(config.getDotsFormatting(false)), - getStyleFromString(config.getDotsFormatting(true))); + public TextComponent.Builder dotsBuilder() { + return getComponentBuilder(null, + getColorFromString(config.getDotsDecoration(false)), + getStyleFromString(config.getDotsDecoration(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); + private TextComponent getComponent(String content, TextColor color, @Nullable TextDecoration style) { + return getComponentBuilder(content, color, style).build(); } - /** 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, ' '); + private TextComponent.Builder getComponentBuilder(@Nullable String content, TextColor color, @Nullable TextDecoration style) { + TextComponent.Builder builder = text() + .decorations(TextDecoration.NAMES.values(), false) + .color(color); + if (content != null) { + builder.append(text(content)); } - return capitals.toString(); + if (style != null) { + builder.decorate(style); + } + return builder; } private TextColor getColorFromString(String configString) { @@ -343,6 +272,12 @@ public class ComponentFactory { return names.value(textColor); } + private TextColor getLighterColor(TextColor color) { + HSVLike oldColor = HSVLike.fromRGB(color.red(), color.green(), color.blue()); + HSVLike newColor = HSVLike.hsvLike(oldColor.h(), 0.45F, oldColor.v()); + return TextColor.color(newColor); + } + private @Nullable TextDecoration getStyleFromString(@NotNull String configString) { if (configString.equalsIgnoreCase("none")) { return null; @@ -355,5 +290,4 @@ public class ComponentFactory { return styles.value(configString); } } - } \ 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 index 00270a4..f77dee9 100644 --- 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 @@ -1,13 +1,16 @@ 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.DebugLevel; 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.enums.Unit; +import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.ExampleMessage; +import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.HelpMessage; +import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler; import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest; +import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger; 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.TextColor; import org.bukkit.Bukkit; import org.bukkit.Statistic; import org.bukkit.map.MinecraftFont; @@ -24,9 +27,13 @@ public class MessageWriter { private static ConfigHandler config; private static ComponentFactory componentFactory; + private final LanguageKeyHandler languageKeyHandler; + private final NumberFormatter formatter; public MessageWriter(ConfigHandler c) { config = c; + formatter = new NumberFormatter(); + languageKeyHandler = new LanguageKeyHandler(); getComponentFactory(); } @@ -44,124 +51,266 @@ public class MessageWriter { } public TextComponent reloadedConfig(boolean isBukkitConsole) { - return componentFactory.msg( - "Config reloaded!", isBukkitConsole); + return componentFactory.pluginPrefixComponent(isBukkitConsole) + .append(space()) + .append(componentFactory.messageComponent().content("Config reloaded!")); } public TextComponent stillReloading(boolean isBukkitConsole) { - return componentFactory.msg( - "The plugin is (re)loading, " + - "your request will be processed when it is done!", isBukkitConsole); + return componentFactory.pluginPrefixComponent(isBukkitConsole) + .append(space()) + .append(componentFactory.messageComponent().content( + "The plugin is (re)loading, your request will be processed when it is done!")); } public TextComponent waitAMoment(boolean longWait, boolean isBukkitConsole) { String msg = longWait ? "Calculating statistics, this may take a minute..." : "Calculating statistics, this may take a few moments..."; - return componentFactory.msg(msg, isBukkitConsole); + return componentFactory.pluginPrefixComponent(isBukkitConsole) + .append(space()) + .append(componentFactory.messageComponent().content(msg)); } public TextComponent missingStatName(boolean isBukkitConsole) { - return componentFactory.msg( - "Please provide a valid statistic name!", isBukkitConsole); + return componentFactory.pluginPrefixComponent(isBukkitConsole) + .append(space()) + .append(componentFactory.messageComponent().content( + "Please provide a valid statistic name!")); } public TextComponent missingSubStatName(Statistic.Type statType, boolean isBukkitConsole) { - return componentFactory.msg( - "Please add a valid " + - getSubStatTypeName(statType) + - " to look up this statistic!", isBukkitConsole); + return componentFactory.pluginPrefixComponent(isBukkitConsole) + .append(space()) + .append(componentFactory.messageComponent().content( + "Please add a valid " + getSubStatTypeName(statType) + " to look up this statistic!")); } public TextComponent missingPlayerName(boolean isBukkitConsole) { - return componentFactory.msg( - "Please specify a valid player-name!", isBukkitConsole); + return componentFactory.pluginPrefixComponent(isBukkitConsole) + .append(space()) + .append(componentFactory.messageComponent().content( + "Please specify a valid player-name!")); } public TextComponent wrongSubStatType(Statistic.Type statType, String subStatEntry, boolean isBukkitConsole) { - return componentFactory.msg( - "\"" + subStatEntry + "\" is not a valid " + getSubStatTypeName(statType) + "!", isBukkitConsole); + return componentFactory.pluginPrefixComponent(isBukkitConsole) + .append(space()) + .append(componentFactory.messageComponent().content( + "\"" + subStatEntry + "\" is not a valid " + getSubStatTypeName(statType) + "!")); } public TextComponent unknownError(boolean isBukkitConsole) { - return componentFactory.msg( + return componentFactory.pluginPrefixComponent(isBukkitConsole) + .append(space()) + .append(componentFactory.messageComponent().content( "Something went wrong with your request, " + - "please try again or see /statistic for a usage explanation!", isBukkitConsole); + "please try again or see /statistic for a usage explanation!")); } - public TextComponent formatPlayerStat(int stat, @NotNull StatRequest request) { - if (!request.isValid()) return unknownError(request.isBukkitConsoleSender()); return Component.text() - .append(componentFactory.playerName( request.getPlayerName() + ": ", Target.PLAYER)) - .append(componentFactory.statNumber(stat, Target.PLAYER)) - .append(space()) - .append(componentFactory.statName(request)) + .append(componentFactory.playerNameBuilder(request.getPlayerName(), Target.PLAYER) + .append(text(":")) + .append(space())) + .append(getStatNumberComponent(request.getStatistic(), stat, Target.PLAYER)) .append(space()) + .append(getStatNameComponent(request)) + .append(getStatUnitComponent(request.getStatistic(), request.getSelection())) .build(); } public TextComponent formatTopStats(@NotNull LinkedHashMap topStats, @NotNull StatRequest request) { - if (!request.isValid()) return unknownError(request.isBukkitConsoleSender()); - TextComponent.Builder topList = Component.text() .append(newline()) - .append(componentFactory.pluginPrefix(request.isBukkitConsoleSender())) - .append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)) - .append(space()) - .append(componentFactory.titleNumber(topStats.size())) - .append(space()) - .append(componentFactory.statName(request)); + .append(componentFactory.pluginPrefixComponent(request.isBukkitConsoleSender())).append(space()) + .append(componentFactory.titleComponent(config.getTopStatsTitle(), Target.TOP)).append(space()) + .append(componentFactory.titleNumberComponent(topStats.size())).append(space()) + .append(getStatNameComponent(request)) + .append(getStatUnitComponent(request.getStatistic(), request.getSelection())); + ArrayList timeUnits = null; + if (Unit.getTypeFromStatistic(request.getStatistic()) == Unit.Type.TIME) { + timeUnits = getTimeUnitRange(topStats.values().iterator().next()); + } boolean useDots = config.useDots(); boolean boldNames = config.playerNameIsBold(); - Set playerNames = topStats.keySet(); MinecraftFont font = new MinecraftFont(); + Set playerNames = topStats.keySet(); int count = 0; for (String playerName : playerNames) { - count = count+1; - + TextComponent.Builder playerNameBuilder = componentFactory.playerNameBuilder(playerName, Target.TOP); + count++; topList.append(newline()) - .append(componentFactory.rankingNumber(count + ". ")) - .append(componentFactory.playerName(playerName, Target.TOP)); - + .append(componentFactory.rankingNumberComponent(count + ". ")) + .append(playerNameBuilder); if (useDots) { topList.append(space()); - - int dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/2); + TextComponent.Builder dotsBuilder = componentFactory.dotsBuilder(); + int dots; if (request.isConsoleSender()) { dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/6) + 7; - } - else if (boldNames) { + } else if (!boldNames) { + dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/2); + } else { dots = (int) Math.round((130.0 - font.getWidth(count + ". ") - (font.getWidth(playerName) * 1.19))/2); } if (dots >= 1) { - topList.append(componentFactory.dots(".".repeat(dots))); + topList.append(dotsBuilder.append(text((".".repeat(dots))))); } + } else { + topList.append(playerNameBuilder.append(text(":"))); } - else { - topList.append(componentFactory.playerName(":", Target.TOP)); + if (timeUnits != null) { + topList.append(space()).append(getTimeNumberComponent(topStats.get(playerName), request.getSelection(), timeUnits)); + } else { + topList.append(space()).append(getStatNumberComponent(request.getStatistic(), topStats.get(playerName), Target.TOP)); } - topList.append(space()).append(componentFactory.statNumber(topStats.get(playerName), Target.TOP)); } return topList.build(); } public TextComponent formatServerStat(long stat, @NotNull StatRequest request) { - if (!request.isValid()) return unknownError(request.isBukkitConsoleSender()); return Component.text() - .append(componentFactory.title(config.getServerTitle(), Target.SERVER)) + .append(componentFactory.titleComponent(config.getServerTitle(), Target.SERVER)) .append(space()) - .append(componentFactory.serverName(config.getServerName())) + .append(componentFactory.serverNameComponent(config.getServerName())) .append(space()) - .append(componentFactory.statNumber(stat, Target.SERVER)) - .append(space()) - .append(componentFactory.statName(request)) + .append(getStatNumberComponent(request.getStatistic(), stat, Target.SERVER)) .append(space()) + .append(getStatNameComponent(request)) + .append(getStatUnitComponent(request.getStatistic(), request.getSelection())) //space is provided by statUnit .build(); } + /** Depending on the config settings, return either a TranslatableComponent representing + the statName (and potential subStatName), or a TextComponent with capitalized English names.*/ + private TextComponent getStatNameComponent(StatRequest request) { + if (config.useTranslatableComponents()) { + String statKey = languageKeyHandler.getStatKey(request.getStatistic()); + String subStatKey = request.getSubStatEntry(); + if (subStatKey != null) { + switch (request.getStatistic().getType()) { + case BLOCK -> subStatKey = languageKeyHandler.getBlockKey(request.getBlock()); + case ENTITY -> subStatKey = languageKeyHandler.getEntityKey(request.getEntity()); + case ITEM -> subStatKey = languageKeyHandler.getItemKey(request.getItem()); + default -> { + } + } + } + return componentFactory.statNameTransComponent(statKey, subStatKey, request.getSelection()); + } + else { + return componentFactory.statNameTextComponent( + getPrettyName(request.getStatistic().toString()), + getPrettyName(request.getSubStatEntry()), + request.getSelection()); + } + } + + private TextComponent getStatNumberComponent(Statistic statistic, long statNumber, Target selection) { + Unit.Type type = Unit.getTypeFromStatistic(statistic); + Unit statUnit; + switch (type) { + case DISTANCE -> statUnit = Unit.fromString(config.getDistanceUnit(false)); + case DAMAGE -> statUnit = Unit.fromString(config.getDamageUnit(false)); + case TIME -> { + return getTimeNumberComponent(statNumber, selection, getTimeUnitRange(statNumber)); + } + default -> statUnit = Unit.NUMBER; + } + String prettyNumber = formatter.format(statNumber, statUnit); + if (!config.useHoverText() || statUnit == Unit.NUMBER) { + return componentFactory.statNumberBuilder(prettyNumber, selection).build(); + } + Unit hoverUnit = type == Unit.Type.DISTANCE ? Unit.fromString(config.getDistanceUnit(true)) : + Unit.fromString(config.getDamageUnit(true)); + String prettyHoverNumber = formatter.format(statNumber, hoverUnit); + MyLogger.logMsg("mainNumber: " + prettyNumber + "\n" + "hoverNumber: " + prettyHoverNumber, DebugLevel.HIGH); + if (config.useTranslatableComponents()) { + String unitKey = languageKeyHandler.getUnitKey(hoverUnit); + if (unitKey == null) { + unitKey = hoverUnit.getLabel(); + } + return componentFactory.statNumberHoverComponent(prettyNumber, prettyHoverNumber, null, unitKey, selection); + } + else { + return componentFactory.statNumberHoverComponent(prettyNumber, prettyHoverNumber, hoverUnit.getLabel(), null, selection); + } + } + + private TextComponent getTimeNumberComponent(long statNumber, Target selection, ArrayList unitRange) { + if (unitRange.size() <= 1 || (config.useHoverText() && unitRange.size() <= 3)) { + MyLogger.logMsg( + "There is something wrong with the time-units you specified, please check your config!", + true); + return componentFactory.statNumberBuilder("-", selection).build(); + } + else { + String mainNumber = formatter.format(statNumber, unitRange.get(0), unitRange.get(1)); + if (!config.useHoverText()) { + return componentFactory.statNumberBuilder(mainNumber, selection).build(); + } else { + String hoverNumber = formatter.format(statNumber, unitRange.get(2), unitRange.get(3)); + MyLogger.logMsg("mainNumber: " + mainNumber + ", hoverNumber: " + hoverNumber, DebugLevel.HIGH); + return componentFactory.statNumberHoverComponent(mainNumber, hoverNumber, + null, null, selection); //Time does not support translatable text, + } //because the unit and number are so tightly interwoven. + } + } + + /** Get an ArrayList consisting of 2 or 4 timeUnits. The order of items is: +

0. maxUnit

+

1. minUnit

+

2. maxHoverUnit

+

3. minHoverUnit

*/ + private ArrayList getTimeUnitRange(long statNumber) { + ArrayList unitRange = new ArrayList<>(); + if (!config.autoDetectTimeUnit(false)) { + unitRange.add(Unit.fromString(config.getTimeUnit(false))); + unitRange.add(Unit.fromString(config.getTimeUnit(false, true))); + } + else { + Unit bigUnit = Unit.getMostSuitableUnit(Unit.Type.TIME, statNumber); + unitRange.add(bigUnit); + unitRange.add(bigUnit.getSmallerUnit(config.getNumberOfExtraTimeUnits(false))); + } + if (config.useHoverText()) { + if (!config.autoDetectTimeUnit(true)) { + unitRange.add(Unit.fromString(config.getTimeUnit(true))); + unitRange.add(Unit.fromString(config.getTimeUnit(true, true))); + } + else { + Unit bigHoverUnit = Unit.getMostSuitableUnit(Unit.Type.TIME, statNumber); + unitRange.add(bigHoverUnit); + unitRange.add(bigHoverUnit.getSmallerUnit(config.getNumberOfExtraTimeUnits(true))); + } + } + MyLogger.logMsg("total selected unitRange for this statistic: " + unitRange, DebugLevel.MEDIUM); + return unitRange; + } + + private TextComponent getStatUnitComponent(Statistic statistic, Target selection) { + Unit statUnit; + switch (Unit.getTypeFromStatistic(statistic)) { + case DAMAGE -> statUnit = Unit.fromString(config.getDamageUnit(false)); + case DISTANCE -> statUnit = Unit.fromString(config.getDistanceUnit(false)); + default -> { + return Component.empty(); + } + } + if (config.useTranslatableComponents()) { + String unitKey = languageKeyHandler.getUnitKey(statUnit); + if (unitKey != null) { + return Component.space() + .append(componentFactory.statUnitComponent(null, unitKey, selection)); + } + } + return Component.space() + .append(componentFactory.statUnitComponent(statUnit.getLabel(), null, selection)); + } + /** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */ private String getSubStatTypeName(Statistic.Type statType) { String subStat = "sub-statistic"; @@ -174,120 +323,30 @@ public class MessageWriter { return subStat; } - public TextComponent usageExamples(boolean isBukkitConsole) { - TextColor mainColor = isBukkitConsole ? PluginColor.GOLD.getConsoleColor() : PluginColor.GOLD.getColor(); - TextColor accentColor1 = isBukkitConsole ? PluginColor.MEDIUM_GOLD.getConsoleColor() : PluginColor.MEDIUM_GOLD.getColor(); - TextColor accentColor3 = isBukkitConsole ? PluginColor.LIGHT_YELLOW.getConsoleColor() : PluginColor.LIGHT_YELLOW.getColor(); - String arrow = isBukkitConsole ? " -> " : " → "; //4 spaces, alt + 26, 1 space + /** 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(); - return Component.newline() - .append(componentFactory.prefixTitle(isBukkitConsole)) - .append(newline()) - .append(text("Examples: ").color(mainColor)) - .append(newline()) - .append(text(arrow).color(mainColor) - .append(text("/statistic ") - .append(text("animals_bred ").color(accentColor1) - .append(text("top").color(accentColor3))))) - .append(newline()) - .append(text(arrow).color(mainColor) - .append(text("/statistic ") - .append(text("mine_block diorite ").color(accentColor1) - .append(text("me").color(accentColor3))))) - .append(newline()) - .append(text(arrow).color(mainColor) - .append(text("/statistic ") - .append(text("deaths ").color(accentColor1) - .append(text("player ").color(accentColor3) - .append(text("Artemis_the_gr8")))))); + int index = capitals.indexOf("_"); + capitals.setCharAt(index + 1, Character.toUpperCase(capitals.charAt(index + 1))); + capitals.setCharAt(index, ' '); + } + return capitals.toString(); + } + + public TextComponent usageExamples(boolean isBukkitConsole) { + return new ExampleMessage(componentFactory, isBukkitConsole); } public TextComponent helpMsg(boolean isConsoleSender) { - if (isConsoleSender || !config.useHoverText()) { - return helpMsgPlain(isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")); - } - else { - return helpMsgHover(); - } - } - - /** Returns the usage-explanation with hovering text */ - private TextComponent helpMsgHover() { - String arrow = " →"; //4 spaces, alt + 26 - 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.complexHoverPart("name", PluginColor.LIGHT_GOLD, - "The name that describes the statistic", - "Example:", - "\"animals_bred\""))) - .append(newline()) - .append(componentFactory.msgPart(arrow, null, null, null) - .append(componentFactory.complexHoverPart("sub-statistic", PluginColor.LIGHT_GOLD, - "Some statistics need an item, block or entity as extra input", - "Example:", - "\"mine_block diorite\""))) - .append(newline()) - .append(text(" ").color(PluginColor.LIGHT_GOLD.getColor()) - .append(componentFactory.simpleHoverPart( - "→", PluginColor.GOLD, - "Choose one", PluginColor.DARK_PURPLE)) - .append(space()) - .append(componentFactory.simpleHoverPart( - "me", - "See your own statistic", PluginColor.LIGHT_BLUE)) - .append(text(" | ")) - .append(componentFactory.simpleHoverPart( - "player", - "Choose any player that has played on your server", PluginColor.LIGHT_BLUE)) - .append(text(" | ")) - .append(componentFactory.simpleHoverPart( - "server", - "See the combined total for everyone on your server", PluginColor.LIGHT_BLUE)) - .append(text(" | ")) - .append(componentFactory.simpleHoverPart( - "top", - "See the top " + config.getTopListMaxSize(), PluginColor.LIGHT_BLUE))) - .append(newline()) - .append(componentFactory.msgPart(arrow, null, null, null) - .append(text("player-name").color(PluginColor.LIGHT_GOLD.getColor()) - .hoverEvent(HoverEvent.showText( - text("In case you typed ").color(PluginColor.LIGHT_BLUE.getColor()) - .append(text("\"player\"").color(PluginColor.MEDIUM_GOLD.getColor())) - .append(text(", add the player's name")))))); - } - - /** 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 isBukkitConsole) { - String arrow = isBukkitConsole ? " ->" : " →"; //4 spaces, alt + 26 - String bullet = isBukkitConsole ? " *" : " •"; //8 spaces, alt + 7 - return Component.newline() - .append(componentFactory.prefixTitle(isBukkitConsole)) - .append(newline()) - .append(componentFactory.subTitle("Type \"statistic examples\" to see examples!")) - .append(newline()) - .append(componentFactory.msgPart("Usage:", null, "/statistic", null, isBukkitConsole)) - .append(newline()) - .append(componentFactory.msgPart(arrow, null, "name", null, isBukkitConsole)) - .append(newline()) - .append(componentFactory.msgPart(arrow, null, "{sub-statistic}", "(a block, item or entity)", isBukkitConsole)) - .append(newline()) - .append(componentFactory.msgPart(arrow, null, "me | player | server | top", null, isBukkitConsole)) - .append(newline()) - .append(componentFactory.msgPart(bullet, "me:", null, "your own statistic", isBukkitConsole)) - .append(newline()) - .append(componentFactory.msgPart(bullet, "player:", null, "choose a player", isBukkitConsole)) - .append(newline()) - .append(componentFactory.msgPart(bullet, "server:", null, "everyone on the server combined", isBukkitConsole)) - .append(newline()) - .append(componentFactory.msgPart(bullet, "top:", null, "the top " + config.getTopListMaxSize(), isBukkitConsole)) - .append(newline()) - .append(componentFactory.msgPart(arrow, null, "{player-name}", null, isBukkitConsole)); + return new HelpMessage(componentFactory, + config.useHoverText() && !isConsoleSender, + isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit"), + config.getTopListMaxSize()); } } \ No newline at end of file diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/NumberFormatter.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/NumberFormatter.java new file mode 100644 index 0000000..f182c91 --- /dev/null +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/NumberFormatter.java @@ -0,0 +1,128 @@ +package com.gmail.artemis.the.gr8.playerstats.msg; + +import com.gmail.artemis.the.gr8.playerstats.enums.Unit; + +import java.text.DecimalFormat; + +public class NumberFormatter { + + private final DecimalFormat format; + + public NumberFormatter() { + format = new DecimalFormat(); + format.setGroupingUsed(true); + format.setGroupingSize(3); + } + + /** Turns the input number into a more readable format depending on its type + (number-of-times, time-, damage- or distance-based) according to the + corresponding config settings, and adds commas in groups of 3.*/ + public String format(long number, Unit statUnit) { + return format(number, statUnit, null); + } + + public String format(long number, Unit statUnit, Unit smallTimeUnit) { + if (smallTimeUnit == null) { + switch (statUnit.getType()) { + case DISTANCE -> { + return formatDistance(number, statUnit); + } + case DAMAGE -> { + return formatDamage(number, statUnit); + } + default -> { + return format.format(number); + } + } + } else { + return formatTime(number, statUnit, smallTimeUnit); + } + } + + /** The unit of damage-based statistics is half a heart by default. + This method turns the number into hearts. */ + private String formatDamage(long number, Unit statUnit) { //7 statistics + if (statUnit == Unit.HEART) { + return format.format(Math.round(number / 2.0)); + } else { + return format.format(number); + } + } + + /** The unit of distance-based statistics is cm by default. This method turns it into blocks by default, + and turns it into km or leaves it as cm otherwise, depending on the config settings. */ + private String formatDistance(long number, Unit statUnit) { //15 statistics + switch (statUnit) { + case CM -> { + return format.format(number); + } + case MILE -> { + return format.format(Math.round(number / 160934.4)); //to get from CM to Miles + } + case KM -> { + return format.format(Math.round(number / 100000.0)); //divide by 100 to get M, divide by 1000 to get KM + } + default -> { + return format.format(Math.round(number / 100.0)); + } + } + } + + /** The unit of time-based statistics is ticks by default.*/ + private String formatTime(long number, Unit bigUnit, Unit smallUnit) { //5 statistics + if (number == 0) { + return "-"; + } + if (bigUnit == Unit.TICK && smallUnit == Unit.TICK || bigUnit == Unit.NUMBER || smallUnit == Unit.NUMBER) { + return format.format(number); + } + + StringBuilder output = new StringBuilder(); + double max = bigUnit.getSeconds(); + double min = smallUnit.getSeconds(); + double leftover = number / 20.0; + + if (isInRange(max, min, 86400) && leftover >= 86400) { + double days = leftover / 60 / 60 / 24; + leftover = leftover % (60 * 60 * 24); + if (smallUnit == Unit.DAY && leftover >= 43200) { + days++; + return output.append(format.format(Math.round(days))) + .append("d").toString(); + } + output.append(format.format(Math.round(days))) + .append("d "); + } + if (isInRange(max, min, 3600) && leftover >= 3600) { + double hours = leftover / 60 / 60; + leftover = leftover % (60 * 60); + if (smallUnit == Unit.HOUR && leftover >= 1800) { + hours++; + return output.append(format.format(Math.round(hours))) + .append("h").toString(); + } + output.append(format.format(Math.round(hours))) + .append("h "); + } + if (isInRange(max, min, 60) && leftover >= 60) { + double minutes = leftover / 60; + leftover = leftover % 60; + if (smallUnit == Unit.MINUTE && leftover >= 30) { + minutes++; + return output.append(format.format(Math.round(minutes))) + .append("m").toString(); + } + output.append(format.format(Math.round(minutes))) + .append("m "); + } + if (isInRange(max, min, 1) && leftover > 0) { + output.append(format.format(Math.round(leftover))) + .append("s"); + } + return output.toString(); + } + + private boolean isInRange(double bigUnit, double smallUnit, double unitToEvaluate) { + return bigUnit >= unitToEvaluate && unitToEvaluate >= smallUnit; + } +} \ No newline at end of file diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/PrideComponentFactory.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/PrideComponentFactory.java index 2f3d155..9782f22 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/PrideComponentFactory.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/PrideComponentFactory.java @@ -22,9 +22,9 @@ public class PrideComponentFactory extends ComponentFactory { @Override - public TextComponent prefixTitle(boolean isBukkitConsole) { + public TextComponent prefixTitleComponent(boolean isBukkitConsole) { if (cancelRainbow(isBukkitConsole)) { - return super.prefixTitle(isBukkitConsole); + return super.prefixTitleComponent(isBukkitConsole); } else { String title = "____________ [PlayerStats] ____________"; //12 underscores @@ -35,9 +35,9 @@ public class PrideComponentFactory extends ComponentFactory { } @Override - public TextComponent pluginPrefix(boolean isConsoleSender) { + public TextComponent pluginPrefixComponent(boolean isConsoleSender) { if (cancelRainbow(isConsoleSender)) { - return super.pluginPrefix(isConsoleSender); + return super.pluginPrefixComponent(isConsoleSender); } return text() .append(MiniMessage.miniMessage() @@ -53,7 +53,7 @@ public class PrideComponentFactory extends ComponentFactory { "<#01c1a7>a" + "<#0690d4>t" + "<#205bf3>s" + - "<#6c15fa>] ")) + "<#6c15fa>]")) .build(); } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/ExampleMessage.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/ExampleMessage.java new file mode 100644 index 0000000..190274a --- /dev/null +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/ExampleMessage.java @@ -0,0 +1,89 @@ +package com.gmail.artemis.the.gr8.playerstats.msg.msgutils; + +import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor; +import com.gmail.artemis.the.gr8.playerstats.msg.ComponentFactory; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; + +import static net.kyori.adventure.text.Component.text; + +public class ExampleMessage implements TextComponent { + + private final TextComponent exampleMessage; + private final ComponentFactory componentFactory; + + public ExampleMessage(ComponentFactory componentFactory, boolean isBukkitConsole) { + this.componentFactory = componentFactory; + exampleMessage = getExampleMessage(isBukkitConsole); + } + + public TextComponent getExampleMessage(boolean isBukkitConsole) { + TextColor mainColor = isBukkitConsole ? PluginColor.GOLD.getConsoleColor() : PluginColor.GOLD.getColor(); + TextColor accentColor1 = isBukkitConsole ? PluginColor.MEDIUM_GOLD.getConsoleColor() : PluginColor.MEDIUM_GOLD.getColor(); + TextColor accentColor3 = isBukkitConsole ? PluginColor.LIGHT_YELLOW.getConsoleColor() : PluginColor.LIGHT_YELLOW.getColor(); + String arrow = isBukkitConsole ? " -> " : " → "; //4 spaces, alt + 26, 1 space + + return Component.newline() + .append(componentFactory.prefixTitleComponent(isBukkitConsole)) + .append(Component.newline()) + .append(text("Examples: ").color(mainColor)) + .append(Component.newline()) + .append(text(arrow).color(mainColor) + .append(text("/statistic ") + .append(text("animals_bred ").color(accentColor1) + .append(text("top").color(accentColor3))))) + .append(Component.newline()) + .append(text(arrow).color(mainColor) + .append(text("/statistic ") + .append(text("mine_block diorite ").color(accentColor1) + .append(text("me").color(accentColor3))))) + .append(Component.newline()) + .append(text(arrow).color(mainColor) + .append(text("/statistic ") + .append(text("deaths ").color(accentColor1) + .append(text("player ").color(accentColor3) + .append(text("Artemis_the_gr8")))))); + } + + @Override + public @NotNull String content() { + return exampleMessage.content(); + } + + @Override + public @NotNull TextComponent content(@NotNull String content) { + return exampleMessage.content(content); + } + + @Override + public @NotNull TextComponent.Builder toBuilder() { + return exampleMessage.toBuilder(); + } + + @Override + public @Unmodifiable @NotNull List children() { + return exampleMessage.children(); + } + + @Override + public @NotNull TextComponent children(@NotNull List children) { + return exampleMessage.children(children); + } + + @Override + public @NotNull Style style() { + return exampleMessage.style(); + } + + @Override + public @NotNull TextComponent style(@NotNull Style style) { + return exampleMessage.style(style); + } +} diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/HelpMessage.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/HelpMessage.java new file mode 100644 index 0000000..c0e032e --- /dev/null +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/HelpMessage.java @@ -0,0 +1,205 @@ +package com.gmail.artemis.the.gr8.playerstats.msg.msgutils; + +import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor; +import com.gmail.artemis.the.gr8.playerstats.msg.ComponentFactory; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +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.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; + +import static net.kyori.adventure.text.Component.text; + +public class HelpMessage implements TextComponent { + + private final ComponentFactory componentFactory; + private final TextComponent helpMessage; + boolean isBukkitConsole; + TextColor GRAY; + TextColor DARK_PURPLE; + TextColor GOLD; + TextColor MEDIUM_GOLD; + TextColor LIGHT_GOLD; + TextColor LIGHT_BLUE; + + + public HelpMessage(ComponentFactory componentFactory, boolean useHover, boolean isBukkitConsole, int listSize) { + this.componentFactory = componentFactory; + this.isBukkitConsole = isBukkitConsole; + getPluginColors(isBukkitConsole); + + if (!useHover || isBukkitConsole) { + helpMessage = getPlainHelpMsg(isBukkitConsole, listSize); + } else { + helpMessage = helpMsgHover(listSize); + } + } + + private TextComponent getPlainHelpMsg(boolean isBukkitConsole, int listSize) { + String arrowSymbol = isBukkitConsole ? "->" : "→"; //alt + 26 + String bulletSymbol = isBukkitConsole ? "*" : "•"; //alt + 7 + TextComponent spaces = text(" "); //4 spaces + TextComponent arrow = text(arrowSymbol).color(NamedTextColor.GOLD); + TextComponent bullet = text(bulletSymbol).color(NamedTextColor.GOLD); + + return Component.newline() + .append(componentFactory.prefixTitleComponent(isBukkitConsole)) + .append(newline()) + .append(text("Type \"/statistic examples\" to see examples!").color(GRAY).decorate(TextDecoration.ITALIC)) + .append(newline()) + .append(text("Usage:").color(GOLD)).append(space()) + .append(text("/statistic").color(LIGHT_GOLD)) + .append(newline()) + .append(spaces).append(arrow).append(space()) + .append(text("name").color(LIGHT_GOLD)) + .append(newline()) + .append(spaces).append(arrow).append(space()) + .append(text("{sub-statistic}").color(LIGHT_GOLD)).append(space()) + .append(text("(a block, item or entity)").color(GRAY)) + .append(newline()) + .append(spaces).append(arrow).append(space()) + .append(text("me | player | server | top").color(LIGHT_GOLD)) + .append(newline()) + .append(spaces).append(spaces).append(bullet).append(space()) + .append(text("me:").color(MEDIUM_GOLD)).append(space()) + .append(text("your own statistic").color(GRAY)) + .append(newline()) + .append(spaces).append(spaces).append(bullet).append(space()) + .append(text("player:").color(MEDIUM_GOLD)).append(space()) + .append(text("choose a player").color(GRAY)) + .append(newline()) + .append(spaces).append(spaces).append(bullet).append(space()) + .append(text("server:").color(MEDIUM_GOLD)).append(space()) + .append(text("everyone on the server combined").color(GRAY)) + .append(newline()) + .append(spaces).append(spaces).append(bullet).append(space()) + .append(text("top:").color(MEDIUM_GOLD)).append(space()) + .append(text("the top").color(GRAY).append(space()).append(text(listSize))) + .append(newline()) + .append(spaces).append(arrow).append(space()) + .append(text("{player-name}").color(LIGHT_GOLD)); + } + + private TextComponent helpMsgHover(int listSize) { + TextComponent spaces = text(" "); + TextComponent arrow = text("→").color(GOLD); + + return Component.newline() + .append(componentFactory.prefixTitleComponent(false)) + .append(newline()) + .append(componentFactory.subTitleComponent("Hover over the arguments for more information!")) + .append(newline()) + .append(text("Usage:").color(GOLD)).append(space()) + .append(text("/statistic").color(LIGHT_GOLD)) + .append(newline()) + .append(spaces).append(arrow).append(space()) + .append(text("name").color(LIGHT_GOLD) + .hoverEvent(HoverEvent.showText(text("The name that describes the statistic").color(LIGHT_BLUE) + .append(newline()) + .append(text("Example: ").color(GOLD)) + .append(text("\"animals_bred\"").color(LIGHT_GOLD))))) + .append(newline()) + .append(spaces).append(arrow).append(space()) + .append(text("sub-statistic").color(LIGHT_GOLD) + .hoverEvent(HoverEvent.showText( + text("Some statistics need an item, block or entity as extra input").color(LIGHT_BLUE) + .append(newline()) + .append(text("Example: ").color(GOLD) + .append(text("\"mine_block diorite\"").color(LIGHT_GOLD)))))) + .append(newline()) + .append(spaces).append(arrow + .hoverEvent(HoverEvent.showText( + text("Choose one").color(DARK_PURPLE)))).append(space()) + .append(text("me").color(LIGHT_GOLD) + .hoverEvent(HoverEvent.showText( + text("See your own statistic").color(LIGHT_BLUE)))) + .append(text(" | ").color(LIGHT_GOLD)) + .append(text("player").color(LIGHT_GOLD) + .hoverEvent(HoverEvent.showText( + text("Choose any player that has played on your server").color(LIGHT_BLUE)))) + .append(text(" | ").color(LIGHT_GOLD)) + .append(text("server").color(LIGHT_GOLD) + .hoverEvent(HoverEvent.showText( + text("See the combined total for everyone on your server").color(LIGHT_BLUE)))) + .append(text(" | ").color(LIGHT_GOLD)) + .append(text("top").color(LIGHT_GOLD) + .hoverEvent(HoverEvent.showText( + text("See the top").color(LIGHT_BLUE).append(space()) + .append(text(listSize))))) + .append(newline()) + .append(spaces).append(arrow).append(space()) + .append(text("player-name").color(LIGHT_GOLD) + .hoverEvent(HoverEvent.showText( + text("In case you typed").color(LIGHT_BLUE).append(space()) + .append(text("\"player\"").color(LIGHT_GOLD)) + .append(text(", add the player's name"))))); + } + + private void getPluginColors(boolean isBukkitConsole) { + if (isBukkitConsole) { + GRAY = PluginColor.GRAY.getConsoleColor(); + DARK_PURPLE = PluginColor.DARK_PURPLE.getConsoleColor(); + GOLD = PluginColor.GOLD.getConsoleColor(); + MEDIUM_GOLD = PluginColor.MEDIUM_GOLD.getConsoleColor(); + LIGHT_GOLD = PluginColor.LIGHT_GOLD.getConsoleColor(); + LIGHT_BLUE = PluginColor.LIGHT_BLUE.getConsoleColor(); + } else { + GRAY = PluginColor.GRAY.getColor(); + DARK_PURPLE = PluginColor.DARK_PURPLE.getColor(); + GOLD = PluginColor.GOLD.getColor(); + MEDIUM_GOLD = PluginColor.MEDIUM_GOLD.getColor(); + LIGHT_GOLD = PluginColor.LIGHT_GOLD.getColor(); + LIGHT_BLUE = PluginColor.LIGHT_BLUE.getColor(); + } + } + + @Override + public @NotNull String content() { + return helpMessage.content(); + } + + @Override + public @NotNull TextComponent content(@NotNull String content) { + return helpMessage.content(content); + } + + @Override + public @NotNull Builder toBuilder() { + return helpMessage.toBuilder(); + } + + @Override + public @Unmodifiable @NotNull List children() { + return helpMessage.children(); + } + + @Override + public @NotNull TextComponent children(@NotNull List children) { + return helpMessage.children(children); + } + + @Override + public @NotNull Style style() { + return helpMessage.style(); + } + + @Override + public @NotNull TextComponent style(@NotNull Style style) { + return helpMessage.style(style); + } + + private TextComponent space() { + return Component.space(); + } + + private TextComponent newline() { + return Component.newline(); + } +} \ No newline at end of file diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/LanguageKeyHandler.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/LanguageKeyHandler.java similarity index 86% rename from src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/LanguageKeyHandler.java rename to src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/LanguageKeyHandler.java index 522d704..c12728d 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/LanguageKeyHandler.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/LanguageKeyHandler.java @@ -1,5 +1,6 @@ -package com.gmail.artemis.the.gr8.playerstats.msg; +package com.gmail.artemis.the.gr8.playerstats.msg.msgutils; +import com.gmail.artemis.the.gr8.playerstats.enums.Unit; import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler; import org.bukkit.Material; import org.bukkit.Statistic; @@ -12,17 +13,14 @@ import java.util.HashMap; public class LanguageKeyHandler { - private final static HashMap statNameKeys; + private final HashMap statNameKeys; - static { + public LanguageKeyHandler() { statNameKeys = new HashMap<>(); generateStatNameKeys(); } - private LanguageKeyHandler() { - } - - public static String getStatKey(@NotNull Statistic statistic) { + public String getStatKey(@NotNull Statistic statistic) { if (statistic.getType() == Statistic.Type.UNTYPED) { return "stat.minecraft." + statNameKeys.get(statistic); } @@ -33,7 +31,7 @@ public class LanguageKeyHandler { /** Get the official Key from the NameSpacedKey for this entityType, or return null if no enum constant can be retrieved or entityType is UNKNOWN.*/ - public static @Nullable String getEntityKey(EntityType entity) { + public @Nullable String getEntityKey(EntityType entity) { if (entity == null || entity == EntityType.UNKNOWN) return null; else { return "entity.minecraft." + entity.getKey().getKey(); @@ -42,7 +40,7 @@ public class LanguageKeyHandler { /** Get the official Key from the NameSpacedKey for this item Material, or return null if no enum constant can be retrieved.*/ - public static @Nullable String getItemKey(Material item) { + public @Nullable String getItemKey(Material item) { if (item == null) return null; else if (item.isBlock()) { return getBlockKey(item); @@ -54,7 +52,7 @@ public class LanguageKeyHandler { /** Returns the official Key from the NameSpacedKey for the block Material provided, or return null if no enum constant can be retrieved.*/ - public static @Nullable String getBlockKey(Material block) { + public @Nullable String getBlockKey(Material block) { if (block == null) return null; else if (block.toString().toLowerCase().contains("wall_banner")) { //replace wall_banner with regular banner, since there is no key for wall banners String blockName = block.toString().toLowerCase().replace("wall_", ""); @@ -66,11 +64,19 @@ public class LanguageKeyHandler { } } - private static void generateDefaultKeys() { + public @Nullable String getUnitKey(Unit unit) { + if (unit == Unit.BLOCK) { + return "soundCategory.block"; + } else { + return null; + } + } + + private void generateDefaultKeys() { Arrays.stream(Statistic.values()).forEach(statistic -> statNameKeys.put(statistic, statistic.toString().toLowerCase())); } - private static void generateStatNameKeys() { + private void generateStatNameKeys() { //get the enum names for all statistics first generateDefaultKeys(); 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 1b8a75f..5b8efc6 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 @@ -111,9 +111,9 @@ public class ReloadThread extends Thread { MyLogger.actionCreated((offlinePlayers != null) ? offlinePlayers.length : 0); ForkJoinPool.commonPool().invoke(task); MyLogger.actionFinished(1); - MyLogger.logTimeTaken("ReloadThread", - ("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time); OfflinePlayerHandler.updateOfflinePlayerList(playerMap); + MyLogger.logTimeTaken("ReloadThread", + ("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time); } } \ No newline at end of file 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 479a8dd..919c813 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 @@ -30,26 +30,6 @@ public class StatRequest { playerFlag = false; } - /** Returns true if this StatRequest has all the information needed for a Statistic lookup to succeed.*/ - public boolean isValid() { - if (statistic == null) return false; - switch (statistic.getType()) { - case BLOCK -> { - if (block == null) return false; - } - case ENTITY -> { - if (entity == null) return false; - } - case ITEM -> { - if (item == null) return false; - } - case UNTYPED -> { - if (subStatEntry != null) return false; - } - } //if target = PLAYER and playerName = null, return false, otherwise return true - return selection != Target.PLAYER || playerName != null; - } - public @NotNull CommandSender getCommandSender() { return sender; } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/EnumHandler.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/EnumHandler.java index 56ab950..6b898f9 100644 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/EnumHandler.java +++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/EnumHandler.java @@ -112,6 +112,18 @@ public class EnumHandler { return statNames.contains(statName.toLowerCase()); } + public static boolean isDistanceStatistic(@NotNull String statName) { + return statName.toLowerCase().contains("one_cm"); + } + + public static boolean isDamageStatistic(@NotNull String statName) { + return statName.toLowerCase().contains("damage"); + } + + public static boolean isTimeStatistic(@NotNull String statName) { + return statName.toLowerCase().contains("time") || statName.toLowerCase().contains("one_minute"); + } + /** Returns the names of all general statistics in lowercase */ public static List getStatNames() { return statNames; 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 924a62e..13f3244 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 @@ -52,6 +52,10 @@ public class MyLogger { } } + public static void logMsg(String content) { + logMsg(content, DebugLevel.LOW, false); + } + public static void logMsg(String content, boolean logAsWarning) { logMsg(content, DebugLevel.LOW, logAsWarning); } @@ -129,7 +133,7 @@ public class MyLogger { if (debugLevel != DebugLevel.LOW) { threadNames = new ConcurrentHashMap<>(); playersIndex.set(0); - logger.info("Initial Action created for " + taskLength + " Players. Start Index is " + playersIndex.get() + ". Processing..."); + logger.info("Initial Action created for " + taskLength + " Players. Processing..."); } } @@ -161,7 +165,7 @@ public class MyLogger { } processedPlayers[nextPlayersIndex() % 10] = playerName; } - else if (debugLevel == DebugLevel.MEDIUM) { + else if (debugLevel == DebugLevel.MEDIUM || debugLevel == DebugLevel.HIGH && thread == 2) { nextPlayersIndex(); } } @@ -179,8 +183,10 @@ public class MyLogger { if (debugLevel != DebugLevel.LOW) { logger.info("Finished Recursive Action! In total " + threadNames.size() + " Threads were used to process " + - playersIndex.get() + " Players: " + - Collections.list(threadNames.keys())); + playersIndex.get() + " Players."); + } + if (debugLevel == DebugLevel.HIGH) { + logger.info(Collections.list(threadNames.keys()).toString()); } } diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/NumberFormatter.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/NumberFormatter.java deleted file mode 100644 index f920a06..0000000 --- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/NumberFormatter.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.gmail.artemis.the.gr8.playerstats.utils; - -import java.text.DecimalFormat; - -public class NumberFormatter { - - private static final DecimalFormat format; - - static{ - format = new DecimalFormat(); - format.setGroupingUsed(true); - format.setGroupingSize(3); - } - private NumberFormatter(){ - } - - public static String format(long number) { - return format.format(number); - } -} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 77f2a7d..fc185ee 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------------------------------------ # # PlayerStats Configuration # # ------------------------------------------------------------------------------------------------------ # -config-version: 4 +config-version: 5 # # ------------------------------- # # @@ -10,7 +10,7 @@ config-version: 4 # How much output you'll get in the server console while PlayerStats is processing # 1 = low (only show unexpected errors) -# 2 = medium (detail all encountered exceptions, log main tasks and show time taken) +# 2 = medium (log main tasks and time taken) # 3 = high (log all tasks and time taken) debug-level: 1 @@ -31,9 +31,34 @@ number-of-days-since-last-joined: 0 # The actual translation is handled by the Minecraft language files and happens automatically translate-to-client-language: true -# Use hover-text for additional info in the usage explanation +# Use hover-text for additional info about statistic numbers enable-hover-text: true +# The unit to display certain statistics in. +# Minecraft measures distance in cm. PlayerStats supports: blocks, cm, m (= blocks), miles, km +distance-unit: blocks +distance-unit-for-hover-text: km + +# Minecraft measures damage in 0.5 hearts (1HP). PlayerStats supports: hp, hearts +damage-unit: hearts +damage-unit-for-hover-text: hp + +# Minecraft measures time in ticks. With the below settings, PlayerStats will: +# Auto-detect the best maximum unit to use (weeks/days/hours/minutes/seconds) for your players' statistics +# Show a specified amount of additional smaller units (example: "x days" would become "x days, y hours, z minutes") +auto-detect-biggest-time-unit: true +number-of-extra-units: 1 +auto-detect-biggest-time-unit-for-hover-text: false +number-of-extra-units-for-hover-text: 0 + +# If you don't want the unit to be auto-detected, set the auto-detect settings to false and specify your own range here +# If the max and min are the same, only that unit will be displayed +# PlayerStats supports: days, hours, minutes, seconds (and ticks if you want the original number) +biggest-time-unit: days +smallest-time-unit: hours +biggest-time-unit-for-hover-text: hours +smallest-time-unit-for-hover-text: seconds + # Automatically use themed formatting for the duration of certain holidays or festivals enable-festive-formatting: true