Merge pull request #82 from itHotL/stat-formatting

Stat formatting
This commit is contained in:
Elise 2022-07-04 22:00:11 +02:00 committed by GitHub
commit 98d9f94544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1157 additions and 487 deletions

View File

@ -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.commands.TabCompleter;
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; 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.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.MessageWriter;
import com.gmail.artemis.the.gr8.playerstats.msg.PrideComponentFactory;
import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;

View File

@ -1,7 +1,6 @@
package com.gmail.artemis.the.gr8.playerstats.commands; package com.gmail.artemis.the.gr8.playerstats.commands;
import com.gmail.artemis.the.gr8.playerstats.ThreadManager; 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.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler; 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.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 com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Statistic; import org.bukkit.Statistic;
@ -22,8 +20,6 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.Component.text;
public class StatCommand implements CommandExecutor { 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 args[0].equalsIgnoreCase("example")) { //in case of "statistic examples", show examples
adventure.sender(sender).sendMessage(messageWriter.usageExamples(isBukkitConsole)); 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 { else {
StatRequest request = generateRequest(sender, args); StatRequest request = generateRequest(sender, args);
TextComponent issues = checkRequest(request, isBukkitConsole); TextComponent issues = checkRequest(request, isBukkitConsole);

View File

@ -15,7 +15,7 @@ public class TabCompleter implements org.bukkit.command.TabCompleter {
private final List<String> commandOptions; private final List<String> commandOptions;
//TODO add "example" to the list
public TabCompleter() { public TabCompleter() {
commandOptions = new ArrayList<>(); commandOptions = new ArrayList<>();
commandOptions.add("top"); commandOptions.add("top");

View File

@ -23,7 +23,7 @@ public class ConfigHandler {
saveDefaultConfig(); saveDefaultConfig();
config = YamlConfiguration.loadConfiguration(configFile); config = YamlConfiguration.loadConfiguration(configFile);
configVersion = 4; configVersion = 5;
checkConfigVersion(); checkConfigVersion();
MyLogger.setDebugLevel(getDebugLevel()); MyLogger.setDebugLevel(getDebugLevel());
@ -91,18 +91,60 @@ public class ConfigHandler {
return config.getInt("number-of-days-since-last-joined", 0); 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.
<p>Default: true</p>*/ <p>Default: true</p>*/
public boolean useTranslatableComponents() { public boolean useTranslatableComponents() {
return config.getBoolean("translate-to-client-language", true); return config.getBoolean("translate-to-client-language", true);
} }
/** Whether to use HoverComponents in the usage explanation. /** Whether to use HoverComponents for additional information.
<p>Default: true</p>*/ <p>Default: true</p>*/
public boolean useHoverText() { public boolean useHoverText() {
return config.getBoolean("enable-hover-text", true); 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. /** Whether to use festive formatting, such as pride colors.
<p>Default: true</p> */ <p>Default: true</p> */
public boolean useFestiveFormatting() { public boolean useFestiveFormatting() {
@ -149,7 +191,7 @@ public class ConfigHandler {
<p>Style: "none"</p> <p>Style: "none"</p>
<p>Color Top: "green"</p> <p>Color Top: "green"</p>
<p>Color Individual/Server: "gold"</p>*/ <p>Color Individual/Server: "gold"</p>*/
public String getPlayerNameFormatting(Target selection, boolean isStyle) { public String getPlayerNameDecoration(Target selection, boolean getStyle) {
String def; String def;
if (selection == Target.TOP) { if (selection == Target.TOP) {
def = "green"; def = "green";
@ -157,7 +199,7 @@ public class ConfigHandler {
else { else {
def = "gold"; 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. /** 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: /** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
<p>Style: "none"</p> <p>Style: "none"</p>
<p>Color: "yellow"</p>*/ <p>Color: "yellow"</p>*/
public String getStatNameFormatting(Target selection, boolean isStyle) { public String getStatNameDecoration(Target selection, boolean getStyle) {
return getStringFromConfig(selection, isStyle, "yellow", "stat-names"); 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: /** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
<p>Style: "none"</p> <p>Style: "none"</p>
<p>Color: "#FFD52B"</p>*/ <p>Color: "#FFD52B"</p>*/
public String getSubStatNameFormatting(Target selection, boolean isStyle) { public String getSubStatNameDecoration(Target selection, boolean getStyle) {
return getStringFromConfig(selection, isStyle, "#FFD52B", "sub-stat-names"); 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: /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p> <p>Style: "none"</p>
<p>Color Top: "#55AAFF"</p> <p>Color Top: "#55AAFF"</p>
<p>Color Individual/Server: "#ADE7FF"</p> */ <p>Color Individual/Server: "#ADE7FF"</p> */
public String getStatNumberFormatting(Target selection, boolean isStyle) { public String getStatNumberDecoration(Target selection, boolean getStyle) {
String def; String def;
if (selection == Target.TOP) { if (selection == Target.TOP) {
def = "#55AAFF"; def = "#55AAFF";
@ -198,14 +240,14 @@ public class ConfigHandler {
else { else {
def = "#ADE7FF"; 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: /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p> <p>Style: "none"</p>
<p>Color Top: "yellow"</p> <p>Color Top: "yellow"</p>
<p>Color Server: "gold"</p>*/ <p>Color Server: "gold"</p>*/
public String getTitleFormatting(Target selection, boolean isStyle) { public String getTitleDecoration(Target selection, boolean getStyle) {
String def; String def;
if (selection == Target.TOP) { if (selection == Target.TOP) {
def = "yellow"; def = "yellow";
@ -213,41 +255,62 @@ public class ConfigHandler {
else { else {
def = "gold"; 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: /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p> <p>Style: "none"</p>
<p>Color: "gold"</p>*/ <p>Color: "gold"</p>*/
public String getTitleNumberFormatting(boolean isStyle) { public String getTitleNumberDecoration(boolean getStyle) {
return getStringFromConfig(Target.TOP, isStyle, "gold", "title-number"); 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: /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p> <p>Style: "none"</p>
<p>Color: "#FFB80E"</p>*/ <p>Color: "#FFB80E"</p>*/
public String getServerNameFormatting(boolean isStyle) { public String getServerNameDecoration(boolean getStyle) {
return getStringFromConfig(Target.SERVER, isStyle, "#FFB80E", "server-name"); 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: /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p> <p>Style: "none"</p>
<p>Color: "gold"</p>*/ <p>Color: "gold"</p>*/
public String getRankNumberFormatting(boolean isStyle) { public String getRankNumberDecoration(boolean getStyle) {
return getStringFromConfig(Target.TOP, isStyle, "gold", "rank-numbers"); 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: /** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p> <p>Style: "none"</p>
<p>Color: "dark_gray"</p> */ <p>Color: "dark_gray"</p> */
public String getDotsFormatting(boolean isStyle) { public String getDotsDecoration(boolean getStyle) {
return getStringFromConfig(Target.TOP, isStyle, "dark_gray", "dots"); 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. */ /** Returns a String representing the Unit that should be used for a certain Unit.Type.
private @Nullable String getStringFromConfig(Target selection, boolean isStyle, String def, String pathName){ If no String can be retrieved from the config, the supplied defaultValue will be returned.
String path = isStyle ? pathName + "-style" : pathName; If the defaultValue is different for hoverText, an optional String defaultHoverValue can be supplied.
String defaultValue = isStyle ? "none" : def; @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); ConfigurationSection section = getRelevantSection(selection);
return section != null ? section.getString(path, defaultValue) : null; return section != null ? section.getString(path, defaultValue) : null;

View File

@ -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() {
}
}
}

View File

@ -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.config.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor; 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.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.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent; 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.NamedTextColor;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.util.HSVLike;
import net.kyori.adventure.util.Index; import net.kyori.adventure.util.Index;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -32,219 +30,116 @@ public class ComponentFactory {
config = c; config = c;
} }
/** Returns [PlayerStats] followed by a single space. */ /** Returns [PlayerStats]. */
public TextComponent pluginPrefix(boolean isBukkitConsole) { public TextComponent pluginPrefixComponent(boolean isBukkitConsole) {
return text("[") return text("[")
.color(PluginColor.GRAY.getColor()) .color(PluginColor.GRAY.getColor())
.append(text("PlayerStats").color(PluginColor.GOLD.getColor())) .append(text("PlayerStats").color(PluginColor.GOLD.getColor()))
.append(text("]")) .append(text("]"));
.append(space());
} }
/** Returns [PlayerStats] surrounded by underscores on both sides. */ /** 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 String underscores = "____________"; //12 underscores for both console and in-game
TextColor underscoreColor = isBukkitConsole ? TextColor underscoreColor = isBukkitConsole ?
PluginColor.DARK_PURPLE.getConsoleColor() : PluginColor.DARK_PURPLE.getColor(); PluginColor.DARK_PURPLE.getConsoleColor() : PluginColor.DARK_PURPLE.getColor();
return text(underscores).color(underscoreColor) return text(underscores).color(underscoreColor)
.append(text(" ")) //4 spaces .append(text(" ")) //4 spaces
.append(pluginPrefix(isBukkitConsole)) .append(pluginPrefixComponent(isBukkitConsole))
.append(text(" ")) //3 spaces (since prefix already has one) .append(text(" ")) //4 spaces
.append(text(underscores)); .append(text(underscores));
} }
/** Returns a TextComponent with the input String as content, with color Gray and decoration Italic.*/ /** 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); return text(content).color(PluginColor.GRAY.getColor()).decorate(TextDecoration.ITALIC);
} }
/** Returns a TextComponents that represents a full message, with [PlayerStats] prepended. */ /** Returns a TextComponents that represents a full message, with [PlayerStats] prepended. */
public TextComponent msg(String msg, boolean isBukkitConsole) { public TextComponent messageComponent() {
return pluginPrefix(isBukkitConsole) return text().color(PluginColor.MEDIUM_BLUE.getColor()).build();
.append(text(msg)
.color(PluginColor.MEDIUM_BLUE.getColor()));
} }
/** Returns a plain TextComponent that represents a single message line. public TextComponent.Builder playerNameBuilder(String playerName, Target selection) {
A space will be inserted after part1, part2 and part3. return getComponentBuilder(playerName,
Each message part has its own designated color. getColorFromString(config.getPlayerNameDecoration(selection, false)),
@param part1 color DARK_GOLD getStyleFromString(config.getPlayerNameDecoration(selection, true)));
@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);
} }
/** Returns a plain TextComponent that represents a single message line. /** @param prettyStatName a statName with underscores removed and each word capitalized
A space will be inserted after part1, part2 and part3. @param prettySubStatName if present, a subStatName with underscores removed and each word capitalized*/
Each message part has its own designated color. public TextComponent statNameTextComponent(String prettyStatName, @Nullable String prettySubStatName, Target selection) {
if isBukkitConsole is true, the colors will be the nearest ChatColor to the below colors. TextComponent.Builder totalStatNameBuilder = getComponentBuilder(prettyStatName,
@param part1 color DARK_GOLD getColorFromString(config.getStatNameDecoration(selection, false)),
@param part2 color MEDIUM_GOLD getStyleFromString(config.getStatNameDecoration(selection, true)));
@param part3 color YELLOW TextComponent subStat = subStatNameTextComponent(prettySubStatName, selection);
@param part4 color GRAY
*/ if (!subStat.equals(Component.empty())) {
public TextComponent msgPart(@Nullable String part1, @Nullable String part2, @Nullable String part3, @Nullable String part4, boolean isBukkitConsole) { totalStatNameBuilder
TextComponent.Builder msg = Component.text(); .append(space().decorations(TextDecoration.NAMES.values(), false))
if (part1 != null) { .append(subStatNameTextComponent(prettySubStatName, selection));
TextColor pluginColor = isBukkitConsole ? PluginColor.GOLD.getConsoleColor() : PluginColor.GOLD.getColor();
msg.append(text(part1)
.color(pluginColor))
.append(space());
} }
if (part2 != null) { return totalStatNameBuilder.build();
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();
} }
/** Returns a TextComponent with a single line of hover-text in the specified color. /** Returns a TextComponent for the subStatName, or an empty component.*/
@param plainText the base message private TextComponent subStatNameTextComponent(@Nullable String prettySubStatName, Target selection) {
@param hoverText the hovering text if (prettySubStatName == null) {
@param hoverColor color of the hovering text */ return Component.empty();
public TextComponent simpleHoverPart(String plainText, String hoverText, PluginColor hoverColor) { } else {
return simpleHoverPart(plainText, null, hoverText, hoverColor); 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. /** Returns a TextComponent with TranslatableComponent as a child.*/
If a PluginColor is provided for the plainText, the base color is set as well. public TextComponent statNameTransComponent(String statKey, String subStatKey, Target selection) {
@param plainText the base message TextComponent.Builder totalStatNameBuilder = getComponentBuilder(null,
@param plainColor color of the base message getColorFromString(config.getStatNameDecoration(selection, false)),
@param hoverText the hovering text getStyleFromString(config.getStatNameDecoration(selection, true)));
@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 hover-text that can consist of three different parts, TextComponent subStat = subStatNameTransComponent(subStatKey, selection);
divided over two different lines. Each part has its own designated color. If all the if (statKey.equalsIgnoreCase("stat_type.minecraft.killed")) {
input Strings are null, it will return an empty Component. return totalStatNameBuilder.append(killEntityBuilder(subStat)).build();
@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());
}
} }
if (hoverLineTwoA != null) { else if (statKey.equalsIgnoreCase("stat_type.minecraft.killed_by")) {
hoverText.append(text(hoverLineTwoA) return totalStatNameBuilder.append(entityKilledByBuilder(subStat)).build();
.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 { else {
statName = LanguageKeyHandler.getStatKey(request.getStatistic()); totalStatNameBuilder.append(translatable().key(statKey));
switch (request.getStatistic().getType()) { if (!subStat.equals(Component.empty())) {
case BLOCK -> subStatName = LanguageKeyHandler.getBlockKey(request.getBlock()); totalStatNameBuilder.append(
case ENTITY -> subStatName = LanguageKeyHandler.getEntityKey(request.getEntity()); space().decorations(TextDecoration.NAMES.values(), false)
case ITEM -> subStatName = LanguageKeyHandler.getItemKey(request.getItem()); .append(subStat));
case UNTYPED -> {
}
} }
return totalStatNameBuilder.build();
} }
return statName(statName, subStatName, request.getSelection());
} }
private TranslatableComponent statName(@NotNull String statKey, String subStatKey, @NotNull Target selection) { /** Returns a TranslatableComponent for the subStatName, or an empty component.*/
TranslatableComponent.Builder totalName; private TextComponent subStatNameTransComponent(String subStatKey, Target selection) {
TextComponent subStat = subStatName(subStatKey, selection); if (subStatKey != null) {
TextColor statNameColor = getColorFromString(config.getStatNameFormatting(selection, false)); return getComponentBuilder(null,
TextDecoration statNameStyle = getStyleFromString(config.getStatNameFormatting(selection, true)); getColorFromString(config.getSubStatNameDecoration(selection, false)),
getStyleFromString(config.getSubStatNameDecoration(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()
.append(text("(")) .append(text("("))
.append(translatable() .append(translatable()
.key(subStatName)) .key(subStatKey))
.append(text(")")) .append(text(")"))
.color(getColorFromString(config.getSubStatNameFormatting(selection, false))); .build();
subStat.decorations(TextDecoration.NAMES.values(), false);
if (style != null) subStat.decoration(style, TextDecoration.State.TRUE);
return subStat.build();
}
else {
return null;
} }
return Component.empty();
} }
/** Construct a custom translation for kill_entity with the language key for commands.kill.success.single ("Killed %s"). /** 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.*/ @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() return translatable()
.key("commands.kill.success.single") //"Killed %s" .key("commands.kill.success.single") //"Killed %s"
.args(subStat); .args(subStat);
@ -254,7 +149,7 @@ public class ComponentFactory {
("Number of Deaths") and book.byAuthor ("by %s"). ("Number of Deaths") and book.byAuthor ("by %s").
@return a TranslatableComponent Builder with stat.minecraft.deaths as key, with a ChildComponent @return a TranslatableComponent Builder with stat.minecraft.deaths as key, with a ChildComponent
with book.byAuthor as key and the subStat Component as args.*/ 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() return translatable()
.key("stat.minecraft.deaths") //"Number of Deaths" .key("stat.minecraft.deaths") //"Number of Deaths"
.append(space()) .append(space())
@ -263,62 +158,96 @@ public class ComponentFactory {
.args(subStat)); .args(subStat));
} }
public TextComponent statNumber(long number, Target selection) { //TODO Add hoverComponent with full number
return createComponent(NumberFormatter.format(number), public TextComponent.Builder statNumberBuilder(String prettyNumber, Target selection) {
getColorFromString(config.getStatNumberFormatting(selection, false)), return getComponentBuilder(prettyNumber,
getStyleFromString(config.getStatNumberFormatting(selection, true))); getColorFromString(config.getStatNumberDecoration(selection, false)),
getStyleFromString(config.getStatNumberDecoration(selection, true)));
} }
public TextComponent title(String content, Target selection) { public TextComponent statNumberHoverComponent(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, Target selection) {
return createComponent(content, TextColor baseColor = getColorFromString(config.getStatNumberDecoration(selection, false));
getColorFromString(config.getTitleFormatting(selection, false)), TextDecoration style = getStyleFromString(config.getStatNumberDecoration(selection, true));
getStyleFromString(config.getTitleFormatting(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) { //TODO Make this dark gray (or at least darker than statNumber, and at least for time statistics)
return createComponent(number + "", public TextComponent statUnitComponent(String unitName, String unitKey, Target selection) {
getColorFromString(config.getTitleNumberFormatting(false)), if (!(unitName == null && unitKey == null)) {
getStyleFromString(config.getTitleNumberFormatting(true))); 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) { public TextComponent titleComponent(String content, Target selection) {
TextComponent colon = text(":").color(getColorFromString(config.getServerNameFormatting(false))); return getComponent(content,
return createComponent(serverName, getColorFromString(config.getTitleDecoration(selection, false)),
getColorFromString(config.getServerNameFormatting(false)), getStyleFromString(config.getTitleDecoration(selection, true)));
getStyleFromString(config.getServerNameFormatting(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); .append(colon);
} }
public TextComponent rankingNumber(String number) { public TextComponent rankingNumberComponent(String number) {
return createComponent(number, return getComponent(number,
getColorFromString(config.getRankNumberFormatting(false)), getColorFromString(config.getRankNumberDecoration(false)),
getStyleFromString(config.getRankNumberFormatting(true))); getStyleFromString(config.getRankNumberDecoration(true)));
} }
public TextComponent dots(String dots) { public TextComponent.Builder dotsBuilder() {
return createComponent(dots, return getComponentBuilder(null,
getColorFromString(config.getDotsFormatting(false)), getColorFromString(config.getDotsDecoration(false)),
getStyleFromString(config.getDotsFormatting(true))); getStyleFromString(config.getDotsDecoration(true)));
} }
private TextComponent createComponent(String content, TextColor color, @Nullable TextDecoration style) { 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); return getComponentBuilder(content, color, style).build();
} }
/** Replace "_" with " " and capitalize each first letter of the input. private TextComponent.Builder getComponentBuilder(@Nullable String content, TextColor color, @Nullable TextDecoration style) {
@param input String to prettify, case-insensitive*/ TextComponent.Builder builder = text()
private String getPrettyName(String input) { .decorations(TextDecoration.NAMES.values(), false)
if (input == null) return null; .color(color);
StringBuilder capitals = new StringBuilder(input.toLowerCase()); if (content != null) {
capitals.setCharAt(0, Character.toUpperCase(capitals.charAt(0))); builder.append(text(content));
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(); if (style != null) {
builder.decorate(style);
}
return builder;
} }
private TextColor getColorFromString(String configString) { private TextColor getColorFromString(String configString) {
@ -343,6 +272,12 @@ public class ComponentFactory {
return names.value(textColor); 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) { private @Nullable TextDecoration getStyleFromString(@NotNull String configString) {
if (configString.equalsIgnoreCase("none")) { if (configString.equalsIgnoreCase("none")) {
return null; return null;
@ -355,5 +290,4 @@ public class ComponentFactory {
return styles.value(configString); return styles.value(configString);
} }
} }
} }

View File

@ -1,13 +1,16 @@
package com.gmail.artemis.the.gr8.playerstats.msg; 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.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; 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.statistic.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; 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.Bukkit;
import org.bukkit.Statistic; import org.bukkit.Statistic;
import org.bukkit.map.MinecraftFont; import org.bukkit.map.MinecraftFont;
@ -24,9 +27,13 @@ public class MessageWriter {
private static ConfigHandler config; private static ConfigHandler config;
private static ComponentFactory componentFactory; private static ComponentFactory componentFactory;
private final LanguageKeyHandler languageKeyHandler;
private final NumberFormatter formatter;
public MessageWriter(ConfigHandler c) { public MessageWriter(ConfigHandler c) {
config = c; config = c;
formatter = new NumberFormatter();
languageKeyHandler = new LanguageKeyHandler();
getComponentFactory(); getComponentFactory();
} }
@ -44,124 +51,266 @@ public class MessageWriter {
} }
public TextComponent reloadedConfig(boolean isBukkitConsole) { public TextComponent reloadedConfig(boolean isBukkitConsole) {
return componentFactory.msg( return componentFactory.pluginPrefixComponent(isBukkitConsole)
"Config reloaded!", isBukkitConsole); .append(space())
.append(componentFactory.messageComponent().content("Config reloaded!"));
} }
public TextComponent stillReloading(boolean isBukkitConsole) { public TextComponent stillReloading(boolean isBukkitConsole) {
return componentFactory.msg( return componentFactory.pluginPrefixComponent(isBukkitConsole)
"The plugin is (re)loading, " + .append(space())
"your request will be processed when it is done!", isBukkitConsole); .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) { public TextComponent waitAMoment(boolean longWait, boolean isBukkitConsole) {
String msg = longWait ? "Calculating statistics, this may take a minute..." : String msg = longWait ? "Calculating statistics, this may take a minute..." :
"Calculating statistics, this may take a few moments..."; "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) { public TextComponent missingStatName(boolean isBukkitConsole) {
return componentFactory.msg( return componentFactory.pluginPrefixComponent(isBukkitConsole)
"Please provide a valid statistic name!", isBukkitConsole); .append(space())
.append(componentFactory.messageComponent().content(
"Please provide a valid statistic name!"));
} }
public TextComponent missingSubStatName(Statistic.Type statType, boolean isBukkitConsole) { public TextComponent missingSubStatName(Statistic.Type statType, boolean isBukkitConsole) {
return componentFactory.msg( return componentFactory.pluginPrefixComponent(isBukkitConsole)
"Please add a valid " + .append(space())
getSubStatTypeName(statType) + .append(componentFactory.messageComponent().content(
" to look up this statistic!", isBukkitConsole); "Please add a valid " + getSubStatTypeName(statType) + " to look up this statistic!"));
} }
public TextComponent missingPlayerName(boolean isBukkitConsole) { public TextComponent missingPlayerName(boolean isBukkitConsole) {
return componentFactory.msg( return componentFactory.pluginPrefixComponent(isBukkitConsole)
"Please specify a valid player-name!", isBukkitConsole); .append(space())
.append(componentFactory.messageComponent().content(
"Please specify a valid player-name!"));
} }
public TextComponent wrongSubStatType(Statistic.Type statType, String subStatEntry, boolean isBukkitConsole) { public TextComponent wrongSubStatType(Statistic.Type statType, String subStatEntry, boolean isBukkitConsole) {
return componentFactory.msg( return componentFactory.pluginPrefixComponent(isBukkitConsole)
"\"" + subStatEntry + "\" is not a valid " + getSubStatTypeName(statType) + "!", isBukkitConsole); .append(space())
.append(componentFactory.messageComponent().content(
"\"" + subStatEntry + "\" is not a valid " + getSubStatTypeName(statType) + "!"));
} }
public TextComponent unknownError(boolean isBukkitConsole) { public TextComponent unknownError(boolean isBukkitConsole) {
return componentFactory.msg( return componentFactory.pluginPrefixComponent(isBukkitConsole)
.append(space())
.append(componentFactory.messageComponent().content(
"Something went wrong with your request, " + "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) { public TextComponent formatPlayerStat(int stat, @NotNull StatRequest request) {
if (!request.isValid()) return unknownError(request.isBukkitConsoleSender());
return Component.text() return Component.text()
.append(componentFactory.playerName( request.getPlayerName() + ": ", Target.PLAYER)) .append(componentFactory.playerNameBuilder(request.getPlayerName(), Target.PLAYER)
.append(componentFactory.statNumber(stat, Target.PLAYER)) .append(text(":"))
.append(space()) .append(space()))
.append(componentFactory.statName(request)) .append(getStatNumberComponent(request.getStatistic(), stat, Target.PLAYER))
.append(space()) .append(space())
.append(getStatNameComponent(request))
.append(getStatUnitComponent(request.getStatistic(), request.getSelection()))
.build(); .build();
} }
public TextComponent formatTopStats(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull StatRequest request) { public TextComponent formatTopStats(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull StatRequest request) {
if (!request.isValid()) return unknownError(request.isBukkitConsoleSender());
TextComponent.Builder topList = Component.text() TextComponent.Builder topList = Component.text()
.append(newline()) .append(newline())
.append(componentFactory.pluginPrefix(request.isBukkitConsoleSender())) .append(componentFactory.pluginPrefixComponent(request.isBukkitConsoleSender())).append(space())
.append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)) .append(componentFactory.titleComponent(config.getTopStatsTitle(), Target.TOP)).append(space())
.append(space()) .append(componentFactory.titleNumberComponent(topStats.size())).append(space())
.append(componentFactory.titleNumber(topStats.size())) .append(getStatNameComponent(request))
.append(space()) .append(getStatUnitComponent(request.getStatistic(), request.getSelection()));
.append(componentFactory.statName(request));
ArrayList<Unit> timeUnits = null;
if (Unit.getTypeFromStatistic(request.getStatistic()) == Unit.Type.TIME) {
timeUnits = getTimeUnitRange(topStats.values().iterator().next());
}
boolean useDots = config.useDots(); boolean useDots = config.useDots();
boolean boldNames = config.playerNameIsBold(); boolean boldNames = config.playerNameIsBold();
Set<String> playerNames = topStats.keySet();
MinecraftFont font = new MinecraftFont(); MinecraftFont font = new MinecraftFont();
Set<String> playerNames = topStats.keySet();
int count = 0; int count = 0;
for (String playerName : playerNames) { for (String playerName : playerNames) {
count = count+1; TextComponent.Builder playerNameBuilder = componentFactory.playerNameBuilder(playerName, Target.TOP);
count++;
topList.append(newline()) topList.append(newline())
.append(componentFactory.rankingNumber(count + ". ")) .append(componentFactory.rankingNumberComponent(count + ". "))
.append(componentFactory.playerName(playerName, Target.TOP)); .append(playerNameBuilder);
if (useDots) { if (useDots) {
topList.append(space()); topList.append(space());
TextComponent.Builder dotsBuilder = componentFactory.dotsBuilder();
int dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/2); int dots;
if (request.isConsoleSender()) { if (request.isConsoleSender()) {
dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/6) + 7; 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); dots = (int) Math.round((130.0 - font.getWidth(count + ". ") - (font.getWidth(playerName) * 1.19))/2);
} }
if (dots >= 1) { if (dots >= 1) {
topList.append(componentFactory.dots(".".repeat(dots))); topList.append(dotsBuilder.append(text((".".repeat(dots)))));
} }
} else {
topList.append(playerNameBuilder.append(text(":")));
} }
else { if (timeUnits != null) {
topList.append(componentFactory.playerName(":", Target.TOP)); 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(); return topList.build();
} }
public TextComponent formatServerStat(long stat, @NotNull StatRequest request) { public TextComponent formatServerStat(long stat, @NotNull StatRequest request) {
if (!request.isValid()) return unknownError(request.isBukkitConsoleSender());
return Component.text() return Component.text()
.append(componentFactory.title(config.getServerTitle(), Target.SERVER)) .append(componentFactory.titleComponent(config.getServerTitle(), Target.SERVER))
.append(space()) .append(space())
.append(componentFactory.serverName(config.getServerName())) .append(componentFactory.serverNameComponent(config.getServerName()))
.append(space()) .append(space())
.append(componentFactory.statNumber(stat, Target.SERVER)) .append(getStatNumberComponent(request.getStatistic(), stat, Target.SERVER))
.append(space())
.append(componentFactory.statName(request))
.append(space()) .append(space())
.append(getStatNameComponent(request))
.append(getStatUnitComponent(request.getStatistic(), request.getSelection())) //space is provided by statUnit
.build(); .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<Unit> 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:
<p>0. maxUnit</p>
<p>1. minUnit</p>
<p>2. maxHoverUnit</p>
<p>3. minHoverUnit</p>*/
private ArrayList<Unit> getTimeUnitRange(long statNumber) {
ArrayList<Unit> 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. */ /** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */
private String getSubStatTypeName(Statistic.Type statType) { private String getSubStatTypeName(Statistic.Type statType) {
String subStat = "sub-statistic"; String subStat = "sub-statistic";
@ -174,120 +323,30 @@ public class MessageWriter {
return subStat; return subStat;
} }
public TextComponent usageExamples(boolean isBukkitConsole) { /** Replace "_" with " " and capitalize each first letter of the input.
TextColor mainColor = isBukkitConsole ? PluginColor.GOLD.getConsoleColor() : PluginColor.GOLD.getColor(); @param input String to prettify, case-insensitive*/
TextColor accentColor1 = isBukkitConsole ? PluginColor.MEDIUM_GOLD.getConsoleColor() : PluginColor.MEDIUM_GOLD.getColor(); private String getPrettyName(String input) {
TextColor accentColor3 = isBukkitConsole ? PluginColor.LIGHT_YELLOW.getConsoleColor() : PluginColor.LIGHT_YELLOW.getColor(); if (input == null) return null;
String arrow = isBukkitConsole ? " -> " : ""; //4 spaces, alt + 26, 1 space StringBuilder capitals = new StringBuilder(input.toLowerCase());
capitals.setCharAt(0, Character.toUpperCase(capitals.charAt(0)));
while (capitals.indexOf("_") != -1) {
MyLogger.replacingUnderscores();
return Component.newline() int index = capitals.indexOf("_");
.append(componentFactory.prefixTitle(isBukkitConsole)) capitals.setCharAt(index + 1, Character.toUpperCase(capitals.charAt(index + 1)));
.append(newline()) capitals.setCharAt(index, ' ');
.append(text("Examples: ").color(mainColor)) }
.append(newline()) return capitals.toString();
.append(text(arrow).color(mainColor) }
.append(text("/statistic ")
.append(text("animals_bred ").color(accentColor1) public TextComponent usageExamples(boolean isBukkitConsole) {
.append(text("top").color(accentColor3))))) return new ExampleMessage(componentFactory, isBukkitConsole);
.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"))))));
} }
public TextComponent helpMsg(boolean isConsoleSender) { public TextComponent helpMsg(boolean isConsoleSender) {
if (isConsoleSender || !config.useHoverText()) { return new HelpMessage(componentFactory,
return helpMsgPlain(isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")); config.useHoverText() && !isConsoleSender,
} isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit"),
else { config.getTopListMaxSize());
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));
} }
} }

View File

@ -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;
}
}

View File

@ -22,9 +22,9 @@ public class PrideComponentFactory extends ComponentFactory {
@Override @Override
public TextComponent prefixTitle(boolean isBukkitConsole) { public TextComponent prefixTitleComponent(boolean isBukkitConsole) {
if (cancelRainbow(isBukkitConsole)) { if (cancelRainbow(isBukkitConsole)) {
return super.prefixTitle(isBukkitConsole); return super.prefixTitleComponent(isBukkitConsole);
} }
else { else {
String title = "<rainbow:16>____________ [PlayerStats] ____________</rainbow>"; //12 underscores String title = "<rainbow:16>____________ [PlayerStats] ____________</rainbow>"; //12 underscores
@ -35,9 +35,9 @@ public class PrideComponentFactory extends ComponentFactory {
} }
@Override @Override
public TextComponent pluginPrefix(boolean isConsoleSender) { public TextComponent pluginPrefixComponent(boolean isConsoleSender) {
if (cancelRainbow(isConsoleSender)) { if (cancelRainbow(isConsoleSender)) {
return super.pluginPrefix(isConsoleSender); return super.pluginPrefixComponent(isConsoleSender);
} }
return text() return text()
.append(MiniMessage.miniMessage() .append(MiniMessage.miniMessage()
@ -53,7 +53,7 @@ public class PrideComponentFactory extends ComponentFactory {
"<#01c1a7>a</#01c1a7>" + "<#01c1a7>a</#01c1a7>" +
"<#0690d4>t</#0690d4>" + "<#0690d4>t</#0690d4>" +
"<#205bf3>s</#205bf3>" + "<#205bf3>s</#205bf3>" +
"<#6c15fa>] </#6c15fa>")) "<#6c15fa>]</#6c15fa>"))
.build(); .build();
} }

View File

@ -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<Component> children() {
return exampleMessage.children();
}
@Override
public @NotNull TextComponent children(@NotNull List<? extends ComponentLike> 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);
}
}

View File

@ -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<Component> children() {
return helpMessage.children();
}
@Override
public @NotNull TextComponent children(@NotNull List<? extends ComponentLike> 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();
}
}

View File

@ -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 com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Statistic; import org.bukkit.Statistic;
@ -12,17 +13,14 @@ import java.util.HashMap;
public class LanguageKeyHandler { public class LanguageKeyHandler {
private final static HashMap<Statistic, String> statNameKeys; private final HashMap<Statistic, String> statNameKeys;
static { public LanguageKeyHandler() {
statNameKeys = new HashMap<>(); statNameKeys = new HashMap<>();
generateStatNameKeys(); generateStatNameKeys();
} }
private LanguageKeyHandler() { public String getStatKey(@NotNull Statistic statistic) {
}
public static String getStatKey(@NotNull Statistic statistic) {
if (statistic.getType() == Statistic.Type.UNTYPED) { if (statistic.getType() == Statistic.Type.UNTYPED) {
return "stat.minecraft." + statNameKeys.get(statistic); return "stat.minecraft." + statNameKeys.get(statistic);
} }
@ -33,7 +31,7 @@ public class LanguageKeyHandler {
/** Get the official Key from the NameSpacedKey for this entityType, /** Get the official Key from the NameSpacedKey for this entityType,
or return null if no enum constant can be retrieved or entityType is UNKNOWN.*/ 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; if (entity == null || entity == EntityType.UNKNOWN) return null;
else { else {
return "entity.minecraft." + entity.getKey().getKey(); return "entity.minecraft." + entity.getKey().getKey();
@ -42,7 +40,7 @@ public class LanguageKeyHandler {
/** Get the official Key from the NameSpacedKey for this item Material, /** Get the official Key from the NameSpacedKey for this item Material,
or return null if no enum constant can be retrieved.*/ 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; if (item == null) return null;
else if (item.isBlock()) { else if (item.isBlock()) {
return getBlockKey(item); return getBlockKey(item);
@ -54,7 +52,7 @@ public class LanguageKeyHandler {
/** Returns the official Key from the NameSpacedKey for the block Material provided, /** Returns the official Key from the NameSpacedKey for the block Material provided,
or return null if no enum constant can be retrieved.*/ 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; 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 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_", ""); 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())); 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 //get the enum names for all statistics first
generateDefaultKeys(); generateDefaultKeys();

View File

@ -111,9 +111,9 @@ public class ReloadThread extends Thread {
MyLogger.actionCreated((offlinePlayers != null) ? offlinePlayers.length : 0); MyLogger.actionCreated((offlinePlayers != null) ? offlinePlayers.length : 0);
ForkJoinPool.commonPool().invoke(task); ForkJoinPool.commonPool().invoke(task);
MyLogger.actionFinished(1); MyLogger.actionFinished(1);
MyLogger.logTimeTaken("ReloadThread",
("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time);
OfflinePlayerHandler.updateOfflinePlayerList(playerMap); OfflinePlayerHandler.updateOfflinePlayerList(playerMap);
MyLogger.logTimeTaken("ReloadThread",
("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time);
} }
} }

View File

@ -30,26 +30,6 @@ public class StatRequest {
playerFlag = false; 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() { public @NotNull CommandSender getCommandSender() {
return sender; return sender;
} }

View File

@ -112,6 +112,18 @@ public class EnumHandler {
return statNames.contains(statName.toLowerCase()); 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 */ /** Returns the names of all general statistics in lowercase */
public static List<String> getStatNames() { public static List<String> getStatNames() {
return statNames; return statNames;

View File

@ -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) { public static void logMsg(String content, boolean logAsWarning) {
logMsg(content, DebugLevel.LOW, logAsWarning); logMsg(content, DebugLevel.LOW, logAsWarning);
} }
@ -129,7 +133,7 @@ public class MyLogger {
if (debugLevel != DebugLevel.LOW) { if (debugLevel != DebugLevel.LOW) {
threadNames = new ConcurrentHashMap<>(); threadNames = new ConcurrentHashMap<>();
playersIndex.set(0); 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; processedPlayers[nextPlayersIndex() % 10] = playerName;
} }
else if (debugLevel == DebugLevel.MEDIUM) { else if (debugLevel == DebugLevel.MEDIUM || debugLevel == DebugLevel.HIGH && thread == 2) {
nextPlayersIndex(); nextPlayersIndex();
} }
} }
@ -179,8 +183,10 @@ public class MyLogger {
if (debugLevel != DebugLevel.LOW) { if (debugLevel != DebugLevel.LOW) {
logger.info("Finished Recursive Action! In total " + logger.info("Finished Recursive Action! In total " +
threadNames.size() + " Threads were used to process " + threadNames.size() + " Threads were used to process " +
playersIndex.get() + " Players: " + playersIndex.get() + " Players.");
Collections.list(threadNames.keys())); }
if (debugLevel == DebugLevel.HIGH) {
logger.info(Collections.list(threadNames.keys()).toString());
} }
} }

View File

@ -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);
}
}

View File

@ -1,7 +1,7 @@
# ------------------------------------------------------------------------------------------------------ # # ------------------------------------------------------------------------------------------------------ #
# PlayerStats Configuration # # 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 # How much output you'll get in the server console while PlayerStats is processing
# 1 = low (only show unexpected errors) # 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) # 3 = high (log all tasks and time taken)
debug-level: 1 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 # The actual translation is handled by the Minecraft language files and happens automatically
translate-to-client-language: true 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 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 # Automatically use themed formatting for the duration of certain holidays or festivals
enable-festive-formatting: true enable-festive-formatting: true