PlayerStats 1.1: "config-version" doesn't exist.
- PlayerStats 1.2: "config-version" is 2.
- PlayerStats 1.3: "config-version" is 3.
- PlayerStats 1.4: "config-version" is 4.
*/
+ /** Checks the number that "config-version" returns to see if the config needs updating, and if so, send it to the {@link ConfigUpdateHandler}.
+ 2 = medium (detail all encountered exceptions, log main tasks and show time taken)
- Default: Hearts for plain text, HP for hover-text.
*/
- public String getDamageUnit(boolean isHoverText) {
- return getUnitString(isHoverText, "hearts", "hp", "damage-unit");
+ DARK_PURPLE: #6E3485 (used for default sub-titles, title-underscores and brackets)
- GOLD: ChatColor Gold (used for first parts of usage messages and for first parts of hover-text accent)
- MEDIUM_GOLD: #FFD52B (used for second parts of usage messages and for second parts of hover-text accent)
- formattedPlayerStatFunction(int stat, @NotNull StatRequest request) {
+ /** Returns a BiFunction for a player statistic. This BiFunction will return a formattedValue,
+ the shape of which is determined by the 2 parameters the BiFunction gets.
+ - Integer shareCode: if a shareCode is provided, a clickable "share" button will be added.
+
- CommandSender sender: if a sender is provided, a signature with "shared by sender-name" will be added.
+
- If both parameters are null, the formattedValue will be returned as is.*/
+ public BiFunction formattedPlayerStatFunction(int stat, @NotNull StatRequest statRequest) {
TextComponent playerStat = Component.text()
- .append(componentFactory.playerName(request.getPlayerName(), Target.PLAYER)
+ .append(componentFactory.playerName(statRequest.getPlayerName(), Target.PLAYER)
.append(text(":"))
.append(space()))
- .append(getStatNumberComponent(request.getStatistic(), stat, Target.PLAYER, request.isConsoleSender()))
+ .append(getStatNumberComponent(statRequest, stat))
.append(space())
- .append(getStatNameComponent(request))
- .append(getStatUnitComponent(request.getStatistic(), request.getSelection(), request.isConsoleSender())) //space is provided by statUnitComponent
+ .append(getStatNameComponent(statRequest))
+ .append(getStatUnitComponent(statRequest.getStatistic(), statRequest.getTarget())) //space is provided by statUnitComponent
.build();
return getFormattingFunction(playerStat, Target.PLAYER);
}
- public BiFunction formattedServerStatFunction(long stat, @NotNull StatRequest request) {
+ /** Returns a BiFunction for a server statistic. This BiFunction will return a formattedValue,
+ the shape of which is determined by the 2 parameters the BiFunction gets.
+ - Integer shareCode: if a shareCode is provided, a clickable "share" button will be added.
+
- CommandSender sender: if a sender is provided, a signature with "shared by sender-name" will be added.
+
- If both parameters are null, the formattedValue will be returned as is.*/
+ public BiFunction formattedServerStatFunction(long stat, @NotNull StatRequest statRequest) {
TextComponent serverStat = text()
.append(componentFactory.title(config.getServerTitle(), Target.SERVER))
.append(space())
.append(componentFactory.serverName(config.getServerName()))
.append(space())
- .append(getStatNumberComponent(request.getStatistic(), stat, Target.SERVER, request.isConsoleSender()))
+ .append(getStatNumberComponent(statRequest, stat))
.append(space())
- .append(getStatNameComponent(request))
- .append(getStatUnitComponent(request.getStatistic(), request.getSelection(), request.isConsoleSender())) //space is provided by statUnit
+ .append(getStatNameComponent(statRequest))
+ .append(getStatUnitComponent(statRequest.getStatistic(), statRequest.getTarget())) //space is provided by statUnit
.build();
return getFormattingFunction(serverStat, Target.SERVER);
}
- public BiFunction formattedTopStatFunction(@NotNull LinkedHashMap topStats, @NotNull StatRequest request) {
- final TextComponent title = getTopStatsTitleComponent(request, topStats.size());
- final TextComponent shortTitle = getTopStatsTitleShortComponent(request, topStats.size());
- final TextComponent list = getTopStatListComponent(topStats, request);
+ /** Returns a BiFunction for a top statistic. This BiFunction will return a formattedValue,
+ the shape of which is determined by the 2 parameters the BiFunction gets.
+ - Integer shareCode: if a shareCode is provided, a clickable "share" button will be added.
+
- CommandSender sender: if a sender is provided, a signature with "shared by sender-name" will be added.
+
- If both parameters are null, the formattedValue will be returned as is.*/
+ public BiFunction formattedTopStatFunction(@NotNull LinkedHashMap topStats, @NotNull StatRequest statRequest) {
+ final TextComponent title = getTopStatsTitleComponent(statRequest, topStats.size());
+ final TextComponent shortTitle = getTopStatDescription(statRequest, topStats.size());
+ final TextComponent list = getTopStatListComponent(topStats, statRequest);
final boolean useEnters = config.useEnters(Target.TOP, false);
final boolean useEntersForShared = config.useEnters(Target.TOP, true);
@@ -241,9 +268,226 @@ public class MessageWriter {
};
}
- private BiFunction getFormattingFunction(@NotNull TextComponent statResult, Target selection) {
- boolean useEnters = config.useEnters(selection, false);
- boolean useEntersForShared = config.useEnters(selection, true);
+ public TextComponent singleTopStatLine(int positionInTopList, String playerName, long statNumber, Statistic statistic) {
+ TextComponent.Builder topStatLineBuilder = Component.text()
+ .append(space())
+ .append(componentFactory.rankNumber(positionInTopList))
+ .append(space());
+
+ if (config.useDots()) {
+ topStatLineBuilder.append(getPlayerNameWithDotsComponent(positionInTopList, playerName));
+ } else {
+ topStatLineBuilder.append(componentFactory.playerName(playerName + ":", Target.TOP));
+ }
+
+ return topStatLineBuilder
+ .append(space())
+ .append(getStatNumberComponent(statistic, Target.TOP, statNumber))
+ .build();
+ }
+
+ private Component getSharerNameComponent(CommandSender sender) {
+ if (sender instanceof Player player) {
+ Component senderName = EasterEggProvider.getPlayerName(player);
+ if (senderName != null) {
+ return senderName;
+ }
+ }
+ return componentFactory.sharerName(sender.getName());
+ }
+
+ private TextComponent getTopStatsTitleComponent(StatRequest statRequest, int statListSize) {
+ return Component.text()
+ .append(componentFactory.pluginPrefix()).append(space())
+ .append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)).append(space())
+ .append(componentFactory.titleNumber(statListSize)).append(space())
+ .append(getStatNameComponent(statRequest)) //space is provided by statUnitComponent
+ .append(getStatUnitComponent(statRequest.getStatistic(), statRequest.getTarget()))
+ .build();
+ }
+
+ private TextComponent getTopStatDescription(StatRequest statRequest, int statListSize) {
+ return Component.text()
+ .append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)).append(space())
+ .append(componentFactory.titleNumber(statListSize)).append(space())
+ .append(getStatNameComponent(statRequest)) //space is provided by statUnitComponent
+ .build();
+ }
+
+ private TextComponent getTopStatListComponent(LinkedHashMap topStats, StatRequest statRequest) {
+ TextComponent.Builder topList = Component.text();
+ Set playerNames = topStats.keySet();
+ boolean useDots = config.useDots();
+
+ int count = 0;
+ for (String playerName : playerNames) {
+ topList.append(newline())
+ .append(space())
+ .append(componentFactory.rankNumber(++count))
+ .append(space());
+ if (useDots) {
+ topList.append(getPlayerNameWithDotsComponent(count, playerName));
+ }
+ else {
+ topList.append(componentFactory.playerName(playerName + ":", Target.TOP));
+ }
+ topList.append(space()).append(getStatNumberComponent(statRequest, topStats.get(playerName)));
+ }
+ return topList.build();
+ }
+
+ private TextComponent getPlayerNameWithDotsComponent(int positionInTopList, String playerName) {
+ int dots = FontUtils.getNumberOfDotsToAlign(positionInTopList + ". " + playerName, isConsoleBuilder, config.playerNameIsBold());
+
+ TextComponent.Builder nameWithDots = Component.text()
+ .append(componentFactory.playerName(playerName, Target.TOP))
+ .append(space());
+ if (dots >= 1) {
+ nameWithDots.append(componentFactory.dots().append(text(".".repeat(dots))));
+ }
+ return nameWithDots.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 statRequest) {
+ if (config.useTranslatableComponents()) {
+ String statKey = languageKeyHandler.getStatKey(statRequest.getStatistic());
+ String subStatKey = statRequest.getSubStatEntryName();
+ if (subStatKey != null) {
+ switch (statRequest.getStatistic().getType()) {
+ case BLOCK -> subStatKey = languageKeyHandler.getBlockKey(statRequest.getBlock());
+ case ENTITY -> subStatKey = languageKeyHandler.getEntityKey(statRequest.getEntity());
+ case ITEM -> subStatKey = languageKeyHandler.getItemKey(statRequest.getItem());
+ default -> {
+ }
+ }
+ }
+ return componentFactory.statAndSubStatNameTranslatable(statKey, subStatKey, statRequest.getTarget());
+ }
+ else {
+ return componentFactory.statAndSubStatName(
+ StringUtils.prettify(statRequest.getStatistic().toString()),
+ StringUtils.prettify(statRequest.getSubStatEntryName()),
+ statRequest.getTarget());
+ }
+ }
+
+ private TextComponent getStatNumberComponent(StatRequest request, long statNumber) {
+ return getStatNumberComponent(request.getStatistic(), request.getTarget(), statNumber);
+ }
+
+ private TextComponent getStatNumberComponent(Statistic statistic, Target target, long statNumber) {
+ Unit.Type statUnitType = Unit.getTypeFromStatistic(statistic);
+ return switch (statUnitType) {
+ case DISTANCE -> getDistanceNumberComponent(statNumber, target);
+ case DAMAGE -> getDamageNumberComponent(statNumber, target);
+ case TIME -> getTimeNumberComponent(statNumber, target);
+ default -> getDefaultNumberComponent(statNumber, target);
+ };
+ }
+
+ private TextComponent getDistanceNumberComponent(long statNumber, Target target) {
+ Unit statUnit = Unit.fromString(config.getDistanceUnit(false));
+ String prettyNumber = formatter.formatDistanceNumber(statNumber, statUnit);
+ if (!useHoverText) {
+ return componentFactory.distanceNumber(prettyNumber, target);
+ }
+
+ Unit hoverUnit = Unit.fromString(config.getDistanceUnit(true));
+ String hoverNumber = formatter.formatDistanceNumber(statNumber, hoverUnit);
+ if (config.useTranslatableComponents()) {
+ String unitKey = languageKeyHandler.getUnitKey(hoverUnit);
+ if (unitKey != null) {
+ return componentFactory.distanceNumberWithTranslatableHoverText(prettyNumber, hoverNumber, unitKey, target);
+ }
+ }
+ return componentFactory.distanceNumberWithHoverText(prettyNumber, hoverNumber, hoverUnit.getLabel(), target);
+ }
+
+ private TextComponent getDamageNumberComponent(long statNumber, Target target) {
+ Unit statUnit = Unit.fromString(config.getDamageUnit(false));
+ String prettyNumber = formatter.formatDamageNumber(statNumber, statUnit);
+ if (!useHoverText) {
+ return componentFactory.damageNumber(prettyNumber, target);
+ }
+
+ Unit hoverUnit = Unit.fromString(config.getDamageUnit(true));
+ String prettyHoverNumber = formatter.formatDamageNumber(statNumber, hoverUnit);
+ if (hoverUnit == Unit.HEART) {
+ return componentFactory.damageNumberWithHeartUnitInHoverText(prettyNumber, prettyHoverNumber, target);
+ }
+ return componentFactory.damageNumberWithHoverText(prettyNumber, prettyHoverNumber, hoverUnit.getLabel(), target);
+ }
+
+ private TextComponent getTimeNumberComponent(long statNumber, Target target) {
+ ArrayList unitRange = getTimeUnitRange(statNumber);
+ if (unitRange.size() <= 1 || (useHoverText && unitRange.size() <= 3)) {
+ MyLogger.logMsg(
+ "There is something wrong with the time-units you specified, please check your config!",
+ true);
+ return componentFactory.statNumber("-", target);
+ }
+ else {
+ String mainNumber = formatter.formatTimeNumber(statNumber, unitRange.get(0), unitRange.get(1));
+ if (!useHoverText) {
+ return componentFactory.statNumber(mainNumber, target);
+ } else {
+ String hoverNumber = formatter.formatTimeNumber(statNumber, unitRange.get(2), unitRange.get(3));
+ MyLogger.logMsg("mainNumber: " + mainNumber + ", hoverNumber: " + hoverNumber, DebugLevel.HIGH);
+ return componentFactory.statNumberWithHoverText(mainNumber, hoverNumber,
+ null, null, target);
+ }
+ }
+ }
+
+ private TextComponent getDefaultNumberComponent(long statNumber, Target target) {
+ return componentFactory.statNumber(formatter.formatNumber(statNumber), target);
+ }
+
+ private TextComponent getStatUnitComponent(Statistic statistic, Target target) {
+ return switch (Unit.getTypeFromStatistic(statistic)) {
+ case DAMAGE -> getDamageUnit(target);
+ case DISTANCE -> getDistanceUnit(target);
+ default -> Component.empty();
+ };
+ }
+
+ private TextComponent getDistanceUnit(Target target) {
+ Unit statUnit = Unit.fromString(config.getDistanceUnit(false));
+ if (config.useTranslatableComponents()) {
+ String unitKey = languageKeyHandler.getUnitKey(statUnit);
+ if (unitKey != null) {
+ return Component.space()
+ .append(componentFactory.statUnitTranslatable(unitKey, target));
+ }
+ }
+ return Component.space()
+ .append(componentFactory.statUnit(statUnit.getLabel(), target));
+ }
+
+ private TextComponent getDamageUnit(Target target) {
+ Unit statUnit = Unit.fromString(config.getDamageUnit(false));
+ if (statUnit == Unit.HEART) {
+ TextComponent heartUnit;
+ if (isConsoleBuilder) {
+ heartUnit = componentFactory.consoleHeart();
+ } else if (useHoverText) {
+ heartUnit = componentFactory.clientHeartWithHoverText();
+ } else {
+ heartUnit = componentFactory.clientHeart(false);
+ }
+ return Component.space()
+ .append(heartUnit);
+ }
+ return Component.space()
+ .append(componentFactory.statUnit(statUnit.getLabel(), target));
+ }
+
+
+ private BiFunction getFormattingFunction(@NotNull TextComponent statResult, Target target) {
+ boolean useEnters = config.useEnters(target, false);
+ boolean useEntersForShared = config.useEnters(target, true);
return (shareCode, sender) -> {
TextComponent.Builder statBuilder = text();
@@ -278,141 +522,6 @@ public class MessageWriter {
};
}
- private Component getSharerNameComponent(CommandSender sender) {
- if (sender instanceof Player player) {
- Component senderName = EasterEggProvider.getPlayerName(player);
- if (senderName != null) {
- return senderName;
- }
- }
- return componentFactory.sharerName(sender.getName());
- }
-
- private TextComponent getTopStatsTitleComponent(StatRequest request, int statListSize) {
- return Component.text()
- .append(componentFactory.pluginPrefix()).append(space())
- .append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)).append(space())
- .append(componentFactory.titleNumber(statListSize)).append(space())
- .append(getStatNameComponent(request)) //space is provided by statUnitComponent
- .append(getStatUnitComponent(request.getStatistic(), request.getSelection(), request.isConsoleSender()))
- .build();
- }
-
- private TextComponent getTopStatsTitleShortComponent(StatRequest request, int statListSize) {
- return Component.text()
- .append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)).append(space())
- .append(componentFactory.titleNumber(statListSize)).append(space())
- .append(getStatNameComponent(request)) //space is provided by statUnitComponent
- .build();
- }
-
- private TextComponent getTopStatListComponent(LinkedHashMap topStats, StatRequest request) {
- TextComponent.Builder topList = Component.text();
- boolean useDots = config.useDots();
- boolean boldNames = config.playerNameIsBold();
- Set playerNames = topStats.keySet();
-
- int count = 0;
- for (String playerName : playerNames) {
- TextComponent.Builder playerNameBuilder = componentFactory.playerName(playerName, Target.TOP).toBuilder();
- topList.append(newline())
- .append(componentFactory.rankNumber(" " + ++count + "."))
- .append(space());
- if (useDots) {
- topList.append(playerNameBuilder)
- .append(space());
- int dots = FontUtils.getNumberOfDotsToAlign(count + ". " + playerName, request.isConsoleSender(), boldNames);
- if (dots >= 1) {
- topList.append(componentFactory.dots().append(text((".".repeat(dots)))));
- }
- }
- else {
- topList.append(playerNameBuilder.append(text(":")));
- }
- topList.append(space()).append(getStatNumberComponent(request.getStatistic(), topStats.get(playerName), Target.TOP, request.isConsoleSender()));
- }
- return topList.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.statAndSubStatNameTranslatable(statKey, subStatKey, request.getSelection());
- }
- else {
- return componentFactory.statAndSubStatName(
- StringUtils.prettify(request.getStatistic().toString()),
- StringUtils.prettify(request.getSubStatEntry()),
- request.getSelection());
- }
- }
-
- private TextComponent getStatNumberComponent(Statistic statistic, long statNumber, Target selection, boolean isConsoleSender) {
- 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.statNumber(prettyNumber, selection);
- }
- 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 + ", hoverNumber: " + prettyHoverNumber, DebugLevel.HIGH);
-
- if (hoverUnit == Unit.HEART) {
- return componentFactory.damageNumberWithHoverText(
- prettyNumber, prettyHoverNumber,
- componentFactory.heart(isConsoleSender, true), selection);
- }
- if (config.useTranslatableComponents()) {
- String unitKey = languageKeyHandler.getUnitKey(hoverUnit);
- if (unitKey != null) {
- return componentFactory.statNumberWithHoverText(prettyNumber, prettyHoverNumber, null, unitKey, selection);
- }
- }
- return componentFactory.statNumberWithHoverText(prettyNumber, prettyHoverNumber, hoverUnit.getLabel(), null, selection);
- }
-
- private TextComponent getTimeNumberComponent(long statNumber, Target selection, ArrayList unitRange) {
- if (unitRange.size() <= 1 || (config.useHoverText() && unitRange.size() <= 3)) {
- MyLogger.logMsg(
- "There is something wrong with the time-units you specified, please check your config!",
- true);
- return componentFactory.statNumber("-", selection);
- }
- else {
- String mainNumber = formatter.format(statNumber, unitRange.get(0), unitRange.get(1));
- if (!config.useHoverText()) {
- return componentFactory.statNumber(mainNumber, selection);
- } else {
- String hoverNumber = formatter.format(statNumber, unitRange.get(2), unitRange.get(3));
- MyLogger.logMsg("mainNumber: " + mainNumber + ", hoverNumber: " + hoverNumber, DebugLevel.HIGH);
- return componentFactory.statNumberWithHoverText(mainNumber, hoverNumber,
- null, null, selection);
- }
- }
- }
-
/** Get an ArrayList consisting of 2 or 4 timeUnits. The order of items is:
0. maxUnit
1. minUnit
@@ -429,7 +538,7 @@ public class MessageWriter {
unitRange.add(bigUnit);
unitRange.add(bigUnit.getSmallerUnit(config.getNumberOfExtraTimeUnits(false)));
}
- if (config.useHoverText()) {
+ if (useHoverText) {
if (!config.autoDetectTimeUnit(true)) {
unitRange.add(Unit.fromString(config.getTimeUnit(true)));
unitRange.add(Unit.fromString(config.getTimeUnit(true, true)));
@@ -444,28 +553,15 @@ public class MessageWriter {
return unitRange;
}
- private TextComponent getStatUnitComponent(Statistic statistic, Target selection, boolean isConsoleSender) {
- 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();
- }
+ /** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */
+ public static String getSubStatTypeName(Statistic.Type statType) {
+ String subStat = "sub-statistic";
+ if (statType == null) return subStat;
+ switch (statType) {
+ case BLOCK -> subStat = "block";
+ case ENTITY -> subStat = "entity";
+ case ITEM -> subStat = "item";
}
- if (config.useTranslatableComponents()) {
- String unitKey = languageKeyHandler.getUnitKey(statUnit);
- if (unitKey != null) {
- return Component.space()
- .append(componentFactory.statUnit(null, unitKey, selection));
- }
- }
- String statName = statUnit.getLabel();
- if (statUnit == Unit.HEART) { //console can do u2665, u2764 looks better in-game
- return Component.space()
- .append(componentFactory.heart(isConsoleSender, false));
- }
- return Component.space()
- .append(componentFactory.statUnit(statName, null, selection));
+ return subStat;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/OutputManager.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/OutputManager.java
index 5ffe3b1..161232f 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/OutputManager.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/OutputManager.java
@@ -1,10 +1,11 @@
package com.gmail.artemis.the.gr8.playerstats.msg;
-import com.gmail.artemis.the.gr8.playerstats.Main;
import com.gmail.artemis.the.gr8.playerstats.ShareManager;
+import com.gmail.artemis.the.gr8.playerstats.api.StatFormatter;
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
-import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
+import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentFactory;
+import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.msg.components.BukkitConsoleComponentFactory;
import com.gmail.artemis.the.gr8.playerstats.msg.components.PrideComponentFactory;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
@@ -14,162 +15,210 @@ import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.time.LocalDate;
import java.time.Month;
import java.util.EnumMap;
import java.util.LinkedHashMap;
-import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Function;
import static com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage.*;
-public final class OutputManager {
-
- private static volatile OutputManager instance;
+/** This class manages all PlayerStats output. It is the only place where messages are sent.
+ It gets the messages from a {@link MessageBuilder}, which is different for a Console as for Players
+ (mainly to deal with the lack of hover-text, and for Bukkit consoles to make up for the lack of hex-colors).*/
+public final class OutputManager implements StatFormatter {
private static BukkitAudiences adventure;
+ private static ConfigHandler config;
private static ShareManager shareManager;
- private static MessageWriter writer;
- private static MessageWriter consoleWriter;
+ private static MessageBuilder messageBuilder;
+ private static MessageBuilder consoleMessageBuilder;
- private static EnumMap> standardMessages;
+ private static EnumMap> standardMessages;
- private OutputManager(ConfigHandler config) {
- adventure = Main.adventure();
- shareManager = ShareManager.getInstance(config);
+ public OutputManager(BukkitAudiences adventure, ConfigHandler config, ShareManager shareManager) {
+ OutputManager.adventure = adventure;
+ OutputManager.config = config;
+ OutputManager.shareManager = shareManager;
- getMessageWriters(config);
+ getMessageBuilders();
prepareFunctions();
}
- public static OutputManager getInstance(ConfigHandler config) {
- OutputManager outputManager = instance;
- if (outputManager != null) {
- return outputManager;
- }
- synchronized (OutputManager.class) {
- if (instance == null) {
- instance = new OutputManager(config);
- }
- return instance;
- }
+ public static void updateMessageBuilders() {
+ getMessageBuilders();
}
- public void updateMessageWriters(ConfigHandler config) {
- getMessageWriters(config);
+ @Override
+ public TextComponent getPluginPrefix() {
+ ComponentFactory factory = new ComponentFactory(config);
+ return factory.pluginPrefix();
}
- public void sendFeedbackMsg(CommandSender sender, StandardMessage message) {
+ @Override
+ public TextComponent getRainbowPluginPrefix() {
+ ComponentFactory prideFactory = new PrideComponentFactory(config);
+ return prideFactory.pluginPrefix();
+ }
+
+ @Override
+ public TextComponent getPluginPrefixAsTitle() {
+ ComponentFactory factory = new ComponentFactory(config);
+ return factory.pluginPrefixAsTitle();
+ }
+
+ @Override
+ public TextComponent getRainbowPluginPrefixAsTitle() {
+ ComponentFactory prideFactory = new PrideComponentFactory(config);
+ return prideFactory.pluginPrefixAsTitle();
+ }
+
+ @Override
+ public TextComponent formatPlayerStat(@NotNull StatRequest statRequest, int playerStat) {
+ BiFunction playerStatFunction =
+ getMessageBuilder(statRequest).formattedPlayerStatFunction(playerStat, statRequest);
+
+ return processFunction(statRequest.getCommandSender(), playerStatFunction);
+ }
+
+ @Override
+ public TextComponent formatServerStat(@NotNull StatRequest statRequest, long serverStat) {
+ BiFunction serverStatFunction =
+ getMessageBuilder(statRequest).formattedServerStatFunction(serverStat, statRequest);
+
+ return processFunction(statRequest.getCommandSender(), serverStatFunction);
+ }
+
+ @Override
+ public TextComponent formatSingleTopStatLine(int positionInTopList, String playerName, long statNumber, Statistic statistic) {
+ return messageBuilder.singleTopStatLine(positionInTopList, playerName, statNumber, statistic);
+ }
+
+ @Override
+ public TextComponent formatTopStat(@NotNull StatRequest statRequest, @NotNull LinkedHashMap topStats) {
+ BiFunction topStatFunction =
+ getMessageBuilder(statRequest).formattedTopStatFunction(topStats, statRequest);
+
+ return processFunction(statRequest.getCommandSender(), topStatFunction);
+ }
+
+ public void sendFeedbackMsg(@NotNull CommandSender sender, StandardMessage message) {
if (message != null) {
adventure.sender(sender).sendMessage(standardMessages.get(message)
- .apply(getWriter(sender)));
+ .apply(getMessageBuilder(sender)));
}
}
- public void sendFeedbackMsgWaitAMoment(CommandSender sender, boolean longWait) {
- adventure.sender(sender).sendMessage(getWriter(sender)
+ public void sendFeedbackMsgWaitAMoment(@NotNull CommandSender sender, boolean longWait) {
+ adventure.sender(sender).sendMessage(getMessageBuilder(sender)
.waitAMoment(longWait));
}
- public void sendFeedbackMsgMissingSubStat(CommandSender sender, Statistic.Type statType) {
- adventure.sender(sender).sendMessage(getWriter(sender)
+ public void sendFeedbackMsgMissingSubStat(@NotNull CommandSender sender, Statistic.Type statType) {
+ adventure.sender(sender).sendMessage(getMessageBuilder(sender)
.missingSubStatName(statType));
}
- public void sendFeedbackMsgWrongSubStat(CommandSender sender, Statistic.Type statType, String subStatName) {
+ public void sendFeedbackMsgWrongSubStat(@NotNull CommandSender sender, Statistic.Type statType, @Nullable String subStatName) {
if (subStatName == null) {
sendFeedbackMsgMissingSubStat(sender, statType);
} else {
- adventure.sender(sender).sendMessage(getWriter(sender)
+ adventure.sender(sender).sendMessage(getMessageBuilder(sender)
.wrongSubStatType(statType, subStatName));
}
}
- public void sendExamples(CommandSender sender) {
- adventure.sender(sender).sendMessage(getWriter(sender)
+ public void sendExamples(@NotNull CommandSender sender) {
+ adventure.sender(sender).sendMessage(getMessageBuilder(sender)
.usageExamples());
}
- public void sendHelp(CommandSender sender) {
- adventure.sender(sender).sendMessage(getWriter(sender)
- .helpMsg(sender instanceof ConsoleCommandSender));
+ public void sendHelp(@NotNull CommandSender sender) {
+ adventure.sender(sender).sendMessage(getMessageBuilder(sender)
+ .helpMsg());
}
- public void shareStatResults(@NotNull TextComponent statResult) {
- adventure.players().sendMessage(statResult);
+ public void sendToAllPlayers(@NotNull TextComponent component) {
+ adventure.players().sendMessage(component);
}
- public void sendPlayerStat(@NotNull StatRequest request, int playerStat) {
- CommandSender sender = request.getCommandSender();
- BiFunction buildFunction =
- getWriter(sender).formattedPlayerStatFunction(playerStat, request);
-
- processAndSend(sender, buildFunction);
+ public void sendToCommandSender(@NotNull CommandSender sender, @NotNull TextComponent component) {
+ adventure.sender(sender).sendMessage(component);
}
- public void sendServerStat(@NotNull StatRequest request, long serverStat) {
- CommandSender sender = request.getCommandSender();
- BiFunction buildFunction =
- getWriter(sender).formattedServerStatFunction(serverStat, request);
+ private TextComponent processFunction(CommandSender sender, @NotNull BiFunction statResultFunction) {
+ boolean saveOutput = !(sender instanceof ConsoleCommandSender) &&
+ ShareManager.isEnabled() &&
+ shareManager.senderHasPermission(sender);
- processAndSend(sender, buildFunction);
- }
-
- public void sendTopStat(@NotNull StatRequest request, LinkedHashMap topStats) {
- CommandSender sender = request.getCommandSender();
- BiFunction buildFunction =
- getWriter(sender).formattedTopStatFunction(topStats, request);
-
- processAndSend(sender, buildFunction);
- }
-
- private void processAndSend(CommandSender sender, BiFunction buildFunction) {
- if (shareManager.isEnabled() && shareManager.senderHasPermission(sender)) {
-
- UUID shareCode = shareManager.saveStatResult(sender.getName(), buildFunction.apply(null, sender));
- adventure.sender(sender).sendMessage(
- buildFunction.apply(shareCode, null));
+ if (saveOutput) {
+ int shareCode =
+ shareManager.saveStatResult(sender.getName(), statResultFunction.apply(null, sender));
+ return statResultFunction.apply(shareCode, null);
}
else {
- adventure.sender(sender).sendMessage(
- buildFunction.apply(null, null));
+ return statResultFunction.apply(null, null);
}
}
- private MessageWriter getWriter(CommandSender sender) {
- return sender instanceof ConsoleCommandSender ? consoleWriter : writer;
+ private MessageBuilder getMessageBuilder(CommandSender sender) {
+ return sender instanceof ConsoleCommandSender ? consoleMessageBuilder : messageBuilder;
}
- private void getMessageWriters(ConfigHandler config) {
- boolean isBukkit = Bukkit.getName().equalsIgnoreCase("CraftBukkit");
- if (config.useRainbowMode() ||
- (config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE))) {
- writer = MessageWriter.fromComponentFactory(config, new PrideComponentFactory(config));
- }
- else {
- writer = MessageWriter.defaultWriter(config);
- }
-
- if (!isBukkit) {
- consoleWriter = writer;
+ private MessageBuilder getMessageBuilder(StatRequest statRequest) {
+ if (statRequest.isAPIRequest() || !statRequest.isConsoleSender()) {
+ return messageBuilder;
} else {
- consoleWriter = MessageWriter.fromComponentFactory(config, new BukkitConsoleComponentFactory(config));
+ return consoleMessageBuilder;
}
}
+ private static void getMessageBuilders() {
+ messageBuilder = getClientMessageBuilder();
+ consoleMessageBuilder = getConsoleMessageBuilder();
+ }
+
+ private static MessageBuilder getClientMessageBuilder() {
+ if (useRainbowStyle()) {
+ return MessageBuilder.fromComponentFactory(config, new PrideComponentFactory(config));
+ }
+ return MessageBuilder.defaultBuilder(config);
+ }
+
+ private static MessageBuilder getConsoleMessageBuilder() {
+ MessageBuilder consoleBuilder;
+ if (isBukkit()) {
+ consoleBuilder = MessageBuilder.fromComponentFactory(config, new BukkitConsoleComponentFactory(config));
+ } else {
+ consoleBuilder = getClientMessageBuilder();
+ }
+ consoleBuilder.setConsoleBuilder(true);
+ consoleBuilder.toggleHoverUse(false);
+ return consoleBuilder;
+ }
+
+ private static boolean useRainbowStyle() {
+ return config.useRainbowMode() || (config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE));
+ }
+
+ private static boolean isBukkit() {
+ return Bukkit.getName().equalsIgnoreCase("CraftBukkit");
+ }
+
private void prepareFunctions() {
standardMessages = new EnumMap<>(StandardMessage.class);
- standardMessages.put(RELOADED_CONFIG, (MessageWriter::reloadedConfig));
- standardMessages.put(STILL_RELOADING, (MessageWriter::stillReloading));
- standardMessages.put(MISSING_STAT_NAME, (MessageWriter::missingStatName));
- standardMessages.put(MISSING_PLAYER_NAME, (MessageWriter::missingPlayerName));
- standardMessages.put(REQUEST_ALREADY_RUNNING, (MessageWriter::requestAlreadyRunning));
- standardMessages.put(STILL_ON_SHARE_COOLDOWN, (MessageWriter::stillOnShareCoolDown));
- standardMessages.put(RESULTS_ALREADY_SHARED, (MessageWriter::resultsAlreadyShared));
- standardMessages.put(STAT_RESULTS_TOO_OLD, (MessageWriter::statResultsTooOld));
- standardMessages.put(UNKNOWN_ERROR, (MessageWriter::unknownError));
+ standardMessages.put(RELOADED_CONFIG, (MessageBuilder::reloadedConfig));
+ standardMessages.put(STILL_RELOADING, (MessageBuilder::stillReloading));
+ standardMessages.put(MISSING_STAT_NAME, (MessageBuilder::missingStatName));
+ standardMessages.put(MISSING_PLAYER_NAME, (MessageBuilder::missingPlayerName));
+ standardMessages.put(REQUEST_ALREADY_RUNNING, (MessageBuilder::requestAlreadyRunning));
+ standardMessages.put(STILL_ON_SHARE_COOLDOWN, (MessageBuilder::stillOnShareCoolDown));
+ standardMessages.put(RESULTS_ALREADY_SHARED, (MessageBuilder::resultsAlreadyShared));
+ standardMessages.put(STAT_RESULTS_TOO_OLD, (MessageBuilder::statResultsTooOld));
+ standardMessages.put(UNKNOWN_ERROR, (MessageBuilder::unknownError));
}
}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/BukkitConsoleComponentFactory.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/BukkitConsoleComponentFactory.java
index 85c98a0..05a9f72 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/BukkitConsoleComponentFactory.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/BukkitConsoleComponentFactory.java
@@ -11,6 +11,8 @@ import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.Component.text;
+/** The {@link ComponentFactory} that is used to build messages for a Bukkit Console.
+ Bukkit consoles don't support hex colors, unlike Paper consoles.*/
public class BukkitConsoleComponentFactory extends ComponentFactory {
public BukkitConsoleComponentFactory(ConfigHandler config) {
@@ -22,6 +24,7 @@ public class BukkitConsoleComponentFactory extends ComponentFactory {
PREFIX = PluginColor.GOLD.getConsoleColor();
BRACKETS = PluginColor.GRAY.getConsoleColor();
UNDERSCORE = PluginColor.DARK_PURPLE.getConsoleColor();
+ HEARTS = PluginColor.RED.getConsoleColor();
MSG_MAIN = PluginColor.MEDIUM_BLUE.getConsoleColor();
MSG_ACCENT = PluginColor.BLUE.getConsoleColor();
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ComponentFactory.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ComponentFactory.java
index 938e332..f5a7162 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ComponentFactory.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ComponentFactory.java
@@ -4,6 +4,8 @@ import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.enums.Unit;
+import com.gmail.artemis.the.gr8.playerstats.msg.MessageBuilder;
+import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
@@ -18,15 +20,11 @@ import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-
-import java.util.UUID;
-
import static net.kyori.adventure.text.Component.*;
-import static net.kyori.adventure.text.Component.text;
-/** Creates Components with the desired formatting. This class can put Strings
- into formatted Components with TextColor and TextDecoration, or return empty Components
- or ComponentBuilders with the desired formatting.*/
+/** Creates Components with the desired formatting for the {@link MessageBuilder} to build messages with.
+ This class can put Strings into formatted Components with TextColor
+ and TextDecoration, or return empty Components with the desired formatting.*/
public class ComponentFactory {
private static ConfigHandler config;
@@ -34,6 +32,7 @@ public class ComponentFactory {
protected TextColor PREFIX; //gold
protected TextColor BRACKETS; //gray
protected TextColor UNDERSCORE; //dark_purple
+ protected TextColor HEARTS; //red
protected TextColor MSG_MAIN; //medium_blue
protected TextColor MSG_ACCENT; //blue
@@ -56,6 +55,7 @@ public class ComponentFactory {
PREFIX = PluginColor.GOLD.getColor();
BRACKETS = PluginColor.GRAY.getColor();
UNDERSCORE = PluginColor.DARK_PURPLE.getColor();
+ HEARTS = PluginColor.RED.getColor();
MSG_MAIN = PluginColor.MEDIUM_BLUE.getColor();
MSG_ACCENT = PluginColor.BLUE.getColor();
@@ -109,10 +109,10 @@ public class ComponentFactory {
return text().color(MSG_ACCENT).build();
}
- public TextComponent title(String content, Target selection) {
+ public TextComponent title(String content, Target target) {
return getComponent(content,
- getColorFromString(config.getTitleDecoration(selection, false)),
- getStyleFromString(config.getTitleDecoration(selection, true)));
+ getColorFromString(config.getTitleDecoration(target, false)),
+ getStyleFromString(config.getTitleDecoration(target, true)));
}
public TextComponent titleNumber(int number) {
@@ -121,8 +121,8 @@ public class ComponentFactory {
getStyleFromString(config.getTitleNumberDecoration(true)));
}
- public TextComponent rankNumber(String number) {
- return getComponent(number,
+ public TextComponent rankNumber(int number) {
+ return getComponent(number + ".",
getColorFromString(config.getRankNumberDecoration(false)),
getStyleFromString(config.getRankNumberDecoration(true)));
}
@@ -141,10 +141,10 @@ public class ComponentFactory {
.append(colon);
}
- public TextComponent playerName(String playerName, Target selection) {
+ public TextComponent playerName(String playerName, Target target) {
return getComponent(playerName,
- getColorFromString(config.getPlayerNameDecoration(selection, false)),
- getStyleFromString(config.getPlayerNameDecoration(selection, true)));
+ getColorFromString(config.getPlayerNameDecoration(target, false)),
+ getStyleFromString(config.getPlayerNameDecoration(target, true)));
}
public TextComponent sharerName(String sharerName) {
@@ -153,8 +153,8 @@ public class ComponentFactory {
getStyleFromString(config.getSharerNameDecoration(true)));
}
- public TextComponent shareButton(UUID shareCode) {
- return surroundingBrackets(
+ public TextComponent shareButton(int shareCode) {
+ return surroundWithBrackets(
text("Share")
.color(MSG_HOVER)
.clickEvent(ClickEvent.runCommand("/statshare " + shareCode))
@@ -163,7 +163,7 @@ public class ComponentFactory {
}
public TextComponent sharedByMessage(Component playerName) {
- return surroundingBrackets(
+ return surroundWithBrackets(
text().append(
getComponent("Shared by",
getColorFromString(config.getSharedByTextDecoration(false)),
@@ -174,7 +174,7 @@ public class ComponentFactory {
}
public TextComponent statResultInHoverText(TextComponent statResult) {
- return surroundingBrackets(
+ return surroundWithBrackets(
text().append(text("Hover Here")
.color(MSG_CLICKED)
.decorate(TextDecoration.ITALIC)
@@ -184,31 +184,31 @@ public class ComponentFactory {
/** @param prettyStatName a statName with underscores removed and each word capitalized
@param prettySubStatName if present, a subStatName with underscores removed and each word capitalized*/
- public TextComponent statAndSubStatName(String prettyStatName, @Nullable String prettySubStatName, Target selection) {
+ public TextComponent statAndSubStatName(String prettyStatName, @Nullable String prettySubStatName, Target target) {
TextComponent.Builder totalStatNameBuilder = getComponentBuilder(prettyStatName,
- getColorFromString(config.getStatNameDecoration(selection, false)),
- getStyleFromString(config.getStatNameDecoration(selection, true)));
- TextComponent subStat = subStatName(prettySubStatName, selection);
+ getColorFromString(config.getStatNameDecoration(target, false)),
+ getStyleFromString(config.getStatNameDecoration(target, true)));
+ TextComponent subStat = subStatName(prettySubStatName, target);
if (!subStat.equals(Component.empty())) {
totalStatNameBuilder
.append(space().decorations(TextDecoration.NAMES.values(), false))
- .append(subStatName(prettySubStatName, selection));
+ .append(subStatName(prettySubStatName, target));
}
return totalStatNameBuilder.build();
}
/** Returns a TextComponent with TranslatableComponent as a child.*/
- public TextComponent statAndSubStatNameTranslatable(String statKey, String subStatKey, Target selection) {
+ public TextComponent statAndSubStatNameTranslatable(String statKey, String subStatKey, Target target) {
TextComponent.Builder totalStatNameBuilder = getComponentBuilder(null,
- getColorFromString(config.getStatNameDecoration(selection, false)),
- getStyleFromString(config.getStatNameDecoration(selection, true)));
+ getColorFromString(config.getStatNameDecoration(target, false)),
+ getStyleFromString(config.getStatNameDecoration(target, true)));
- TextComponent subStat = subStatNameTranslatable(subStatKey, selection);
- if (statKey.equalsIgnoreCase("stat_type.minecraft.killed")) {
+ TextComponent subStat = subStatNameTranslatable(subStatKey, target);
+ if (LanguageKeyHandler.isKeyForKillEntity(statKey)) {
return totalStatNameBuilder.append(killEntityBuilder(subStat)).build();
}
- else if (statKey.equalsIgnoreCase("stat_type.minecraft.killed_by")) {
+ else if (LanguageKeyHandler.isKeyForEntityKilledBy(statKey)) {
return totalStatNameBuilder.append(entityKilledByBuilder(subStat)).build();
}
else {
@@ -222,63 +222,95 @@ public class ComponentFactory {
}
}
- public TextComponent statNumber(String prettyNumber, Target selection) {
+ public TextComponent statNumber(String prettyNumber, Target target) {
return getComponent(prettyNumber,
- getColorFromString(config.getStatNumberDecoration(selection, false)),
- getStyleFromString(config.getStatNumberDecoration(selection, true)));
+ getColorFromString(config.getStatNumberDecoration(target, false)),
+ getStyleFromString(config.getStatNumberDecoration(target, true)));
}
- public TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, Target selection) {
- return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, hoverUnitKey, null, selection);
+ public TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, Target target) {
+ return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, hoverUnitKey, null, target);
}
- public TextComponent damageNumberWithHoverText(String mainNumber, String hoverNumber, TextComponent heart, Target selection) {
- return statNumberWithHoverText(mainNumber, hoverNumber, null, null, heart, selection);
+ public TextComponent damageNumber(String prettyNumber, Target target) {
+ return statNumber(prettyNumber, target);
+ }
+ public TextComponent damageNumberWithHoverText(String mainNumber, String hoverNumber, String hoverUnitName, Target target) {
+ return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, null, null, target);
}
- public TextComponent statUnit(String unitName, String unitKey, Target selection) {
- if (!(unitName == null && unitKey == null)) {
- TextComponent.Builder statUnitBuilder = getComponentBuilder(null,
- getColorFromString(config.getSubStatNameDecoration(selection, false)),
- getStyleFromString(config.getSubStatNameDecoration(selection, true)));
- if (unitKey != null) {
- statUnitBuilder.append(translatable()
- .key(unitKey));
- } else {
- statUnitBuilder.append(text(unitName));
- }
- return surroundingBrackets(statUnitBuilder.build());
- }
- else {
- return Component.empty();
- }
+ public TextComponent damageNumberWithHeartUnitInHoverText(String mainNumber, String hoverNumber, Target target) {
+ return statNumberWithHoverText(mainNumber, hoverNumber, null, null, clientHeart(true), target);
}
- public TextComponent heart(boolean isConsoleSender, boolean isHoverUnit) {
- TextColor heartColor = TextColor.fromHexString("#FF1313");
- char heart = isConsoleSender ? '\u2665' : '\u2764';
- if (isHoverUnit) {
- return Component.text(heart).color(heartColor);
+ public TextComponent distanceNumber(String prettyNumber, Target target) {
+ return statNumber(prettyNumber, target);
+ }
+
+ public TextComponent distanceNumberWithHoverText(String mainNumber, String hoverNumber, String hoverUnitName, Target target) {
+ return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, null, target);
+ }
+
+ public TextComponent distanceNumberWithTranslatableHoverText(String mainNumber, String hoverNumber, String hoverUnitKey, Target target) {
+ return statNumberWithHoverText(mainNumber, hoverNumber, null, hoverUnitKey, target);
+ }
+
+ public TextComponent statUnit(String unitName, Target target) {
+ TextComponent statUnit = getComponentBuilder(unitName,
+ getColorFromString(config.getSubStatNameDecoration(target, false)),
+ getStyleFromString(config.getSubStatNameDecoration(target, true)))
+ .build();
+ return surroundWithBrackets(statUnit);
+ }
+
+ public TextComponent statUnitTranslatable(String unitKey, Target target) {
+ TextComponent statUnit = getComponentBuilder(null,
+ getColorFromString(config.getSubStatNameDecoration(target, false)),
+ getStyleFromString(config.getSubStatNameDecoration(target, true)))
+ .append(translatable()
+ .key(unitKey))
+ .build();
+ return surroundWithBrackets(statUnit);
+ }
+
+ public TextComponent clientHeart(boolean isDisplayedInHoverText) {
+ TextComponent basicHeartComponent = basicHeartComponent('\u2764');
+ if (isDisplayedInHoverText) {
+ return basicHeartComponent;
}
- TextComponent.Builder heartComponent = Component.text()
- .content(String.valueOf(heart))
- .color(heartColor);
- if (config.useHoverText()) {
- heartComponent.hoverEvent(HoverEvent.showText(
- text(Unit.HEART.getLabel())
- .color(MSG_HOVER_ACCENT)));
- }
- return surroundingBrackets(heartComponent.build());
+ return surroundWithBrackets(basicHeartComponent);
+ }
+
+ public TextComponent clientHeartWithHoverText() {
+ TextComponent basicHeartComponent = basicHeartComponent('\u2764')
+ .toBuilder()
+ .hoverEvent(HoverEvent.showText(
+ text(Unit.HEART.getLabel())
+ .color(MSG_HOVER_ACCENT)))
+ .build();
+ return surroundWithBrackets(basicHeartComponent);
+ }
+
+ public TextComponent consoleHeart() {
+ return surroundWithBrackets(basicHeartComponent('\u2665'));
+ }
+
+ //console can do u2665, u2764 looks better in-game
+ private TextComponent basicHeartComponent(char heartChar) {
+ return Component.text()
+ .content(String.valueOf(heartChar))
+ .color(HEARTS)
+ .build();
}
/** Returns a TextComponent for the subStatName, or an empty component.*/
- private TextComponent subStatName(@Nullable String prettySubStatName, Target selection) {
+ private TextComponent subStatName(@Nullable String prettySubStatName, Target target) {
if (prettySubStatName == null) {
return Component.empty();
} else {
return getComponentBuilder(null,
- getColorFromString(config.getSubStatNameDecoration(selection, false)),
- getStyleFromString(config.getSubStatNameDecoration(selection, true)))
+ getColorFromString(config.getSubStatNameDecoration(target, false)),
+ getStyleFromString(config.getSubStatNameDecoration(target, true)))
.append(text("("))
.append(text(prettySubStatName))
.append(text(")"))
@@ -287,11 +319,11 @@ public class ComponentFactory {
}
/** Returns a TranslatableComponent for the subStatName, or an empty component.*/
- private TextComponent subStatNameTranslatable(String subStatKey, Target selection) {
+ private TextComponent subStatNameTranslatable(String subStatKey, Target target) {
if (subStatKey != null) {
return getComponentBuilder(null,
- getColorFromString(config.getSubStatNameDecoration(selection, false)),
- getStyleFromString(config.getSubStatNameDecoration(selection, true)))
+ getColorFromString(config.getSubStatNameDecoration(target, false)),
+ getStyleFromString(config.getSubStatNameDecoration(target, true)))
.append(text("("))
.append(translatable()
.key(subStatKey))
@@ -305,7 +337,7 @@ public class ComponentFactory {
@return a TranslatableComponent Builder with the subStat Component as args.*/
private TranslatableComponent.Builder killEntityBuilder(@NotNull TextComponent subStat) {
return translatable()
- .key("commands.kill.success.single") //"Killed %s"
+ .key(LanguageKeyHandler.getAlternativeKeyForKillEntity()) //"Killed %s"
.args(subStat);
}
@@ -315,21 +347,21 @@ public class ComponentFactory {
with book.byAuthor as key and the subStat Component as args.*/
private TranslatableComponent.Builder entityKilledByBuilder(@NotNull TextComponent subStat) {
return translatable()
- .key("stat.minecraft.deaths") //"Number of Deaths"
+ .key(LanguageKeyHandler.getAlternativeKeyForEntityKilledBy()) //"Number of Deaths"
.append(space())
.append(translatable()
- .key("book.byAuthor") //"by %s"
+ .key(LanguageKeyHandler.getAlternativeKeyForEntityKilledByArg()) //"by %s"
.args(subStat));
}
- private TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, @Nullable TextComponent heart, Target selection) {
- TextColor baseColor = getColorFromString(config.getStatNumberDecoration(selection, false));
- TextDecoration style = getStyleFromString(config.getStatNumberDecoration(selection, true));
+ private TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, @Nullable TextComponent heartComponent, Target target) {
+ TextColor baseColor = getColorFromString(config.getStatNumberDecoration(target, false));
+ TextDecoration style = getStyleFromString(config.getStatNumberDecoration(target, true));
TextComponent.Builder hoverText = getComponentBuilder(hoverNumber, getLighterColor(baseColor), style);
- if (heart != null) {
+ if (heartComponent != null) {
hoverText.append(space())
- .append(heart);
+ .append(heartComponent);
}
else if (hoverUnitKey != null) {
hoverText.append(space())
@@ -342,7 +374,7 @@ public class ComponentFactory {
return getComponent(mainNumber, baseColor, style).hoverEvent(HoverEvent.showText(hoverText));
}
- private TextComponent surroundingBrackets(TextComponent component) {
+ private TextComponent surroundWithBrackets(TextComponent component) {
return getComponent(null, BRACKETS, null)
.append(text("["))
.append(component)
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ComponentUtils.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ComponentUtils.java
new file mode 100644
index 0000000..0fb86b3
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ComponentUtils.java
@@ -0,0 +1,76 @@
+package com.gmail.artemis.the.gr8.playerstats.msg.components;
+
+import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
+import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.StringUtils;
+import net.kyori.adventure.text.*;
+import net.kyori.adventure.text.flattener.ComponentFlattener;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+
+/** A utility class for handling Adventure's Components.
+ Its main function is currently to help serialize Components into String.*/
+public final class ComponentUtils {
+
+ /** Returns a LegacyComponentSerializer that is capable of serializing TranslatableComponents,
+ and capable of dealing with the custom language-keys I am using to improve the entity-related
+ statistic names. This serializer will create a String with hex colors and styles, and it will
+ turn language keys into prettified, readable English. */
+ public static LegacyComponentSerializer getTranslatableComponentSerializer() {
+ LegacyComponentSerializer serializer = getTextComponentSerializer();
+
+ ComponentFlattener flattener = ComponentFlattener.basic().toBuilder()
+ .mapper(TranslatableComponent.class, trans -> {
+ StringBuilder totalPrettyName = new StringBuilder();
+ if (LanguageKeyHandler.isKeyForEntityKilledByArg(trans.key())) {
+ return "";
+ }
+ else if (LanguageKeyHandler.isKeyForEntityKilledBy(trans.key()) ||
+ LanguageKeyHandler.isKeyForKillEntity(trans.key())) {
+
+ TextComponent.Builder temp = Component.text();
+ trans.iterator(ComponentIteratorType.DEPTH_FIRST, ComponentIteratorFlag.INCLUDE_TRANSLATABLE_COMPONENT_ARGUMENTS)
+ .forEachRemaining(component -> {
+ if (component instanceof TextComponent text) {
+ if (!text.children().isEmpty()) {
+ text.iterator(ComponentIteratorType.DEPTH_FIRST).forEachRemaining(component1 -> {
+ if (component1 instanceof TextComponent text1 && text1.content().contains("(")) {
+ temp.style(text.style()).color(text.color());
+ }
+ });
+ }
+ } else if (component instanceof TranslatableComponent translatable) {
+ if (translatable.key().contains("entity")) {
+ temp.append(Component.space())
+ .append(Component.text("(")
+ .append(Component.text(StringUtils.prettify(LanguageKeyHandler.convertToName(translatable.key()))))
+ .append(Component.text(")")));
+ totalPrettyName.append(
+ serializer.serialize(temp.build()));
+ } else if (!LanguageKeyHandler.isKeyForEntityKilledByArg(translatable.key())) {
+ totalPrettyName.append(
+ StringUtils.prettify(
+ LanguageKeyHandler.convertToName(
+ translatable.key())));
+ }
+ }
+ });
+ }
+ else {
+ return StringUtils.prettify(
+ LanguageKeyHandler.convertToName(
+ trans.key()));
+ }
+ return totalPrettyName.toString();
+ })
+ .build();
+
+ return serializer.toBuilder().flattener(flattener).build();
+ }
+
+ private static LegacyComponentSerializer getTextComponentSerializer() {
+ return LegacyComponentSerializer
+ .builder()
+ .hexColors()
+ .useUnusualXRepeatedCharacterHexFormat()
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ExampleMessage.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ExampleMessage.java
index 56bc33b..3ecb809 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ExampleMessage.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/ExampleMessage.java
@@ -11,6 +11,7 @@ import java.util.List;
import static net.kyori.adventure.text.Component.text;
+/** A fully constructed message with examples on how to use PlayerStats.*/
public final class ExampleMessage implements TextComponent {
private final TextComponent exampleMessage;
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/HelpMessage.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/HelpMessage.java
index ba9b603..6558a65 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/HelpMessage.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/HelpMessage.java
@@ -13,6 +13,7 @@ import java.util.List;
import static net.kyori.adventure.text.Component.text;
+/** The help message that explains how to use PlayerStats.*/
public final class HelpMessage implements TextComponent {
private final TextComponent helpMessage;
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/PrideComponentFactory.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/PrideComponentFactory.java
index 67dceaa..7f3b8f2 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/PrideComponentFactory.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/components/PrideComponentFactory.java
@@ -11,6 +11,7 @@ import java.util.Random;
import static net.kyori.adventure.text.Component.*;
+/** A festive version of the {@link ComponentFactory}*/
public class PrideComponentFactory extends ComponentFactory {
public PrideComponentFactory(ConfigHandler c) {
@@ -22,6 +23,7 @@ public class PrideComponentFactory extends ComponentFactory {
PREFIX = PluginColor.GOLD.getColor();
BRACKETS = PluginColor.GRAY.getColor();
UNDERSCORE = PluginColor.DARK_PURPLE.getColor();
+ HEARTS = PluginColor.RED.getColor();
MSG_MAIN = PluginColor.GRAY.getColor(); //difference 1
MSG_ACCENT = PluginColor.LIGHT_GOLD.getColor(); //difference 2
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/EasterEggProvider.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/EasterEggProvider.java
index 87e4d25..5ca026c 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/EasterEggProvider.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/EasterEggProvider.java
@@ -1,12 +1,18 @@
package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
+import me.clip.placeholderapi.PlaceholderAPI;
import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.Tag;
+import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
import java.util.Random;
-/**This class is just for fun, and adds some silly names for players on my server.
+/**This class is just for fun and adds some silly names for players on my server.
It does not impact the rest of the plugin, and will only be used for the players mentioned in here.*/
public final class EasterEggProvider {
@@ -96,7 +102,7 @@ public final class EasterEggProvider {
if (playerName == null) {
return null;
} else {
- return MiniMessage.miniMessage().deserialize(playerName);
+ return MiniMessage.miniMessage().deserialize(playerName, papiTag(player));
}
}
@@ -107,4 +113,16 @@ public final class EasterEggProvider {
private static boolean sillyNumberIsBetween(int sillyNumber, int lowerBound, int upperBound) {
return sillyNumber >= lowerBound && sillyNumber <= upperBound;
}
+
+ private static TagResolver papiTag(final @NotNull Player player) {
+ return TagResolver.resolver("papi", (argumentQueue, context) -> {
+ final String papiPlaceholder = argumentQueue.popOr("papi tag requires an argument").value();
+ final String parsedPlaceholder = PlaceholderAPI.setPlaceholders(player, '%' + papiPlaceholder + '%');
+ TextComponent componentPlaceholder = LegacyComponentSerializer.legacyAmpersand().deserialize(parsedPlaceholder);
+ if (!componentPlaceholder.content().isEmpty()) {
+ componentPlaceholder = componentPlaceholder.toBuilder().append(Component.space()).build();
+ }
+ return Tag.selfClosingInserting(componentPlaceholder);
+ });
+ }
}
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/FontUtils.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/FontUtils.java
index 31e0287..eb52166 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/FontUtils.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/FontUtils.java
@@ -2,6 +2,7 @@ package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
import org.bukkit.map.MinecraftFont;
+/** A small utility class that helps calculate how many dots to use to get the numbers of a top-statistic aligned. */
public final class FontUtils {
private FontUtils() {
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/LanguageKeyHandler.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/LanguageKeyHandler.java
index 44c3a0a..6fb6923 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/LanguageKeyHandler.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/LanguageKeyHandler.java
@@ -11,6 +11,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.HashMap;
+/** A utility class that provides language keys to be put in a TranslatableComponent.*/
public final class LanguageKeyHandler {
private static HashMap statNameKeys;
@@ -19,6 +20,74 @@ public final class LanguageKeyHandler {
statNameKeys = generateStatNameKeys();
}
+ /** Checks if a given Key is the language key "stat_type.minecraft.killed"
+ or "commands.kill.success.single" (which results in "Killed %s").*/
+ public static boolean isKeyForKillEntity(String statKey) {
+ return statKey.equalsIgnoreCase("stat_type.minecraft.killed") ||
+ statKey.equalsIgnoreCase("commands.kill.success.single");
+ }
+
+ /** Returns a language key to replace the default Statistic.Kill_Entity key.
+ @return the key "commands.kill.success.single", which results in "Killed %s" */
+ public static String getAlternativeKeyForKillEntity() {
+ return "commands.kill.success.single";
+ }
+
+ /** Checks if a given Key is the language key "stat_type.minecraft.killed_by"
+ or "stat.minecraft.deaths" (which results in "Number of Deaths").*/
+ public static boolean isKeyForEntityKilledBy(String statKey) {
+ return statKey.equalsIgnoreCase("stat_type.minecraft.killed_by") ||
+ statKey.equalsIgnoreCase("stat.minecraft.deaths");
+ }
+
+ /** Returns a language key to replace the default Statistic.Entity_Killed_By key.
+ @return the key "stat.minecraft.deaths", which results in "Number of Deaths"
+ (meant to be followed by {@link #getAlternativeKeyForEntityKilledByArg()})*/
+ public static String getAlternativeKeyForEntityKilledBy() {
+ return "stat.minecraft.deaths";
+ }
+
+ /** Checks if a given Key is the language key "book.byAuthor"
+ (which results in "by %s"). */
+ public static boolean isKeyForEntityKilledByArg(String statKey) {
+ return statKey.equalsIgnoreCase("book.byAuthor");
+ }
+
+ /** Returns a language key to complete the alternative key for Statistic.Entity_Killed_By.
+ @return the key "book.byAuthor", which results in "by %". If used after
+ {@link #getAlternativeKeyForEntityKilledBy()}, you will get "Number of Deaths" "by %s"*/
+ public static String getAlternativeKeyForEntityKilledByArg() {
+ return "book.byAuthor";
+ }
+
+ public static String convertToName(String key) {
+ if (key.equalsIgnoreCase("soundCategory.block")) {
+ return Unit.BLOCK.getLabel();
+ } else if (isKeyForKillEntity(key)) {
+ return "times_killed";
+ } else if (isKeyForEntityKilledBy(key)) {
+ return "number_of_times_killed_by";
+ } else if (isKeyForEntityKilledByArg(key)) { //this one returns nothing, because the previous one returns the full text
+ return "";
+ }
+ String toReplace = "";
+ if (key.contains("stat")) {
+ if (key.contains("type")) {
+ toReplace = "stat_type";
+ } else {
+ toReplace = "stat";
+ }
+ } else if (key.contains("entity")) { //for the two entity-related ones, put brackets around it to make up for the multiple-keys/args-serializer issues
+ toReplace = "entity";
+ } else if (key.contains("block")) {
+ toReplace = "block";
+ } else if (key.contains("item")) {
+ toReplace = "item";
+ }
+ toReplace = toReplace + ".minecraft.";
+ return key.replace(toReplace, "");
+ }
+
public String getStatKey(@NotNull Statistic statistic) {
if (statistic.getType() == Statistic.Type.UNTYPED) {
return "stat.minecraft." + statNameKeys.get(statistic);
@@ -33,7 +102,7 @@ public final class LanguageKeyHandler {
public @Nullable String getEntityKey(EntityType entity) {
if (entity == null || entity == EntityType.UNKNOWN) return null;
else {
- return "entity.minecraft." + entity.getKey().getKey();
+ return "entity.minecraft." + entity.getKey().getKey();
}
}
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/NumberFormatter.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/NumberFormatter.java
index cafacd8..3d1ae47 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/NumberFormatter.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/NumberFormatter.java
@@ -4,6 +4,9 @@ import com.gmail.artemis.the.gr8.playerstats.enums.Unit;
import java.text.DecimalFormat;
+/** A utility class that formats statistic numbers into something more readable.
+ It transforms {@link Unit} Type TIME, DAMAGE, and DISTANCE into a more Minecraft-appropriate Unit,
+ and adds commas to break up large numbers.*/
public final class NumberFormatter {
private final DecimalFormat format;
@@ -17,25 +20,13 @@ public final class NumberFormatter {
/** 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) {
- return switch (statUnit.getType()) {
- case DISTANCE -> formatDistance(number, statUnit);
- case DAMAGE -> formatDamage(number, statUnit);
- default -> format.format(number);
- };
- } else {
- return formatTime(number, statUnit, smallTimeUnit);
- }
+ public String formatNumber(long number) {
+ return format.format(number);
}
/** 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
+ public String formatDamageNumber(long number, Unit statUnit) { //7 statistics
if (statUnit == Unit.HEART) {
return format.format(Math.round(number / 2.0));
} else {
@@ -45,7 +36,7 @@ public final class NumberFormatter {
/** 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
+ public String formatDistanceNumber(long number, Unit statUnit) { //15 statistics
switch (statUnit) {
case CM -> {
return format.format(number);
@@ -63,7 +54,7 @@ public final class NumberFormatter {
}
/** The unit of time-based statistics is ticks by default.*/
- private String formatTime(long number, Unit bigUnit, Unit smallUnit) { //5 statistics
+ public String formatTimeNumber(long number, Unit bigUnit, Unit smallUnit) { //5 statistics
if (number == 0) {
return "-";
}
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/StringUtils.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/StringUtils.java
index 6d987cb..309ef86 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/StringUtils.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/msg/msgutils/StringUtils.java
@@ -2,6 +2,7 @@ package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
+/** A small utility class that helps make enum constant names prettier for output in stat-messages.*/
public final class StringUtils {
private StringUtils() {
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadAction.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadAction.java
index 0bd3dd7..784398b 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadAction.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadAction.java
@@ -9,7 +9,8 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RecursiveAction;
-public final class ReloadAction extends RecursiveAction {
+/** The action that is executed when a reload-command is triggered. */
+final class ReloadAction extends RecursiveAction {
private static int threshold;
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadThread.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadThread.java
index 7c70ace..7bc4dd7 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadThread.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/reload/ReloadThread.java
@@ -7,6 +7,7 @@ import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
+import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import org.bukkit.Bukkit;
@@ -20,26 +21,20 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Predicate;
-public class ReloadThread extends Thread {
+/** The Thread that is in charge of reloading PlayerStats. */
+public final class ReloadThread extends Thread {
private static ConfigHandler config;
- private static OutputManager messageSender;
- private final OfflinePlayerHandler offlinePlayerHandler;
-
- private static ShareManager shareManager;
+ private static OutputManager outputManager;
private final int reloadThreadID;
private final StatThread statThread;
private final CommandSender sender;
-
- public ReloadThread(ConfigHandler c, OutputManager m, OfflinePlayerHandler o, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
+ public ReloadThread(ConfigHandler c, OutputManager m, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
config = c;
- messageSender = m;
- offlinePlayerHandler = o;
-
- shareManager = ShareManager.getInstance(c);
+ outputManager = m;
reloadThreadID = ID;
statThread = s;
@@ -49,6 +44,11 @@ public class ReloadThread extends Thread {
MyLogger.threadCreated(this.getName());
}
+ /** This method will perform a series of tasks. If a {@link StatThread} is still running,
+ it will join the statThread and wait for it to finish. Then, it will reload the config,
+ update the offlinePlayerList in the {@link OfflinePlayerHandler}, update the {@link DebugLevel},
+ update the share-settings in {@link ShareManager} and topListSize-settings in {@link StatManager},
+ and update the MessageBuilders in the {@link OutputManager}.*/
@Override
public void run() {
long time = System.currentTimeMillis();
@@ -69,21 +69,21 @@ public class ReloadThread extends Thread {
reloadEverything();
if (sender != null) {
- messageSender.sendFeedbackMsg(sender, StandardMessage.RELOADED_CONFIG);
+ outputManager.sendFeedbackMsg(sender, StandardMessage.RELOADED_CONFIG);
}
}
else { //during first start-up
MyLogger.setDebugLevel(config.getDebugLevel());
- offlinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
+ OfflinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
}
}
private void reloadEverything() {
MyLogger.setDebugLevel(config.getDebugLevel());
- messageSender.updateMessageWriters(config);
- offlinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
- shareManager.updateSettings(config);
+ OutputManager.updateMessageBuilders();
+ OfflinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
+ ShareManager.updateSettings(config);
}
private ConcurrentHashMap loadOfflinePlayers() {
@@ -125,7 +125,7 @@ public class ReloadThread extends Thread {
MyLogger.actionFinished(1);
MyLogger.logTimeTaken("ReloadThread",
- ("loaded " + playerMap.size() + " offline players"), time);
+ ("loaded " + playerMap.size() + " offline players"), time, DebugLevel.LOW);
return playerMap;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatAction.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatAction.java
index 2468a0a..56926df 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatAction.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatAction.java
@@ -1,7 +1,7 @@
package com.gmail.artemis.the.gr8.playerstats.statistic;
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
-import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
+import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.google.common.collect.ImmutableList;
@@ -9,17 +9,17 @@ import org.bukkit.OfflinePlayer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.RecursiveAction;
+import java.util.concurrent.RecursiveTask;
-
-public final class StatAction extends RecursiveAction {
+/** The action that is executed when a stat-command is triggered. */
+final class StatAction extends RecursiveTask> {
private static int threshold;
private final OfflinePlayerHandler offlinePlayerHandler;
private final ImmutableList playerNames;
- private final StatRequest request;
- private final ConcurrentHashMap playerStats;
+ private final StatRequest statRequest;
+ private final ConcurrentHashMap allStats;
/**
* Gets the statistic numbers for all players whose name is on the list, puts them in a ConcurrentHashMap
@@ -27,34 +27,36 @@ public final class StatAction extends RecursiveAction {
* @param offlinePlayerHandler the OfflinePlayerHandler to convert playerNames into Players
* @param playerNames ImmutableList of playerNames for players that should be included in stat calculations
* @param statRequest a validated statRequest
- * @param playerStats the ConcurrentHashMap to put the results on
+ * @param allStats the ConcurrentHashMap to put the results on
*/
- public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList playerNames, StatRequest statRequest, ConcurrentHashMap playerStats) {
+ public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList playerNames, StatRequest statRequest, ConcurrentHashMap allStats) {
threshold = ThreadManager.getTaskThreshold();
this.offlinePlayerHandler = offlinePlayerHandler;
this.playerNames = playerNames;
- this.request = statRequest;
- this.playerStats = playerStats;
+ this.statRequest = statRequest;
+ this.allStats = allStats;
MyLogger.subActionCreated(Thread.currentThread().getName());
}
@Override
- protected void compute() {
+ protected ConcurrentHashMap compute() {
if (playerNames.size() < threshold) {
- getStatsDirectly();
+ return getStatsDirectly();
}
else {
- final StatAction subTask1 = new StatAction(offlinePlayerHandler, playerNames.subList(0, playerNames.size()/2), request, playerStats);
- final StatAction subTask2 = new StatAction(offlinePlayerHandler, playerNames.subList(playerNames.size()/2, playerNames.size()), request, playerStats);
+ final StatAction subTask1 = new StatAction(offlinePlayerHandler, playerNames.subList(0, playerNames.size()/2), statRequest, allStats);
+ final StatAction subTask2 = new StatAction(offlinePlayerHandler, playerNames.subList(playerNames.size()/2, playerNames.size()), statRequest, allStats);
//queue and compute all subtasks in the right order
- invokeAll(subTask1, subTask2);
+ subTask1.fork();
+ subTask2.compute();
+ return subTask1.join();
}
}
- private void getStatsDirectly() {
+ private ConcurrentHashMap getStatsDirectly() {
Iterator iterator = playerNames.iterator();
if (iterator.hasNext()) {
do {
@@ -63,17 +65,18 @@ public final class StatAction extends RecursiveAction {
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(playerName);
if (player != null) {
int statistic = 0;
- switch (request.getStatistic().getType()) {
- case UNTYPED -> statistic = player.getStatistic(request.getStatistic());
- case ENTITY -> statistic = player.getStatistic(request.getStatistic(), request.getEntity());
- case BLOCK -> statistic = player.getStatistic(request.getStatistic(), request.getBlock());
- case ITEM -> statistic = player.getStatistic(request.getStatistic(), request.getItem());
+ switch (statRequest.getStatistic().getType()) {
+ case UNTYPED -> statistic = player.getStatistic(statRequest.getStatistic());
+ case ENTITY -> statistic = player.getStatistic(statRequest.getStatistic(), statRequest.getEntity());
+ case BLOCK -> statistic = player.getStatistic(statRequest.getStatistic(), statRequest.getBlock());
+ case ITEM -> statistic = player.getStatistic(statRequest.getStatistic(), statRequest.getItem());
}
if (statistic > 0) {
- playerStats.put(playerName, statistic);
+ allStats.put(playerName, statistic);
}
}
} while (iterator.hasNext());
}
+ return allStats;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatManager.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatManager.java
new file mode 100644
index 0000000..37c3c1e
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatManager.java
@@ -0,0 +1,93 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic;
+
+import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
+import com.gmail.artemis.the.gr8.playerstats.api.StatCalculator;
+import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
+import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
+import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
+import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
+import com.google.common.collect.ImmutableList;
+import org.bukkit.OfflinePlayer;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ForkJoinPool;
+import java.util.stream.Collectors;
+
+public final class StatManager implements StatCalculator {
+
+ private final OfflinePlayerHandler offlinePlayerHandler;
+
+ public StatManager(OfflinePlayerHandler offlinePlayerHandler) {
+ this.offlinePlayerHandler = offlinePlayerHandler;
+ }
+
+ /** Gets the statistic data for an individual player. If somehow the player
+ cannot be found, this returns 0.*/
+ @Override
+ public int getPlayerStat(StatRequest statRequest) {
+ OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(statRequest.getPlayerName());
+ if (player != null) {
+ return switch (statRequest.getStatistic().getType()) {
+ case UNTYPED -> player.getStatistic(statRequest.getStatistic());
+ case ENTITY -> player.getStatistic(statRequest.getStatistic(), statRequest.getEntity());
+ case BLOCK -> player.getStatistic(statRequest.getStatistic(), statRequest.getBlock());
+ case ITEM -> player.getStatistic(statRequest.getStatistic(), statRequest.getItem());
+ };
+ }
+ return 0;
+ }
+
+ @Override
+ public LinkedHashMap getTopStats(StatRequest statRequest) {
+ return getAllStatsAsync(statRequest).entrySet().stream()
+ .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
+ .limit(statRequest.getTopListSize())
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
+ }
+
+ @Override
+ public long getServerStat(StatRequest statRequest) {
+ List numbers = getAllStatsAsync(statRequest)
+ .values()
+ .parallelStream()
+ .toList();
+ return numbers.parallelStream().mapToLong(Integer::longValue).sum();
+ }
+
+ /** Invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players
+ that are stored in the {@link OfflinePlayerHandler}) */
+ private @NotNull ConcurrentHashMap getAllStatsAsync(StatRequest statRequest) {
+ long time = System.currentTimeMillis();
+
+ ForkJoinPool commonPool = ForkJoinPool.commonPool();
+ ConcurrentHashMap allStats;
+
+ try {
+ allStats = commonPool.invoke(getStatTask(statRequest));
+ } catch (ConcurrentModificationException e) {
+ MyLogger.logMsg("The statRequest could not be executed due to a ConcurrentModificationException. " +
+ "This likely happened because Bukkit hasn't fully initialized all player-data yet. " +
+ "Try again and it should be fine!", true);
+ throw new ConcurrentModificationException(e.toString());
+ }
+
+ MyLogger.actionFinished(2);
+ ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
+ MyLogger.logTimeTaken("StatThread", "calculated all stats", time, DebugLevel.MEDIUM);
+
+ return allStats;
+ }
+
+ private StatAction getStatTask(StatRequest statRequest) {
+ int size = offlinePlayerHandler.getOfflinePlayerCount() != 0 ? offlinePlayerHandler.getOfflinePlayerCount() : 16;
+ ConcurrentHashMap allStats = new ConcurrentHashMap<>(size);
+ ImmutableList playerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
+
+ StatAction task = new StatAction(offlinePlayerHandler, playerNames, statRequest, allStats);
+ MyLogger.actionCreated(playerNames.size());
+
+ return task;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatThread.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatThread.java
index 341ede1..5a44daf 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatThread.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/StatThread.java
@@ -2,41 +2,34 @@ package com.gmail.artemis.the.gr8.playerstats.statistic;
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
-import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
+import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
+import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread;
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
-import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
-import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
-import com.google.common.collect.ImmutableList;
-import org.bukkit.OfflinePlayer;
-import org.jetbrains.annotations.NotNull;
+import net.kyori.adventure.text.TextComponent;
import org.jetbrains.annotations.Nullable;
import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ForkJoinPool;
-import java.util.stream.Collectors;
-public class StatThread extends Thread {
+/** The Thread that is in charge of getting and calculating statistics.*/
+public final class StatThread extends Thread {
- private static ConfigHandler config;
- private final OutputManager outputManager;
- private final OfflinePlayerHandler offlinePlayerHandler;
+ private static OutputManager outputManager;
+ private static StatManager statManager;
private final ReloadThread reloadThread;
- private final StatRequest request;
+ private final StatRequest statRequest;
- public StatThread(ConfigHandler c, OutputManager m, OfflinePlayerHandler o, int ID, StatRequest s, @Nullable ReloadThread r) {
- config = c;
+ public StatThread(OutputManager m, StatManager t, int ID, StatRequest s, @Nullable ReloadThread r) {
outputManager = m;
- offlinePlayerHandler = o;
+ statManager = t;
reloadThread = r;
- request = s;
+ statRequest = s;
- this.setName("StatThread-" + request.getCommandSender().getName() + "-" + ID);
+ this.setName("StatThread-" + statRequest.getCommandSender().getName() + "-" + ID);
MyLogger.threadCreated(this.getName());
}
@@ -44,13 +37,13 @@ public class StatThread extends Thread {
public void run() throws IllegalStateException, NullPointerException {
MyLogger.threadStart(this.getName());
- if (request == null) {
- throw new NullPointerException("No statistic request was found!");
+ if (statRequest == null) {
+ throw new NullPointerException("No statistic statRequest was found!");
}
if (reloadThread != null && reloadThread.isAlive()) {
try {
MyLogger.waitingForOtherThread(this.getName(), reloadThread.getName());
- outputManager.sendFeedbackMsg(request.getCommandSender(), StandardMessage.STILL_RELOADING);
+ outputManager.sendFeedbackMsg(statRequest.getCommandSender(), StandardMessage.STILL_RELOADING);
reloadThread.join();
} catch (InterruptedException e) {
@@ -61,74 +54,29 @@ public class StatThread extends Thread {
long lastCalc = ThreadManager.getLastRecordedCalcTime();
if (lastCalc > 2000) {
- outputManager.sendFeedbackMsgWaitAMoment(request.getCommandSender(), lastCalc > 20000);
+ outputManager.sendFeedbackMsgWaitAMoment(statRequest.getCommandSender(), lastCalc > 20000);
}
- Target selection = request.getSelection();
+ Target selection = statRequest.getTarget();
try {
- switch (selection) {
- case PLAYER -> outputManager.sendPlayerStat(request, getIndividualStat());
- case TOP -> outputManager.sendTopStat(request, getTopStats());
- case SERVER -> outputManager.sendServerStat(request, getServerTotal());
- }
- } catch (ConcurrentModificationException e) {
- if (!request.isConsoleSender()) {
- outputManager.sendFeedbackMsg(request.getCommandSender(), StandardMessage.UNKNOWN_ERROR);
- }
- }
- }
-
- private LinkedHashMap getTopStats() throws ConcurrentModificationException {
- return getAllStats().entrySet().stream()
- .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
- .limit(config.getTopListMaxSize()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
- }
-
- private long getServerTotal() {
- List numbers = getAllStats().values().parallelStream().toList();
- return numbers.parallelStream().mapToLong(Integer::longValue).sum();
- }
-
- //invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players in the list)
- private @NotNull ConcurrentHashMap getAllStats() throws ConcurrentModificationException {
- long time = System.currentTimeMillis();
-
- int size = offlinePlayerHandler.getOfflinePlayerCount() != 0 ? offlinePlayerHandler.getOfflinePlayerCount() : 16;
- ConcurrentHashMap playerStats = new ConcurrentHashMap<>(size);
- ImmutableList playerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
-
- StatAction task = new StatAction(offlinePlayerHandler, playerNames, request, playerStats);
- MyLogger.actionCreated(playerNames.size());
- ForkJoinPool commonPool = ForkJoinPool.commonPool();
-
- try {
- commonPool.invoke(task);
- } catch (ConcurrentModificationException e) {
- MyLogger.logMsg("The request could not be executed due to a ConcurrentModificationException. " +
- "This likely happened because Bukkit hasn't fully initialized all player-data yet. " +
- "Try again and it should be fine!", true);
- throw new ConcurrentModificationException(e.toString());
- }
-
- MyLogger.actionFinished(2);
- ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
- MyLogger.logTimeTaken("StatThread", "calculated all stats", time);
-
- return playerStats;
- }
-
- /** Gets the statistic data for an individual player. If somehow the player
- cannot be found, this returns 0.*/
- private int getIndividualStat() {
- OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(request.getPlayerName());
- if (player != null) {
- return switch (request.getStatistic().getType()) {
- case UNTYPED -> player.getStatistic(request.getStatistic());
- case ENTITY -> player.getStatistic(request.getStatistic(), request.getEntity());
- case BLOCK -> player.getStatistic(request.getStatistic(), request.getBlock());
- case ITEM -> player.getStatistic(request.getStatistic(), request.getItem());
+ TextComponent statResult = switch (selection) {
+ case PLAYER -> outputManager.formatPlayerStat(statRequest, statManager.getPlayerStat(statRequest));
+ case TOP -> outputManager.formatTopStat(statRequest, statManager.getTopStats(statRequest));
+ case SERVER -> outputManager.formatServerStat(statRequest, statManager.getServerStat(statRequest));
};
+ if (statRequest.isAPIRequest()) {
+ String msg = ComponentUtils.getTranslatableComponentSerializer()
+ .serialize(statResult);
+ statRequest.getCommandSender().sendMessage(msg);
+ }
+ else {
+ outputManager.sendToCommandSender(statRequest.getCommandSender(), statResult);
+ }
+ }
+ catch (ConcurrentModificationException e) {
+ if (!statRequest.isConsoleSender()) {
+ outputManager.sendFeedbackMsg(statRequest.getCommandSender(), StandardMessage.UNKNOWN_ERROR);
+ }
}
- return 0;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/PlayerStatRequest.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/PlayerStatRequest.java
new file mode 100644
index 0000000..44153e1
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/PlayerStatRequest.java
@@ -0,0 +1,46 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.request;
+
+import com.gmail.artemis.the.gr8.playerstats.api.RequestExecutor;
+import com.gmail.artemis.the.gr8.playerstats.statistic.result.PlayerStatResult;
+import com.gmail.artemis.the.gr8.playerstats.statistic.result.StatResult;
+import net.kyori.adventure.text.TextComponent;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.entity.EntityType;
+import org.jetbrains.annotations.NotNull;
+
+public final class PlayerStatRequest implements RequestExecutor {
+
+ private final StatRequestHandler statRequestHandler;
+
+ public PlayerStatRequest(StatRequestHandler statRequestHandler) {
+ this.statRequestHandler = statRequestHandler;
+ }
+
+ @Override
+ public StatResult untyped(@NotNull Statistic statistic) {
+ StatRequest completedRequest = statRequestHandler.untyped(statistic);
+ return getStatResult(completedRequest);
+ }
+
+ @Override
+ public StatResult blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
+ StatRequest completedRequest = statRequestHandler.blockOrItemType(statistic, material);
+ return getStatResult(completedRequest);
+ }
+
+ @Override
+ public StatResult entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
+ StatRequest completedRequest = statRequestHandler.entityType(statistic, entityType);
+ return getStatResult(completedRequest);
+ }
+
+ private PlayerStatResult getStatResult(StatRequest completedRequest) {
+ int stat = RequestExecutor.super.getStatCalculator()
+ .getPlayerStat(completedRequest);
+ TextComponent prettyStat = RequestExecutor.super.getStatFormatter()
+ .formatPlayerStat(completedRequest, stat);
+
+ return new PlayerStatResult(stat, prettyStat);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/ServerStatRequest.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/ServerStatRequest.java
new file mode 100644
index 0000000..d62dfdd
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/ServerStatRequest.java
@@ -0,0 +1,46 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.request;
+
+import com.gmail.artemis.the.gr8.playerstats.api.RequestExecutor;
+import com.gmail.artemis.the.gr8.playerstats.statistic.result.ServerStatResult;
+import com.gmail.artemis.the.gr8.playerstats.statistic.result.StatResult;
+import net.kyori.adventure.text.TextComponent;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.entity.EntityType;
+import org.jetbrains.annotations.NotNull;
+
+public final class ServerStatRequest implements RequestExecutor {
+
+ private final StatRequestHandler statRequestHandler;
+
+ public ServerStatRequest(StatRequestHandler statRequestHandler) {
+ this.statRequestHandler = statRequestHandler;
+ }
+
+ @Override
+ public StatResult untyped(@NotNull Statistic statistic) {
+ StatRequest completedRequest = statRequestHandler.untyped(statistic);
+ return getStatResult(completedRequest);
+ }
+
+ @Override
+ public StatResult blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
+ StatRequest completedRequest = statRequestHandler.blockOrItemType(statistic, material);
+ return getStatResult(completedRequest);
+ }
+
+ @Override
+ public StatResult entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
+ StatRequest completedRequest = statRequestHandler.entityType(statistic, entityType);
+ return getStatResult(completedRequest);
+ }
+
+ private ServerStatResult getStatResult(StatRequest completedRequest) {
+ long stat = RequestExecutor.super.getStatCalculator()
+ .getServerStat(completedRequest);
+ TextComponent prettyStat = RequestExecutor.super.getStatFormatter()
+ .formatServerStat(completedRequest, stat);
+
+ return new ServerStatResult(stat, prettyStat);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/StatRequest.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/StatRequest.java
new file mode 100644
index 0000000..ff1ac08
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/StatRequest.java
@@ -0,0 +1,186 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.request;
+
+import com.gmail.artemis.the.gr8.playerstats.api.RequestGenerator;
+import com.gmail.artemis.the.gr8.playerstats.enums.Target;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.EntityType;
+import org.jetbrains.annotations.NotNull;
+
+/** A StatRequest is the object PlayerStats uses to calculate and format the requested statistic.
+ This object can be generated from two different sources:
+
- Internally: by PlayerStats itself when /stat is called, using the args provided by the CommandSender.
+
- Externally: through the API methods provided by the {@link RequestGenerator} interface.
+
+
For this StatRequest to be valid, it needs the following values:
+
+ - a {@link Statistic}
statistic
+ - if this Statistic is not of {@link Statistic.Type} Untyped, a
subStatEntryName
needs to be set,
+ together with one of the following values:
+
- for Type.Block: a {@link Material} blockMaterial
+
- for Type.Item: a {@link Material} itemMaterial
+
- for Type.Entity: an {@link EntityType} entityType
+ - a {@link Target}
target
(automatically set for all API-requests)
+ - if the
target
is Target.Player, a playerName
needs to be added
+
*/
+public final class StatRequest {
+
+ private final CommandSender sender;
+ private boolean isAPIRequest;
+ private Statistic statistic;
+ private String playerName;
+ private Target target;
+ private int topListSize;
+
+ private String subStatEntryName;
+ private EntityType entity;
+ private Material block;
+ private Material item;
+ private boolean playerFlag;
+
+ /** Create a new {@link StatRequest} with default values:
+
- CommandSender sender (provided)
+
- Target target = {@link Target#TOP}
+
- int topListSize = 10
+
- boolean playerFlag = false
+
- boolean isAPIRequest
+
+ @param sender the CommandSender who prompted this RequestGenerator
+ @param isAPIRequest whether this RequestGenerator is coming through the API or the onCommand
+ */
+ private StatRequest(@NotNull CommandSender sender, boolean isAPIRequest) {
+ this.sender = sender;
+ this.isAPIRequest = isAPIRequest;
+ target = Target.TOP;
+ playerFlag = false;
+ }
+
+ public static StatRequest getBasicRequest(CommandSender sender) {
+ return new StatRequest(sender, false);
+ }
+
+ public static StatRequest getBasicAPIRequest() {
+ return new StatRequest(Bukkit.getConsoleSender(), true);
+ }
+
+ public void setAPIRequest() {
+ this.isAPIRequest = true;
+ }
+
+ public boolean isAPIRequest() {
+ return isAPIRequest;
+ }
+
+ public @NotNull CommandSender getCommandSender() {
+ return sender;
+ }
+
+ public boolean isConsoleSender() {
+ return sender instanceof ConsoleCommandSender;
+ }
+
+ public void setStatistic(Statistic statistic) {
+ this.statistic = statistic;
+ }
+
+ public Statistic getStatistic() {
+ return statistic;
+ }
+
+ public void setSubStatEntryName(String subStatEntry) {
+ this.subStatEntryName = subStatEntry;
+ }
+
+ public String getSubStatEntryName() {
+ return subStatEntryName;
+ }
+
+ public void setPlayerName(String playerName) {
+ this.playerName = playerName;
+ }
+
+ public String getPlayerName() {
+ return playerName;
+ }
+
+ public void setPlayerFlag(boolean playerFlag) {
+ this.playerFlag = playerFlag;
+ }
+
+ public boolean getPlayerFlag() {
+ return playerFlag;
+ }
+
+ public void setTarget(Target target) {
+ this.target = target;
+ }
+
+ public @NotNull Target getTarget() {
+ return target;
+ }
+
+ public void setTopListSize(int topListSize) {
+ this.topListSize = topListSize;
+ }
+
+ public int getTopListSize() {
+ return this.topListSize;
+ }
+
+ public void setEntity(EntityType entity) {
+ this.entity = entity;
+ }
+
+ public EntityType getEntity() {
+ return entity;
+ }
+
+ public void setBlock(Material block) {
+ this.block = block;
+ }
+
+ public Material getBlock() {
+ return block;
+ }
+
+ public void setItem(Material item) {
+ this.item = item;
+ }
+
+ public Material getItem() {
+ return item;
+ }
+
+ public boolean isValid() {
+ if (statistic == null) {
+ return false;
+ } else if (target == Target.PLAYER && playerName == null) {
+ return false;
+ } else if (statistic.getType() != Statistic.Type.UNTYPED &&
+ subStatEntryName == null) {
+ return false;
+ } else {
+ return hasMatchingSubStat();
+ }
+ }
+
+ private boolean hasMatchingSubStat() {
+ switch (statistic.getType()) {
+ case BLOCK -> {
+ return block != null;
+ }
+ case ENTITY -> {
+ return entity != null;
+ }
+ case ITEM -> {
+ return item != null;
+ }
+ default -> {
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/StatRequestHandler.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/StatRequestHandler.java
new file mode 100644
index 0000000..92042b5
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/StatRequestHandler.java
@@ -0,0 +1,171 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.request;
+
+import com.gmail.artemis.the.gr8.playerstats.Main;
+import com.gmail.artemis.the.gr8.playerstats.api.RequestGenerator;
+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.OfflinePlayerHandler;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public record StatRequestHandler(StatRequest statRequest) implements RequestGenerator {
+
+ public static StatRequestHandler playerRequestHandler(String playerName) {
+ StatRequest request = StatRequest.getBasicAPIRequest();
+ request.setTarget(Target.PLAYER);
+ request.setPlayerName(playerName);
+ return new StatRequestHandler(request);
+ }
+
+ public static StatRequestHandler serverRequestHandler() {
+ StatRequest request = StatRequest.getBasicAPIRequest();
+ request.setTarget(Target.SERVER);
+ return new StatRequestHandler(request);
+ }
+
+ public static StatRequestHandler topRequestHandler(int topListSize) {
+ StatRequest request = StatRequest.getBasicAPIRequest();
+ request.setTarget(Target.TOP);
+ request.setTopListSize(topListSize != 0 ? topListSize : Main.getConfigHandler().getTopListMaxSize());
+ return new StatRequestHandler(request);
+ }
+
+ /**
+ @param sender the CommandSender that requested this specific statistic
+ */
+ public static StatRequestHandler internalRequestHandler(CommandSender sender) {
+ StatRequest request = StatRequest.getBasicRequest(sender);
+ request.setTopListSize(Main.getConfigHandler().getTopListMaxSize());
+ return new StatRequestHandler(request);
+ }
+
+ @Override
+ public StatRequest untyped(@NotNull Statistic statistic) throws IllegalArgumentException {
+ if (statistic.getType() == Statistic.Type.UNTYPED) {
+ statRequest.setStatistic(statistic);
+ return statRequest;
+ }
+ throw new IllegalArgumentException("This statistic is not of Type.Untyped");
+ }
+
+ @Override
+ public StatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
+ Statistic.Type type = statistic.getType();
+ if (type == Statistic.Type.BLOCK && material.isBlock()) {
+ statRequest.setBlock(material);
+ }
+ else if (type == Statistic.Type.ITEM && material.isItem()){
+ statRequest.setItem(material);
+ }
+ else {
+ throw new IllegalArgumentException("Either this statistic is not of Type.Block or Type.Item, or no valid block or item has been provided");
+ }
+ statRequest.setSubStatEntryName(material.toString());
+ return statRequest;
+ }
+
+ @Override
+ public StatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
+ if (statistic.getType() == Statistic.Type.ENTITY) {
+ statRequest.setSubStatEntryName(entityType.toString());
+ statRequest.setEntity(entityType);
+ return statRequest;
+ }
+ throw new IllegalArgumentException("This statistic is not of Type.Entity");
+ }
+
+ /**
+ This will create a {@link StatRequest} from the provided args, with the requesting Player (or Console)
+ as CommandSender. This CommandSender will receive feedback messages if the StatRequest could not be created.
+
+ @param args an Array of args such as a CommandSender would put in Minecraft chat:
+ - a statName
(example: "mine_block")
+ - if applicable, a subStatEntryName
(example: diorite)(
+ - a target
for this lookup: can be "top", "server", "player" (or "me" to indicate the current CommandSender)
+ - if "player" was chosen, include a playerName
+ @return the generated StatRequest
+ */
+ public StatRequest getRequestFromArgs(String[] args) {
+ EnumHandler enumHandler = Main.getEnumHandler();
+ OfflinePlayerHandler offlinePlayerHandler = Main.getOfflinePlayerHandler();
+ CommandSender sender = statRequest.getCommandSender();
+
+ for (String arg : args) {
+ //check for statName
+ if (enumHandler.isStatistic(arg) && statRequest.getStatistic() == null) {
+ statRequest.setStatistic(EnumHandler.getStatEnum(arg));
+ }
+ //check for subStatEntry and playerFlag
+ else if (enumHandler.isSubStatEntry(arg)) {
+ if (arg.equalsIgnoreCase("player") && !statRequest.getPlayerFlag()) {
+ statRequest.setPlayerFlag(true);
+ } else {
+ if (statRequest.getSubStatEntryName() == null) statRequest.setSubStatEntryName(arg);
+ }
+ }
+ //check for selection
+ else if (arg.equalsIgnoreCase("top")) {
+ statRequest.setTarget(Target.TOP);
+ } else if (arg.equalsIgnoreCase("server")) {
+ statRequest.setTarget(Target.SERVER);
+ } else if (arg.equalsIgnoreCase("me")) {
+ if (sender instanceof Player) {
+ statRequest.setPlayerName(sender.getName());
+ statRequest.setTarget(Target.PLAYER);
+ } else if (sender instanceof ConsoleCommandSender) {
+ statRequest.setTarget(Target.SERVER);
+ }
+ } else if (offlinePlayerHandler.isRelevantPlayer(arg) && statRequest.getPlayerName() == null) {
+ statRequest.setPlayerName(arg);
+ statRequest.setTarget(Target.PLAYER);
+ } else if (arg.equalsIgnoreCase("api")) {
+ statRequest.setAPIRequest();
+ }
+ }
+ patchRequest(statRequest);
+ return statRequest;
+ }
+
+ /**
+ Adjust the StatRequest object if needed: unpack the playerFlag into a subStatEntry,
+ try to retrieve the corresponding Enum Constant for any relevant block/entity/item,
+ and remove any unnecessary subStatEntries.
+ */
+ private void patchRequest(StatRequest statRequest) {
+ if (statRequest.getStatistic() != null) {
+ Statistic.Type type = statRequest.getStatistic().getType();
+
+ if (statRequest.getPlayerFlag()) { //unpack the playerFlag
+ if (type == Statistic.Type.ENTITY && statRequest.getSubStatEntryName() == null) {
+ statRequest.setSubStatEntryName("player");
+ } else {
+ statRequest.setTarget(Target.PLAYER);
+ }
+ }
+
+ String subStatEntry = statRequest.getSubStatEntryName();
+ switch (type) { //attempt to convert relevant subStatEntries into their corresponding Enum Constant
+ case BLOCK -> {
+ Material block = EnumHandler.getBlockEnum(subStatEntry);
+ if (block != null) statRequest.setBlock(block);
+ }
+ case ENTITY -> {
+ EntityType entity = EnumHandler.getEntityEnum(subStatEntry);
+ if (entity != null) statRequest.setEntity(entity);
+ }
+ case ITEM -> {
+ Material item = EnumHandler.getItemEnum(subStatEntry);
+ if (item != null) statRequest.setItem(item);
+ }
+ case UNTYPED -> { //remove unnecessary subStatEntries
+ if (subStatEntry != null) statRequest.setSubStatEntryName(null);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/TopStatRequest.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/TopStatRequest.java
new file mode 100644
index 0000000..9eeccdb
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/request/TopStatRequest.java
@@ -0,0 +1,48 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.request;
+
+import com.gmail.artemis.the.gr8.playerstats.api.RequestExecutor;
+import com.gmail.artemis.the.gr8.playerstats.statistic.result.StatResult;
+import com.gmail.artemis.the.gr8.playerstats.statistic.result.TopStatResult;
+import net.kyori.adventure.text.TextComponent;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.entity.EntityType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.LinkedHashMap;
+
+public final class TopStatRequest implements RequestExecutor> {
+
+ private final StatRequestHandler statRequestHandler;
+
+ public TopStatRequest(StatRequestHandler statRequestHandler) {
+ this.statRequestHandler = statRequestHandler;
+ }
+
+ @Override
+ public StatResult> untyped(@NotNull Statistic statistic) {
+ StatRequest completedRequest = statRequestHandler.untyped(statistic);
+ return getStatResult(completedRequest);
+ }
+
+ @Override
+ public StatResult> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
+ StatRequest completedRequest = statRequestHandler.blockOrItemType(statistic, material);
+ return getStatResult(completedRequest);
+ }
+
+ @Override
+ public StatResult> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
+ StatRequest completedRequest = statRequestHandler.entityType(statistic, entityType);
+ return getStatResult(completedRequest);
+ }
+
+ private TopStatResult getStatResult(StatRequest completedRequest) {
+ LinkedHashMap stat = RequestExecutor.super.getStatCalculator()
+ .getTopStats(completedRequest);
+ TextComponent prettyStat = RequestExecutor.super.getStatFormatter()
+ .formatTopStat(completedRequest, stat);
+
+ return new TopStatResult(stat, prettyStat);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/InternalStatResult.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/InternalStatResult.java
new file mode 100644
index 0000000..37a1fa6
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/InternalStatResult.java
@@ -0,0 +1,30 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.result;
+
+import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
+import net.kyori.adventure.text.TextComponent;
+
+/** This Record is used to store stat-results internally, so Players can share them by clicking a share-button.*/
+public record InternalStatResult(String executorName, TextComponent formattedValue, int ID) implements StatResult {
+
+ /** Gets the ID number for this StatResult. Unlike for the other {@link StatResult} implementations,
+ this one does not return the actual statistic data, because this implementation is meant for internal
+ saving-and-sharing only. This method is only for Interface-consistency, InternalStatResult#ID is better.
+
+ @return Integer that represents this StatResult's ID number
+ */
+ @Override
+ public Integer getNumericalValue() {
+ return ID;
+ }
+
+ @Override
+ public TextComponent getFormattedTextComponent() {
+ return formattedValue;
+ }
+
+ @Override
+ public String toString() {
+ return ComponentUtils.getTranslatableComponentSerializer()
+ .serialize(formattedValue);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/PlayerStatResult.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/PlayerStatResult.java
new file mode 100644
index 0000000..fadec88
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/PlayerStatResult.java
@@ -0,0 +1,23 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.result;
+
+import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
+import net.kyori.adventure.text.TextComponent;
+
+public record PlayerStatResult(int value, TextComponent formattedValue) implements StatResult {
+
+ @Override
+ public Integer getNumericalValue() {
+ return value;
+ }
+
+ @Override
+ public TextComponent getFormattedTextComponent() {
+ return formattedValue;
+ }
+
+ @Override
+ public String toString() {
+ return ComponentUtils.getTranslatableComponentSerializer()
+ .serialize(formattedValue);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/ServerStatResult.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/ServerStatResult.java
new file mode 100644
index 0000000..4ed892b
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/ServerStatResult.java
@@ -0,0 +1,23 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.result;
+
+import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
+import net.kyori.adventure.text.TextComponent;
+
+public record ServerStatResult(long value, TextComponent formattedValue) implements StatResult {
+
+ @Override
+ public Long getNumericalValue() {
+ return value;
+ }
+
+ @Override
+ public TextComponent getFormattedTextComponent() {
+ return formattedValue;
+ }
+
+ @Override
+ public String toString() {
+ return ComponentUtils.getTranslatableComponentSerializer()
+ .serialize(formattedValue);
+ }
+}
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/StatResult.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/StatResult.java
new file mode 100644
index 0000000..0ee9249
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/StatResult.java
@@ -0,0 +1,63 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.result;
+
+import com.gmail.artemis.the.gr8.playerstats.api.Formatter;
+import net.kyori.adventure.platform.bukkit.BukkitAudiences;
+import net.kyori.adventure.text.TextComponent;
+
+/** Holds the result of a completed stat-lookup. The Type
parameter
+ T
of this StatResult represents the data type of the stored number:
+
+ -
Integer
for playerStat
+ -
Long
for serverStat
+ -
LinkedHashMap(String, Integer)
for topStat
+
+ You can get these raw numbers with {@link #getNumericalValue()}. Additionally,
+ you can get a formatted message that contains the following information:
+
+ - for playerStat:
+
[player-name]: [formatted-number] [stat-name] [sub-stat-name]
+ - for serverStat:
+
[Total on] [server-name]: [formatted-number] [stat-name] [sub-stat-name]
+ - for topStat:
+
[PlayerStats] [Top x] [stat-name] [sub-stat-name]
+
[1.] [player-name] [.....] [formatted-number]
+
[2.] [player-name] [.....] [formatted-number]
+
[3.] etc...
+
+
+ By default, the resulting message is a {@link TextComponent}, which can be sent directly
+ to a Minecraft client or console with the Adventure library. To send a Component,
+ you need to get a {@link BukkitAudiences} object, and use that to send the desired Component.
+ Normally you would have to add Adventure as a dependency to your project,
+ but since the library is included in PlayerStats, you can access it directly.
+ Information on how to get and use the BukkitAudiences object can be
+ found on Adventure's website.
+
+ You can also use the provided {@link #toString()} method to get the same information
+ in String-format. Don't use Adventure's toString methods on the Components
+ - those are for debugging purposes. And finally, if you want the results to be
+ formatted differently, you can get an instance of the {@link Formatter}.
+ */
+public interface StatResult {
+
+ /** Gets the raw number for the completed stat-lookup this {@link StatResult} stores.
+
+ @return {@code Integer} for playerStat, {@code Long} for serverStat,
+ and {@code LinkedHashMap} for topStat*/
+ T getNumericalValue();
+
+ /** Gets the formatted message for the completed stat-lookup this {@link StatResult} stores.
+
+ @return a {@code TextComponent} message containing the formatted number.
+ This message follows the same style/color/language settings that are specified in the
+ PlayerStats config. See class description for more information. */
+ TextComponent getFormattedTextComponent();
+
+ /** Turns the formatted message for the completed stat-lookup into String.
+
+ @return a String message containing the formatted number. This message follows
+ the same style and color settings that are specified in the PlayerStats config,
+ but it is not translatable (it is always plain English). See class description
+ for more information.*/
+ String toString();
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/TopStatResult.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/TopStatResult.java
new file mode 100644
index 0000000..8046a4b
--- /dev/null
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/statistic/result/TopStatResult.java
@@ -0,0 +1,25 @@
+package com.gmail.artemis.the.gr8.playerstats.statistic.result;
+
+import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
+import net.kyori.adventure.text.TextComponent;
+
+import java.util.LinkedHashMap;
+
+public record TopStatResult(LinkedHashMap value, TextComponent formattedValue) implements StatResult> {
+
+ @Override
+ public LinkedHashMap getNumericalValue() {
+ return value;
+ }
+
+ @Override
+ public TextComponent getFormattedTextComponent() {
+ return formattedValue;
+ }
+
+ @Override
+ public String toString() {
+ return ComponentUtils.getTranslatableComponentSerializer()
+ .serialize(formattedValue);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/EnumHandler.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/EnumHandler.java
index 08259f9..1a14b7a 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/EnumHandler.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/EnumHandler.java
@@ -19,54 +19,27 @@ import java.util.stream.Stream;
and turn a name into its corresponding enum constant. */
public final class EnumHandler {
- private final static List blockNames;
- private final static List entityNames;
- private final static List itemNames;
- private final static List statNames;
- private final static List subStatNames;
+ private static List blockNames;
+ private static List itemNames;
+ private static List statNames;
+ private static List subStatNames;
- static {
- blockNames = Arrays.stream(Material.values())
- .filter(Material::isBlock)
- .map(Material::toString)
- .map(String::toLowerCase)
- .collect(Collectors.toList());
-
- entityNames = Arrays.stream(EntityType.values())
- .map(EntityType::toString)
- .map(String::toLowerCase)
- .filter(entityName -> !entityName.equalsIgnoreCase("unknown"))
- .collect(Collectors.toList());
-
- itemNames = Arrays.stream(Material.values())
- .filter(Material::isItem)
- .map(Material::toString)
- .map(String::toLowerCase)
- .collect(Collectors.toList());
-
- subStatNames = Stream.of(blockNames, entityNames, itemNames)
- .flatMap(Collection::stream)
- .distinct()
- .collect(Collectors.toList());
-
- statNames = Arrays.stream(Statistic.values())
- .map(Statistic::toString)
- .map(String::toLowerCase)
- .collect(Collectors.toList());
+ public EnumHandler() {
+ prepareLists();
}
/** Returns all block-names in lowercase */
- public static List getBlockNames() {
+ public List getBlockNames() {
return blockNames;
}
/** Returns all item-names in lowercase*/
- public static List getItemNames() {
+ public List getItemNames() {
return itemNames;
}
/** Returns all statistic-names in lowercase */
- public static List getStatNames() {
+ public List getStatNames() {
return statNames;
}
@@ -115,31 +88,50 @@ public final class EnumHandler {
/** Checks if string is a valid statistic
@param statName String, case-insensitive */
- public static boolean isStatistic(@NotNull String statName) {
+ public boolean isStatistic(@NotNull String statName) {
return statNames.contains(statName.toLowerCase());
}
/** Checks whether the given String equals the name of an entity-type statistic. */
- public static boolean isEntityStatistic(String statName) {
+ public boolean isEntityStatistic(String statName) {
return statName.equalsIgnoreCase(Statistic.ENTITY_KILLED_BY.toString()) ||
statName.equalsIgnoreCase(Statistic.KILL_ENTITY.toString());
}
/** Checks if this statistic is a subStatEntry, meaning it is a block, item or entity
@param statName String, case-insensitive*/
- public static boolean isSubStatEntry(@NotNull String statName) {
+ public boolean isSubStatEntry(@NotNull String statName) {
return subStatNames.contains(statName.toLowerCase());
}
- /** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */
- public static String getSubStatTypeName(Statistic.Type statType) {
- String subStat = "sub-statistic";
- if (statType == null) return subStat;
- switch (statType) {
- case BLOCK -> subStat = "block";
- case ENTITY -> subStat = "entity";
- case ITEM -> subStat = "item";
- }
- return subStat;
+ private void prepareLists() {
+ List entityNames = Arrays.stream(EntityType.values())
+ .map(EntityType::toString)
+ .map(String::toLowerCase)
+ .filter(entityName -> !entityName.equalsIgnoreCase("unknown"))
+ .collect(Collectors.toList());
+
+ blockNames = Arrays.stream(Material.values())
+ .filter(Material::isBlock)
+ .map(Material::toString)
+ .map(String::toLowerCase)
+ .collect(Collectors.toList());
+
+ itemNames = Arrays.stream(Material.values())
+ .filter(Material::isItem)
+ .map(Material::toString)
+ .map(String::toLowerCase)
+ .collect(Collectors.toList());
+
+ subStatNames = Stream.of(blockNames, entityNames, itemNames)
+ .flatMap(Collection::stream)
+ .distinct()
+ .collect(Collectors.toList());
+
+ statNames = Arrays.stream(Statistic.values())
+ .map(Statistic::toString)
+ .map(String::toLowerCase)
+ .collect(Collectors.toList());
}
+
}
\ No newline at end of file
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MyLogger.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MyLogger.java
index 2322078..67fc2f2 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MyLogger.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/MyLogger.java
@@ -13,6 +13,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
+/** The PlayerStats Logger*/
public final class MyLogger {
private static final Logger logger;
@@ -36,10 +37,10 @@ public final class MyLogger {
}
/** Sets the desired debugging level.
- 1 = low (only show unexpected errors)
- 2 = medium (detail all encountered exceptions, log main tasks and show time taken)
- 3 = high (log all tasks and time taken)
- Default: 1
*/
+
1 = low (only show unexpected errors)
+
2 = medium (detail all encountered exceptions, log main tasks and show time taken)
+
3 = high (log all tasks and time taken)
+
Default: 1*/
public static void setDebugLevel(int level) {
if (level == 2) {
debugLevel = DebugLevel.MEDIUM;
@@ -80,6 +81,10 @@ public final class MyLogger {
}
}
+ public static void logException(@NotNull Exception exception, String caughtBy) {
+ logException(exception, caughtBy, null);
+ }
+
/** Log the encountered exception as a warning to console,
with some information about which class/method caught it
and with a printStackTrace if DebugLevel is HIGH.
@@ -96,7 +101,7 @@ public final class MyLogger {
}
}
- /** If DebugLevel is MEDIUM or HIGH, logs when the while loop in MessageWriter, getLanguageKey is being run. */
+ /** If DebugLevel is MEDIUM or HIGH, logs when the while loop in MessageBuilder, getLanguageKey is being run. */
public static void replacingUnderscores() {
if (debugLevel != DebugLevel.LOW) {
logger.info("Replacing underscores and capitalizing names...");
@@ -105,14 +110,14 @@ public final class MyLogger {
/** Output to console that the given thread has been created (but not started yet).*/
public static void threadCreated(String threadName) {
- if (debugLevel != DebugLevel.LOW) {
+ if (debugLevel == DebugLevel.HIGH) {
logger.info(threadName + " created!");
}
}
/** Output to console that the given thread has been started. */
public static void threadStart(String threadName) {
- if (debugLevel == DebugLevel.MEDIUM || debugLevel == DebugLevel.HIGH) {
+ if (debugLevel == DebugLevel.HIGH) {
logger.info(threadName + " started!");
}
}
@@ -190,14 +195,6 @@ public final class MyLogger {
}
}
- /** Output to console how long a certain task has taken (regardless of DebugLevel).
- @param className Name of the class executing the task
- @param methodName Name or description of the task
- @param startTime Timestamp marking the beginning of the task */
- public static void logTimeTaken(String className, String methodName, long startTime) {
- logTimeTaken(className, methodName, startTime, DebugLevel.LOW);
- }
-
/** Output to console how long a certain task has taken if DebugLevel is equal to or higher than the specified threshold.
@param className Name of the class executing the task
@param methodName Name or description of the task
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/OfflinePlayerHandler.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/OfflinePlayerHandler.java
index 8aa6719..bccbce3 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/OfflinePlayerHandler.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/OfflinePlayerHandler.java
@@ -7,7 +7,10 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
-public class OfflinePlayerHandler {
+/** A utility class that deals with OfflinePlayers. It stores a list of all OfflinePlayer-names
+ that need to be included in statistic calculations, and can retrieve the corresponding OfflinePlayer
+ object for a given player-name.*/
+public final class OfflinePlayerHandler {
private static ConcurrentHashMap offlinePlayerUUIDs;
private static ArrayList playerNames;
@@ -17,6 +20,16 @@ public class OfflinePlayerHandler {
playerNames = new ArrayList<>();
}
+ /**
+ * Get a new HashMap that stores the players to include in stat calculations.
+ * This HashMap is stored as a private variable in OfflinePlayerHandler.
+ * @param playerList ConcurrentHashMap with keys: playerNames and values: UUIDs
+ */
+ public static void updateOfflinePlayerList(ConcurrentHashMap playerList) {
+ offlinePlayerUUIDs = playerList;
+ playerNames = Collections.list(offlinePlayerUUIDs.keys());
+ }
+
/** Checks if a given playerName is on the private HashMap of players that should be included in statistic calculations
@param playerName String, case-sensitive */
public boolean isRelevantPlayer(String playerName) {
@@ -33,16 +46,6 @@ public class OfflinePlayerHandler {
return playerNames;
}
- /**
- * Get a new HashMap that stores the players to include in stat calculations.
- * This HashMap is stored as a private variable in OfflinePlayerHandler.
- * @param playerList ConcurrentHashMap with keys: playerNames and values: UUIDs
- */
- public void updateOfflinePlayerList(ConcurrentHashMap playerList) {
- offlinePlayerUUIDs = playerList;
- playerNames = Collections.list(offlinePlayerUUIDs.keys());
- }
-
/**
* Uses the playerName to get the player's UUID from a private HashMap, and uses the UUID to get the corresponding OfflinePlayer Object.
* @param playerName name of the target player
@@ -53,6 +56,7 @@ public class OfflinePlayerHandler {
return Bukkit.getOfflinePlayer(offlinePlayerUUIDs.get(playerName));
}
else {
+ MyLogger.logMsg("Cannot calculate statistics for player-name: " + playerName, true);
return null;
}
}
diff --git a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/UnixTimeHandler.java b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/UnixTimeHandler.java
index 32134ce..ae21edb 100644
--- a/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/UnixTimeHandler.java
+++ b/src/main/java/com/gmail/artemis/the/gr8/playerstats/utils/UnixTimeHandler.java
@@ -1,5 +1,6 @@
package com.gmail.artemis.the.gr8.playerstats.utils;
+/** A small utility class that calculates with unix time.*/
public final class UnixTimeHandler {
private UnixTimeHandler() {
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 1a6fa87..c7dfe99 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -4,6 +4,8 @@ version: 1.6.1
api-version: 1.13
description: adds commands to view player statistics in chat
author: Artemis_the_gr8
+softdepend:
+ - PlaceholderAPI
commands:
statistic:
aliases: