mirror of
https://github.com/itHotL/PlayerStats.git
synced 2024-11-26 12:36:16 +01:00
Merge pull request #76 from itHotL/message-factory-rework
Message factory rework
This commit is contained in:
commit
054c8b6d7c
@ -23,21 +23,30 @@
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer>
|
||||
<mainClass>com.gmail.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.jetbrains:annotations</exclude>
|
||||
<exclude>META-INF/versions/**</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer>
|
||||
<mainClass>com.gmail.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.jetbrains:annotations</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/versions/**</exclude>
|
||||
<exclude>META-INF/maven/com.tchristofferson/**</exclude>
|
||||
<exclude>images/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
37
pom.xml
37
pom.xml
@ -92,23 +92,32 @@
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.gmail.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.jetbrains:annotations</exclude>
|
||||
<exclude>META-INF/versions/**</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.gmail.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.jetbrains:annotations</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/versions/**</exclude>
|
||||
<exclude>META-INF/maven/com.tchristofferson/**</exclude>
|
||||
<exclude>images/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -6,8 +6,8 @@ import com.gmail.artemis.the.gr8.playerstats.commands.TabCompleter;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.listeners.JoinListener;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.LanguageKeyHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.PrideMessageFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.PrideComponentFactory;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
@ -31,20 +31,19 @@ public class Main extends JavaPlugin {
|
||||
//initialize the Adventure library
|
||||
adventure = BukkitAudiences.create(this);
|
||||
|
||||
//first get an instance of the ConfigHandler and LanguageKeyHandler
|
||||
//first get an instance of the ConfigHandler
|
||||
ConfigHandler config = new ConfigHandler(this);
|
||||
LanguageKeyHandler language = new LanguageKeyHandler();
|
||||
|
||||
//for now always use the PrideMessageFactory (it'll use the regular formatting when needed)
|
||||
MessageFactory messageFactory = new PrideMessageFactory(config, language);
|
||||
//for now always use the PrideComponentFactory (it'll use the regular formatting when needed)
|
||||
MessageWriter messageWriter = new MessageWriter(config);
|
||||
|
||||
//initialize the threadManager
|
||||
ThreadManager threadManager = new ThreadManager(adventure(), config, messageFactory, this);
|
||||
ThreadManager threadManager = new ThreadManager(adventure(), config, messageWriter, this);
|
||||
|
||||
//register all commands and the tabCompleter
|
||||
PluginCommand statcmd = this.getCommand("statistic");
|
||||
if (statcmd != null) {
|
||||
statcmd.setExecutor(new StatCommand(adventure(), messageFactory, threadManager));
|
||||
statcmd.setExecutor(new StatCommand(adventure(), messageWriter, threadManager));
|
||||
statcmd.setTabCompleter(new TabCompleter());
|
||||
}
|
||||
PluginCommand reloadcmd = this.getCommand("statisticreload");
|
||||
|
@ -1,10 +1,10 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
|
||||
import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@ -19,16 +19,16 @@ public class ThreadManager {
|
||||
private final Main plugin;
|
||||
private final BukkitAudiences adventure;
|
||||
private static ConfigHandler config;
|
||||
private static MessageFactory messageFactory;
|
||||
private static MessageWriter messageWriter;
|
||||
|
||||
private ReloadThread reloadThread;
|
||||
private StatThread statThread;
|
||||
private static long lastRecordedCalcTime;
|
||||
|
||||
public ThreadManager(BukkitAudiences a, ConfigHandler c, MessageFactory m, Main p) {
|
||||
public ThreadManager(BukkitAudiences a, ConfigHandler c, MessageWriter m, Main p) {
|
||||
adventure = a;
|
||||
config = c;
|
||||
messageFactory = m;
|
||||
messageWriter = m;
|
||||
plugin = p;
|
||||
|
||||
statThreadID = 0;
|
||||
@ -41,7 +41,7 @@ public class ThreadManager {
|
||||
if (reloadThread == null || !reloadThread.isAlive()) {
|
||||
reloadThreadID += 1;
|
||||
|
||||
reloadThread = new ReloadThread(adventure, config, messageFactory, plugin, threshold, reloadThreadID, statThread, sender);
|
||||
reloadThread = new ReloadThread(adventure, config, messageWriter, threshold, reloadThreadID, statThread, sender);
|
||||
reloadThread.start();
|
||||
}
|
||||
else {
|
||||
@ -52,7 +52,7 @@ public class ThreadManager {
|
||||
public void startStatThread(StatRequest request) {
|
||||
statThreadID += 1;
|
||||
|
||||
statThread = new StatThread(adventure, config, messageFactory, plugin, statThreadID, threshold, request, reloadThread);
|
||||
statThread = new StatThread(adventure, config, messageWriter, plugin, statThreadID, threshold, request, reloadThread);
|
||||
statThread.start();
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.commands;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.Command;
|
||||
@ -19,31 +22,44 @@ import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
|
||||
public class StatCommand implements CommandExecutor {
|
||||
|
||||
private final BukkitAudiences adventure;
|
||||
private final MessageFactory messageFactory;
|
||||
private final MessageWriter messageWriter;
|
||||
private final ThreadManager threadManager;
|
||||
|
||||
public StatCommand(BukkitAudiences a, MessageFactory m, ThreadManager t) {
|
||||
public StatCommand(BukkitAudiences a, MessageWriter m, ThreadManager t) {
|
||||
adventure = a;
|
||||
messageFactory = m;
|
||||
messageWriter = m;
|
||||
threadManager = t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
boolean isBukkitConsole = sender instanceof ConsoleCommandSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit");
|
||||
if (args.length == 0 || args[0].equalsIgnoreCase("help")) { //in case of less than 1 argument or "help", display the help message
|
||||
adventure.sender(sender).sendMessage(messageFactory.helpMsg(sender instanceof ConsoleCommandSender));
|
||||
adventure.sender(sender).sendMessage(messageWriter.helpMsg(sender instanceof ConsoleCommandSender));
|
||||
}
|
||||
else if (args[0].equalsIgnoreCase("examples") ||
|
||||
args[0].equalsIgnoreCase("example")) { //in case of "statistic examples", show examples
|
||||
adventure.sender(sender).sendMessage(messageFactory.usageExamples(sender instanceof ConsoleCommandSender));
|
||||
adventure.sender(sender).sendMessage(messageWriter.usageExamples(isBukkitConsole));
|
||||
}
|
||||
else if (args[0].equalsIgnoreCase("test")) {
|
||||
TextComponent msg = text("Tier 1").color(PluginColor.GOLD.getColor())
|
||||
.append(text("Tier 2").color(PluginColor.MEDIUM_GOLD.getColor())
|
||||
.append(text("Tier 3").color(TextColor.fromHexString("#FFEA40"))
|
||||
.append(text("Tier 4").color(PluginColor.LIGHT_GOLD.getColor()))
|
||||
.append(text("Tier 3?")))
|
||||
.append(text("Tier 2?")))
|
||||
.append(text("Tier 1?"));
|
||||
adventure.sender(sender).sendMessage(msg);
|
||||
}
|
||||
else {
|
||||
StatRequest request = generateRequest(sender, args);
|
||||
TextComponent issues = checkRequest(request);
|
||||
TextComponent issues = checkRequest(request, isBukkitConsole);
|
||||
if (issues == null) {
|
||||
threadManager.startStatThread(request);
|
||||
}
|
||||
@ -140,20 +156,19 @@ public class StatCommand implements CommandExecutor {
|
||||
<p>2. Is a subStat needed, and is a subStat Enum Constant present? (block/entity/item)</p>
|
||||
<p>3. If the target is PLAYER, is a valid PlayerName provided? </p>
|
||||
@return null if the Request is valid, and an explanation message otherwise. */
|
||||
private @Nullable TextComponent checkRequest(StatRequest request) {
|
||||
boolean isConsoleSender = request.getCommandSender() instanceof ConsoleCommandSender;
|
||||
private @Nullable TextComponent checkRequest(StatRequest request, boolean isBukkitConsole) {
|
||||
if (request.getStatistic() == null) {
|
||||
return messageFactory.missingStatName(isConsoleSender);
|
||||
return messageWriter.missingStatName(isBukkitConsole);
|
||||
}
|
||||
Statistic.Type type = request.getStatistic().getType();
|
||||
if (request.getSubStatEntry() == null && type != Statistic.Type.UNTYPED) {
|
||||
return messageFactory.missingSubStatName(type, isConsoleSender);
|
||||
return messageWriter.missingSubStatName(type, isBukkitConsole);
|
||||
}
|
||||
else if (!matchingSubStat(request)) {
|
||||
return messageFactory.wrongSubStatType(type, request.getSubStatEntry(), isConsoleSender);
|
||||
return messageWriter.wrongSubStatType(type, request.getSubStatEntry(), isBukkitConsole);
|
||||
}
|
||||
else if (request.getSelection() == Target.PLAYER && request.getPlayerName() == null) {
|
||||
return messageFactory.missingPlayerName(isConsoleSender);
|
||||
return messageWriter.missingPlayerName(isBukkitConsole);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
|
@ -26,7 +26,7 @@ public class ConfigHandler {
|
||||
configVersion = 4;
|
||||
checkConfigVersion();
|
||||
|
||||
MyLogger.setDebugLevel(debugLevel());
|
||||
MyLogger.setDebugLevel(getDebugLevel());
|
||||
}
|
||||
|
||||
/** Checks the number that "config-version" returns to see if the config needs updating, and if so, send it to the Updater.
|
||||
@ -56,7 +56,6 @@ public class ConfigHandler {
|
||||
}
|
||||
try {
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
MyLogger.setDebugLevel(debugLevel());
|
||||
return true;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
@ -70,7 +69,7 @@ public class ConfigHandler {
|
||||
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
|
||||
<p>3 = high (log all tasks and time taken)</p>
|
||||
<p>Default: 1</p>*/
|
||||
public int debugLevel() {
|
||||
public int getDebugLevel() {
|
||||
return config.getInt("debug-level", 1);
|
||||
}
|
||||
|
||||
@ -88,7 +87,7 @@ public class ConfigHandler {
|
||||
|
||||
/** Returns the number of maximum days since a player has last been online.
|
||||
<p>Default: 0 (which signals not to use this limit)</p>*/
|
||||
public int lastPlayedLimit() {
|
||||
public int getLastPlayedLimit() {
|
||||
return config.getInt("number-of-days-since-last-joined", 0);
|
||||
}
|
||||
|
||||
@ -112,7 +111,7 @@ public class ConfigHandler {
|
||||
|
||||
/** Whether to use rainbow colors for the [PlayerStats] prefix rather than the default gold/purple.
|
||||
<p>Default: false</p> */
|
||||
public boolean useRainbowPrefix() {
|
||||
public boolean useRainbowMode() {
|
||||
return config.getBoolean("rainbow-mode", false);
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ public class ConfigUpdateHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/** Adjusts some of the default colors to migrate from versions 2 or 3 to version 4.*/
|
||||
/** Adjusts some of the default colors to migrate from versions 2 or 3 to version 4.1.*/
|
||||
private void updateDefaultColors(YamlConfiguration configuration) {
|
||||
updateColor(configuration, "top-list.title", "yellow", "#FFD52B");
|
||||
updateColor(configuration, "top-list.stat-names", "yellow", "#FFD52B");
|
||||
|
@ -0,0 +1,40 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.enums;
|
||||
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
|
||||
/** This enum represents the colorscheme PlayerStats uses in its output messages.
|
||||
<p>GRAY: ChatColor Gray</p>
|
||||
<p>DARK_PURPLE: #6E3485 (used for default sub-titles, title-underscores and brackets)</p>
|
||||
<p>MEDIUM_BLUE: #55AAFF (used for all plain feedback and error messages)</p>
|
||||
<p>LIGHT_BLUE: #55C6FF (used for default hover-text)</p>
|
||||
<p>GOLD: ChatColor Gold (used for first parts of usage messages and for first parts of hover-text accent)</p>
|
||||
<p>MEDIUM_GOLD: #FFD52B (used for second parts of usage messages and for second parts of hover-text accent) </p>
|
||||
<p>LIGHT_GOLD: #FFEA40 (used for third parts of usage messages)</p>
|
||||
<p>LIGHT_YELLOW: #FFFF8E (used for last parts of explanation message)</p>
|
||||
*/
|
||||
public enum PluginColor {
|
||||
GRAY (NamedTextColor.GRAY), //#AAAAAA
|
||||
DARK_PURPLE (TextColor.fromHexString("#6E3485")),
|
||||
MEDIUM_BLUE (TextColor.fromHexString("#55AAFF")),
|
||||
LIGHT_BLUE (TextColor.fromHexString("#55C6FF")),
|
||||
GOLD (NamedTextColor.GOLD), //#FFAA00
|
||||
MEDIUM_GOLD (TextColor.fromHexString("#FFD52B")),
|
||||
LIGHT_GOLD (TextColor.fromHexString("#FFEA40")),
|
||||
LIGHT_YELLOW (TextColor.fromHexString("#FFFF8E"));
|
||||
|
||||
|
||||
private final TextColor color;
|
||||
|
||||
PluginColor(TextColor color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public TextColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public TextColor getConsoleColor() {
|
||||
return NamedTextColor.nearestTo(color);
|
||||
}
|
||||
}
|
@ -0,0 +1,359 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.NumberFormatter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.util.Index;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
/** Creates Components with the desired formatting. This class can put Strings
|
||||
into formatted Components with TextColor and TextDecoration, and turn
|
||||
certain Strings into appropriate LanguageKeys to return a TranslatableComponent.*/
|
||||
public class ComponentFactory {
|
||||
|
||||
private static ConfigHandler config;
|
||||
|
||||
public ComponentFactory(ConfigHandler c) {
|
||||
config = c;
|
||||
}
|
||||
|
||||
/** Returns [PlayerStats] followed by a single space. */
|
||||
public TextComponent pluginPrefix(boolean isBukkitConsole) {
|
||||
return text("[")
|
||||
.color(PluginColor.GRAY.getColor())
|
||||
.append(text("PlayerStats").color(PluginColor.GOLD.getColor()))
|
||||
.append(text("]"))
|
||||
.append(space());
|
||||
}
|
||||
|
||||
/** Returns [PlayerStats] surrounded by underscores on both sides. */
|
||||
public TextComponent prefixTitle(boolean isBukkitConsole) {
|
||||
String underscores = "____________"; //12 underscores for both console and in-game
|
||||
TextColor underscoreColor = isBukkitConsole ?
|
||||
PluginColor.DARK_PURPLE.getConsoleColor() : PluginColor.DARK_PURPLE.getColor();
|
||||
|
||||
return text(underscores).color(underscoreColor)
|
||||
.append(text(" ")) //4 spaces
|
||||
.append(pluginPrefix(isBukkitConsole))
|
||||
.append(text(" ")) //3 spaces (since prefix already has one)
|
||||
.append(text(underscores));
|
||||
}
|
||||
|
||||
/** Returns a TextComponent with the input String as content, with color Gray and decoration Italic.*/
|
||||
public TextComponent subTitle(String content) {
|
||||
return text(content).color(PluginColor.GRAY.getColor()).decorate(TextDecoration.ITALIC);
|
||||
}
|
||||
|
||||
/** Returns a TextComponents that represents a full message, with [PlayerStats] prepended. */
|
||||
public TextComponent msg(String msg, boolean isBukkitConsole) {
|
||||
return pluginPrefix(isBukkitConsole)
|
||||
.append(text(msg)
|
||||
.color(PluginColor.MEDIUM_BLUE.getColor()));
|
||||
}
|
||||
|
||||
/** Returns a plain TextComponent that represents a single message line.
|
||||
A space will be inserted after part1, part2 and part3.
|
||||
Each message part has its own designated color.
|
||||
@param part1 color DARK_GOLD
|
||||
@param part2 color MEDIUM_GOLD
|
||||
@param part3 color YELLOW
|
||||
@param part4 color GRAY
|
||||
*/
|
||||
public TextComponent msgPart(@Nullable String part1, @Nullable String part2, @Nullable String part3, @Nullable String part4) {
|
||||
return msgPart(part1, part2, part3, part4, false);
|
||||
}
|
||||
|
||||
/** Returns a plain TextComponent that represents a single message line.
|
||||
A space will be inserted after part1, part2 and part3.
|
||||
Each message part has its own designated color.
|
||||
if isBukkitConsole is true, the colors will be the nearest ChatColor to the below colors.
|
||||
@param part1 color DARK_GOLD
|
||||
@param part2 color MEDIUM_GOLD
|
||||
@param part3 color YELLOW
|
||||
@param part4 color GRAY
|
||||
*/
|
||||
public TextComponent msgPart(@Nullable String part1, @Nullable String part2, @Nullable String part3, @Nullable String part4, boolean isBukkitConsole) {
|
||||
TextComponent.Builder msg = Component.text();
|
||||
if (part1 != null) {
|
||||
TextColor pluginColor = isBukkitConsole ? PluginColor.GOLD.getConsoleColor() : PluginColor.GOLD.getColor();
|
||||
msg.append(text(part1)
|
||||
.color(pluginColor))
|
||||
.append(space());
|
||||
}
|
||||
if (part2 != null) {
|
||||
TextColor pluginColor = isBukkitConsole ? PluginColor.MEDIUM_GOLD.getConsoleColor() : PluginColor.MEDIUM_GOLD.getColor();
|
||||
msg.append(text(part2)
|
||||
.color(pluginColor))
|
||||
.append(space());
|
||||
}
|
||||
if (part3 != null) {
|
||||
TextColor pluginColor = isBukkitConsole ? PluginColor.LIGHT_GOLD.getConsoleColor() : PluginColor.LIGHT_GOLD.getColor();
|
||||
msg.append(text(part3)
|
||||
.color(pluginColor))
|
||||
.append(space());
|
||||
}
|
||||
if (part4 != null) {
|
||||
TextColor pluginColor = isBukkitConsole ? PluginColor.GRAY.getConsoleColor() : PluginColor.GRAY.getColor();
|
||||
msg.append(text(part4)
|
||||
.color(pluginColor));
|
||||
}
|
||||
return msg.build();
|
||||
}
|
||||
|
||||
/** Returns a TextComponent with a single line of hover-text in the specified color.
|
||||
@param plainText the base message
|
||||
@param hoverText the hovering text
|
||||
@param hoverColor color of the hovering text */
|
||||
public TextComponent simpleHoverPart(String plainText, String hoverText, PluginColor hoverColor) {
|
||||
return simpleHoverPart(plainText, null, hoverText, hoverColor);
|
||||
}
|
||||
|
||||
/** Returns a TextComponent with a single line of hover-text in the specified color.
|
||||
If a PluginColor is provided for the plainText, the base color is set as well.
|
||||
@param plainText the base message
|
||||
@param plainColor color of the base message
|
||||
@param hoverText the hovering text
|
||||
@param hoverColor color of the hovering text */
|
||||
public TextComponent simpleHoverPart(String plainText, @Nullable PluginColor plainColor, String hoverText, PluginColor hoverColor) {
|
||||
TextComponent.Builder msg = Component.text()
|
||||
.append(text(plainText))
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text(hoverText)
|
||||
.color(hoverColor.getColor())));
|
||||
if (plainColor != null) {
|
||||
msg.color(plainColor.getColor());
|
||||
}
|
||||
return msg.build();
|
||||
}
|
||||
|
||||
/** Returns a TextComponent with hover-text that can consist of three different parts,
|
||||
divided over two different lines. Each part has its own designated color. If all the
|
||||
input Strings are null, it will return an empty Component.
|
||||
@param plainText the non-hovering part
|
||||
@param color the color for the non-hovering part
|
||||
@param hoverLineOne text on the first line, with color LIGHT_BLUE
|
||||
@param hoverLineTwoA text on the second line, with color GOLD
|
||||
@param hoverLineTwoB text on the second part of the second line, with color LIGHT_GOLD
|
||||
*/
|
||||
public TextComponent complexHoverPart(@NotNull String plainText, @NotNull PluginColor color, String hoverLineOne, String hoverLineTwoA, String hoverLineTwoB) {
|
||||
TextComponent base = Component.text(plainText).color(color.getColor());
|
||||
TextComponent.Builder hoverText = Component.text();
|
||||
if (hoverLineOne != null) {
|
||||
hoverText.append(text(hoverLineOne)
|
||||
.color(PluginColor.LIGHT_BLUE.getColor()));
|
||||
if (hoverLineTwoA != null || hoverLineTwoB != null) {
|
||||
hoverText.append(newline());
|
||||
}
|
||||
}
|
||||
if (hoverLineTwoA != null) {
|
||||
hoverText.append(text(hoverLineTwoA)
|
||||
.color(PluginColor.GOLD.getColor()));
|
||||
if (hoverLineTwoB != null) {
|
||||
hoverText.append(space());
|
||||
}
|
||||
}
|
||||
if (hoverLineTwoB != null) {
|
||||
hoverText.append(text(hoverLineTwoB).color(PluginColor.LIGHT_GOLD.getColor()));
|
||||
}
|
||||
return base.hoverEvent(HoverEvent.showText(hoverText.build()));
|
||||
}
|
||||
|
||||
|
||||
public TextComponent playerName(String playerName, Target selection) {
|
||||
return createComponent(playerName,
|
||||
getColorFromString(config.getPlayerNameFormatting(selection, false)),
|
||||
getStyleFromString(config.getPlayerNameFormatting(selection, true)));
|
||||
}
|
||||
|
||||
public TranslatableComponent statName(@NotNull StatRequest request) {
|
||||
String statName = request.getStatistic().name();
|
||||
String subStatName = request.getSubStatEntry();
|
||||
if (!config.useTranslatableComponents()) {
|
||||
statName = getPrettyName(statName);
|
||||
subStatName = getPrettyName(subStatName);
|
||||
}
|
||||
else {
|
||||
statName = LanguageKeyHandler.getStatKey(request.getStatistic());
|
||||
switch (request.getStatistic().getType()) {
|
||||
case BLOCK -> subStatName = LanguageKeyHandler.getBlockKey(request.getBlock());
|
||||
case ENTITY -> subStatName = LanguageKeyHandler.getEntityKey(request.getEntity());
|
||||
case ITEM -> subStatName = LanguageKeyHandler.getItemKey(request.getItem());
|
||||
case UNTYPED -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return statName(statName, subStatName, request.getSelection());
|
||||
}
|
||||
|
||||
private TranslatableComponent statName(@NotNull String statKey, String subStatKey, @NotNull Target selection) {
|
||||
TranslatableComponent.Builder totalName;
|
||||
TextComponent subStat = subStatName(subStatKey, selection);
|
||||
TextColor statNameColor = getColorFromString(config.getStatNameFormatting(selection, false));
|
||||
TextDecoration statNameStyle = getStyleFromString(config.getStatNameFormatting(selection, true));
|
||||
|
||||
if (statKey.equalsIgnoreCase("stat_type.minecraft.killed") && subStat != null) {
|
||||
totalName = killEntity(subStat);
|
||||
}
|
||||
else if (statKey.equalsIgnoreCase("stat_type.minecraft.killed_by") && subStat != null) {
|
||||
totalName = entityKilledBy(subStat);
|
||||
}
|
||||
else {
|
||||
totalName = translatable().key(statKey);
|
||||
if (subStat != null) totalName.append(space()).append(subStat);
|
||||
}
|
||||
|
||||
if (statNameStyle != null) totalName.decoration(statNameStyle, TextDecoration.State.TRUE);
|
||||
return totalName
|
||||
.color(statNameColor)
|
||||
.build();
|
||||
}
|
||||
|
||||
private @Nullable TextComponent subStatName(@Nullable String subStatName, Target selection) {
|
||||
if (subStatName != null) {
|
||||
TextDecoration style = getStyleFromString(config.getSubStatNameFormatting(selection, true));
|
||||
TextComponent.Builder subStat = text()
|
||||
.append(text("("))
|
||||
.append(translatable()
|
||||
.key(subStatName))
|
||||
.append(text(")"))
|
||||
.color(getColorFromString(config.getSubStatNameFormatting(selection, false)));
|
||||
|
||||
subStat.decorations(TextDecoration.NAMES.values(), false);
|
||||
if (style != null) subStat.decoration(style, TextDecoration.State.TRUE);
|
||||
return subStat.build();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Construct a custom translation for kill_entity with the language key for commands.kill.success.single ("Killed %s").
|
||||
@return a TranslatableComponent Builder with the subStat Component as args.*/
|
||||
private TranslatableComponent.Builder killEntity(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key("commands.kill.success.single") //"Killed %s"
|
||||
.args(subStat);
|
||||
}
|
||||
|
||||
/** Construct a custom translation for entity_killed_by with the language keys for stat.minecraft.deaths
|
||||
("Number of Deaths") and book.byAuthor ("by %s").
|
||||
@return a TranslatableComponent Builder with stat.minecraft.deaths as key, with a ChildComponent
|
||||
with book.byAuthor as key and the subStat Component as args.*/
|
||||
private TranslatableComponent.Builder entityKilledBy(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key("stat.minecraft.deaths") //"Number of Deaths"
|
||||
.append(space())
|
||||
.append(translatable()
|
||||
.key("book.byAuthor") //"by %s"
|
||||
.args(subStat));
|
||||
}
|
||||
|
||||
public TextComponent statNumber(long number, Target selection) {
|
||||
return createComponent(NumberFormatter.format(number),
|
||||
getColorFromString(config.getStatNumberFormatting(selection, false)),
|
||||
getStyleFromString(config.getStatNumberFormatting(selection, true)));
|
||||
}
|
||||
|
||||
public TextComponent title(String content, Target selection) {
|
||||
return createComponent(content,
|
||||
getColorFromString(config.getTitleFormatting(selection, false)),
|
||||
getStyleFromString(config.getTitleFormatting(selection, true)));
|
||||
}
|
||||
|
||||
public TextComponent titleNumber(int number) {
|
||||
return createComponent(number + "",
|
||||
getColorFromString(config.getTitleNumberFormatting(false)),
|
||||
getStyleFromString(config.getTitleNumberFormatting(true)));
|
||||
}
|
||||
|
||||
public TextComponent serverName(String serverName) {
|
||||
TextComponent colon = text(":").color(getColorFromString(config.getServerNameFormatting(false)));
|
||||
return createComponent(serverName,
|
||||
getColorFromString(config.getServerNameFormatting(false)),
|
||||
getStyleFromString(config.getServerNameFormatting(true)))
|
||||
.append(colon);
|
||||
}
|
||||
|
||||
public TextComponent rankingNumber(String number) {
|
||||
return createComponent(number,
|
||||
getColorFromString(config.getRankNumberFormatting(false)),
|
||||
getStyleFromString(config.getRankNumberFormatting(true)));
|
||||
}
|
||||
|
||||
public TextComponent dots(String dots) {
|
||||
return createComponent(dots,
|
||||
getColorFromString(config.getDotsFormatting(false)),
|
||||
getStyleFromString(config.getDotsFormatting(true)));
|
||||
}
|
||||
|
||||
private TextComponent createComponent(String content, TextColor color, @Nullable TextDecoration style) {
|
||||
return style == null ? text(content).color(color) : text(content).color(color).decoration(style, TextDecoration.State.TRUE);
|
||||
}
|
||||
|
||||
/** Replace "_" with " " and capitalize each first letter of the input.
|
||||
@param input String to prettify, case-insensitive*/
|
||||
private String getPrettyName(String input) {
|
||||
if (input == null) return null;
|
||||
StringBuilder capitals = new StringBuilder(input.toLowerCase());
|
||||
capitals.setCharAt(0, Character.toUpperCase(capitals.charAt(0)));
|
||||
while (capitals.indexOf("_") != -1) {
|
||||
MyLogger.replacingUnderscores();
|
||||
|
||||
int index = capitals.indexOf("_");
|
||||
capitals.setCharAt(index + 1, Character.toUpperCase(capitals.charAt(index + 1)));
|
||||
capitals.setCharAt(index, ' ');
|
||||
}
|
||||
return capitals.toString();
|
||||
}
|
||||
|
||||
private TextColor getColorFromString(String configString) {
|
||||
if (configString != null) {
|
||||
try {
|
||||
if (configString.contains("#")) {
|
||||
return TextColor.fromHexString(configString);
|
||||
}
|
||||
else {
|
||||
return getTextColorByName(configString);
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException exception) {
|
||||
Bukkit.getLogger().warning(exception.toString());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TextColor getTextColorByName(String textColor) {
|
||||
Index<String, NamedTextColor> names = NamedTextColor.NAMES;
|
||||
return names.value(textColor);
|
||||
}
|
||||
|
||||
private @Nullable TextDecoration getStyleFromString(@NotNull String configString) {
|
||||
if (configString.equalsIgnoreCase("none")) {
|
||||
return null;
|
||||
}
|
||||
else if (configString.equalsIgnoreCase("magic")) {
|
||||
return TextDecoration.OBFUSCATED;
|
||||
}
|
||||
else {
|
||||
Index<String, TextDecoration> styles = TextDecoration.NAMES;
|
||||
return styles.value(configString);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -12,14 +12,17 @@ import java.util.HashMap;
|
||||
|
||||
public class LanguageKeyHandler {
|
||||
|
||||
private final HashMap<Statistic, String> statNameKeys;
|
||||
private final static HashMap<Statistic, String> statNameKeys;
|
||||
|
||||
public LanguageKeyHandler() {
|
||||
static {
|
||||
statNameKeys = new HashMap<>();
|
||||
generateStatNameKeys();
|
||||
}
|
||||
|
||||
public String getStatKey(@NotNull Statistic statistic) {
|
||||
private LanguageKeyHandler() {
|
||||
}
|
||||
|
||||
public static String getStatKey(@NotNull Statistic statistic) {
|
||||
if (statistic.getType() == Statistic.Type.UNTYPED) {
|
||||
return "stat.minecraft." + statNameKeys.get(statistic);
|
||||
}
|
||||
@ -30,7 +33,7 @@ public class LanguageKeyHandler {
|
||||
|
||||
/** Get the official Key from the NameSpacedKey for this entityType,
|
||||
or return null if no enum constant can be retrieved or entityType is UNKNOWN.*/
|
||||
public @Nullable String getEntityKey(EntityType entity) {
|
||||
public static @Nullable String getEntityKey(EntityType entity) {
|
||||
if (entity == null || entity == EntityType.UNKNOWN) return null;
|
||||
else {
|
||||
return "entity.minecraft." + entity.getKey().getKey();
|
||||
@ -39,7 +42,7 @@ public class LanguageKeyHandler {
|
||||
|
||||
/** Get the official Key from the NameSpacedKey for this item Material,
|
||||
or return null if no enum constant can be retrieved.*/
|
||||
public @Nullable String getItemKey(Material item) {
|
||||
public static @Nullable String getItemKey(Material item) {
|
||||
if (item == null) return null;
|
||||
else if (item.isBlock()) {
|
||||
return getBlockKey(item);
|
||||
@ -51,7 +54,7 @@ public class LanguageKeyHandler {
|
||||
|
||||
/** Returns the official Key from the NameSpacedKey for the block Material provided,
|
||||
or return null if no enum constant can be retrieved.*/
|
||||
public @Nullable String getBlockKey(Material block) {
|
||||
public static @Nullable String getBlockKey(Material block) {
|
||||
if (block == null) return null;
|
||||
else if (block.toString().toLowerCase().contains("wall_banner")) { //replace wall_banner with regular banner, since there is no key for wall banners
|
||||
String blockName = block.toString().toLowerCase().replace("wall_", "");
|
||||
@ -63,11 +66,11 @@ public class LanguageKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDefaultKeys() {
|
||||
private static void generateDefaultKeys() {
|
||||
Arrays.stream(Statistic.values()).forEach(statistic -> statNameKeys.put(statistic, statistic.toString().toLowerCase()));
|
||||
}
|
||||
|
||||
private void generateStatNameKeys() {
|
||||
private static void generateStatNameKeys() {
|
||||
//get the enum names for all statistics first
|
||||
generateDefaultKeys();
|
||||
|
||||
|
@ -1,556 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.NumberFormatter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.util.Index;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.map.MinecraftFont;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
|
||||
public class MessageFactory {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private final LanguageKeyHandler language;
|
||||
|
||||
private final TextColor msgColor; //my favorite shade of light blue, somewhere between blue and aqua
|
||||
private final TextColor hoverBaseColor; //light blue - one shade lighter than msgColor
|
||||
private final TextColor accentColor1; //gold - one shade lighter than standard gold
|
||||
private final TextColor accentColor2; //yellow - a few shades darker than standard yellow
|
||||
|
||||
public MessageFactory(ConfigHandler c, LanguageKeyHandler l) {
|
||||
config = c;
|
||||
language = l;
|
||||
|
||||
msgColor = TextColor.fromHexString("#55AAFF");
|
||||
hoverBaseColor = TextColor.fromHexString("#55C6FF");
|
||||
accentColor1 = TextColor.fromHexString("#FFB80E");
|
||||
accentColor2 = TextColor.fromHexString("#FFD52B");
|
||||
}
|
||||
|
||||
protected TextComponent pluginPrefix(boolean isConsoleSender) {
|
||||
return text("[")
|
||||
.color(NamedTextColor.GRAY)
|
||||
.append(text("PlayerStats").color(NamedTextColor.GOLD))
|
||||
.append(text("]"))
|
||||
.append(space());
|
||||
}
|
||||
|
||||
public TextComponent reloadedConfig(boolean isConsoleSender) {
|
||||
return pluginPrefix(isConsoleSender)
|
||||
.append(text("Config reloaded!")
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent stillReloading(boolean isConsoleSender) {
|
||||
return pluginPrefix(isConsoleSender)
|
||||
.append(text("The plugin is still (re)loading, your request will be processed when it is done!")
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent partiallyReloaded(boolean isConsoleSender) {
|
||||
return pluginPrefix(isConsoleSender)
|
||||
.append(text("The reload process was interrupted. If you notice unexpected behavior, please reload PlayerStats again to fix it!")
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent waitAMoment(boolean longWait, boolean isConsoleSender) {
|
||||
return longWait ? pluginPrefix(isConsoleSender)
|
||||
.append(text("Calculating statistics, this may take a minute...")
|
||||
.color(msgColor))
|
||||
: pluginPrefix(isConsoleSender)
|
||||
.append(text("Calculating statistics, this may take a few moments...")
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent formatExceptions(@NotNull String exception, boolean isConsoleSender) {
|
||||
return pluginPrefix(isConsoleSender)
|
||||
.append(text(exception)
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent missingStatName(boolean isConsoleSender) {
|
||||
return pluginPrefix(isConsoleSender)
|
||||
.append(text("Please provide a valid statistic name!")
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent missingSubStatName(Statistic.Type statType, boolean isConsoleSender) {
|
||||
return pluginPrefix(isConsoleSender)
|
||||
.append(text("Please add a valid ")
|
||||
.append(text(getSubStatTypeName(statType)))
|
||||
.append(text(" to look up this statistic!"))
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent missingPlayerName(boolean isConsoleSender) {
|
||||
return pluginPrefix(isConsoleSender)
|
||||
.append(text("Please specify a valid player-name!")
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent wrongSubStatType(Statistic.Type statType, String subStatEntry, boolean isConsoleSender) {
|
||||
return pluginPrefix(isConsoleSender)
|
||||
.append(text("\"")
|
||||
.append(text(subStatEntry))
|
||||
.append(text("\""))
|
||||
.append(text(" is not a valid "))
|
||||
.append(text(getSubStatTypeName(statType)))
|
||||
.append(text("!"))
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent unknownError(boolean isConsoleSender) {
|
||||
return pluginPrefix(isConsoleSender)
|
||||
.append(text("Something went wrong with your request, please try again or see /statistic for a usage explanation!")
|
||||
.color(msgColor));
|
||||
}
|
||||
|
||||
public TextComponent helpMsg(boolean isConsoleSender) {
|
||||
if (!isConsoleSender) {
|
||||
return config.useHoverText() ? helpMsgHover() : helpMsgPlain(false);
|
||||
}
|
||||
else {
|
||||
return helpMsgPlain(true);
|
||||
}
|
||||
}
|
||||
|
||||
public TextComponent usageExamples(boolean isConsoleSender) {
|
||||
TextComponent spaces = text(" "); //4 spaces
|
||||
TextComponent arrow = text("→ ").color(NamedTextColor.GOLD);
|
||||
TextColor accentColor = TextColor.fromHexString("#FFE339");
|
||||
|
||||
if (isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")) {
|
||||
arrow = text("-> ").color(NamedTextColor.GOLD);
|
||||
accentColor = NamedTextColor.YELLOW;
|
||||
}
|
||||
|
||||
return Component.newline()
|
||||
.append(getPrefixAsTitle(isConsoleSender))
|
||||
.append(newline())
|
||||
.append(text("Examples: ").color(NamedTextColor.GOLD))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("/statistic animals_bred top").color(accentColor))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("/statistic mine_block diorite me").color(accentColor))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("/statistic deaths player Artemis_the_gr8").color(accentColor))
|
||||
.append(newline());
|
||||
}
|
||||
|
||||
public TextComponent formatPlayerStat(int stat, @NotNull StatRequest request) {
|
||||
if (!request.isValid()) return unknownError(request.isConsoleSender());
|
||||
return Component.text()
|
||||
.append(playerNameComponent(Target.PLAYER, request.getPlayerName() + ": "))
|
||||
.append(statNumberComponent(Target.PLAYER, stat))
|
||||
.append(space())
|
||||
.append(statNameComponent(request))
|
||||
.append(space())
|
||||
.build();
|
||||
}
|
||||
|
||||
public TextComponent formatTopStats(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull StatRequest request) {
|
||||
if (!request.isValid()) return unknownError(request.isConsoleSender());
|
||||
|
||||
TextComponent.Builder topList = Component.text();
|
||||
topList.append(getTopStatTitle(topStats.size(), request));
|
||||
|
||||
boolean useDots = config.useDots();
|
||||
Set<String> playerNames = topStats.keySet();
|
||||
MinecraftFont font = new MinecraftFont();
|
||||
|
||||
int count = 0;
|
||||
for (String playerName : playerNames) {
|
||||
count = count+1;
|
||||
|
||||
topList.append(newline())
|
||||
.append(rankingNumberComponent(count + ". "))
|
||||
.append(playerNameComponent(Target.TOP, playerName));
|
||||
|
||||
if (useDots) {
|
||||
topList.append(space());
|
||||
|
||||
int dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/2);
|
||||
if (request.isConsoleSender()) {
|
||||
dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/6) + 7;
|
||||
}
|
||||
else if (config.playerNameIsBold()) {
|
||||
dots = (int) Math.round((130.0 - font.getWidth(count + ". ") - (font.getWidth(playerName) * 1.19))/2);
|
||||
}
|
||||
if (dots >= 1) {
|
||||
topList.append(dotsComponent(".".repeat(dots)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
topList.append(playerNameComponent(Target.TOP, ":"));
|
||||
}
|
||||
topList.append(space()).append(statNumberComponent(Target.TOP, topStats.get(playerName)));
|
||||
}
|
||||
return topList.build();
|
||||
}
|
||||
|
||||
public TextComponent formatServerStat(long stat, @NotNull StatRequest request) {
|
||||
if (!request.isValid()) return unknownError(request.isConsoleSender());
|
||||
return Component.text()
|
||||
.append(titleComponent(Target.SERVER, config.getServerTitle()))
|
||||
.append(space())
|
||||
.append(serverNameComponent())
|
||||
.append(space())
|
||||
.append(statNumberComponent(Target.SERVER, stat))
|
||||
.append(space())
|
||||
.append(statNameComponent(request))
|
||||
.append(space())
|
||||
.build();
|
||||
}
|
||||
|
||||
protected TextComponent getPrefixAsTitle(boolean isConsoleSender) {
|
||||
String underscores = "____________"; //12 underscores for both console and in-game
|
||||
TextColor underscoreColor = TextColor.fromHexString("#6E3485"); //a dark shade of purple
|
||||
|
||||
if (isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")) {
|
||||
underscoreColor = NamedTextColor.DARK_PURPLE;
|
||||
}
|
||||
|
||||
return text(underscores).color(underscoreColor)
|
||||
.append(text(" ")) //4 spaces
|
||||
.append(pluginPrefix(isConsoleSender))
|
||||
.append(text(" ")) //3 spaces (since prefix already has one)
|
||||
.append(text(underscores));
|
||||
}
|
||||
|
||||
protected TextComponent getTopStatTitle(int topLength, @NotNull StatRequest request) {
|
||||
return Component.text()
|
||||
.append(newline())
|
||||
.append(pluginPrefix(request.isConsoleSender()))
|
||||
.append(titleComponent(Target.TOP, config.getTopStatsTitle()))
|
||||
.append(space())
|
||||
.append(titleNumberComponent(topLength))
|
||||
.append(space())
|
||||
.append(statNameComponent(request))
|
||||
.append(space())
|
||||
.build();
|
||||
}
|
||||
|
||||
protected TextComponent playerNameComponent(Target selection, String playerName) {
|
||||
return getComponent(playerName,
|
||||
getColorFromString(config.getPlayerNameFormatting(selection, false)),
|
||||
getStyleFromString(config.getPlayerNameFormatting(selection, true)));
|
||||
}
|
||||
|
||||
protected TranslatableComponent statNameComponent(@NotNull StatRequest request) {
|
||||
if (request.getStatistic() == null) return null;
|
||||
TextColor statNameColor = getColorFromString(config.getStatNameFormatting(request.getSelection(), false));
|
||||
TextDecoration statNameStyle = getStyleFromString(config.getStatNameFormatting(request.getSelection(), true));
|
||||
|
||||
String statName = request.getStatistic().name();
|
||||
String subStatName = request.getSubStatEntry();
|
||||
if (!config.useTranslatableComponents()) {
|
||||
statName = getPrettyName(statName);
|
||||
subStatName = getPrettyName(subStatName);
|
||||
}
|
||||
else {
|
||||
statName = language.getStatKey(request.getStatistic());
|
||||
switch (request.getStatistic().getType()) {
|
||||
case BLOCK -> subStatName = language.getBlockKey(request.getBlock());
|
||||
case ENTITY -> subStatName = language.getEntityKey(request.getEntity());
|
||||
case ITEM -> subStatName = language.getItemKey(request.getItem());
|
||||
case UNTYPED -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
TextComponent subStat = subStatNameComponent(request.getSelection(), subStatName);
|
||||
TranslatableComponent.Builder totalName;
|
||||
|
||||
if (statName.equalsIgnoreCase("stat_type.minecraft.killed") && subStat != null) {
|
||||
totalName = killEntityComponent(subStat);
|
||||
}
|
||||
else if (statName.equalsIgnoreCase("stat_type.minecraft.killed_by") && subStat != null) {
|
||||
totalName = entityKilledByComponent(subStat);
|
||||
}
|
||||
else {
|
||||
totalName = translatable().key(statName);
|
||||
if (subStat != null) totalName.append(space()).append(subStat);
|
||||
}
|
||||
|
||||
if (statNameStyle != null) totalName.decoration(statNameStyle, TextDecoration.State.TRUE);
|
||||
return totalName
|
||||
.color(statNameColor)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected @Nullable TextComponent subStatNameComponent(Target selection, @Nullable String subStatName) {
|
||||
if (subStatName != null) {
|
||||
TextDecoration style = getStyleFromString(config.getSubStatNameFormatting(selection, true));
|
||||
TextComponent.Builder subStat = text()
|
||||
.append(text("("))
|
||||
.append(translatable()
|
||||
.key(subStatName))
|
||||
.append(text(")"))
|
||||
.color(getColorFromString(config.getSubStatNameFormatting(selection, false)));
|
||||
|
||||
subStat.decorations(TextDecoration.NAMES.values(), false);
|
||||
if (style != null) subStat.decoration(style, TextDecoration.State.TRUE);
|
||||
return subStat.build();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Construct a custom translation for kill_entity with the language key for commands.kill.success.single ("Killed %s").
|
||||
@return a TranslatableComponent Builder with the subStat Component as args.*/
|
||||
private TranslatableComponent.Builder killEntityComponent(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key("commands.kill.success.single") //"Killed %s"
|
||||
.args(subStat);
|
||||
}
|
||||
|
||||
/** Construct a custom translation for entity_killed_by with the language keys for stat.minecraft.deaths
|
||||
("Number of Deaths") and book.byAuthor ("by %s").
|
||||
@return a TranslatableComponent Builder with stat.minecraft.deaths as key, with a ChildComponent
|
||||
with book.byAuthor as key and the subStat Component as args.*/
|
||||
private TranslatableComponent.Builder entityKilledByComponent(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key("stat.minecraft.deaths") //"Number of Deaths"
|
||||
.append(space())
|
||||
.append(translatable()
|
||||
.key("book.byAuthor") //"by %s"
|
||||
.args(subStat));
|
||||
}
|
||||
|
||||
protected TextComponent statNumberComponent(Target selection, long number) {
|
||||
return getComponent(NumberFormatter.format(number),
|
||||
getColorFromString(config.getStatNumberFormatting(selection, false)),
|
||||
getStyleFromString(config.getStatNumberFormatting(selection, true)));
|
||||
}
|
||||
|
||||
protected TextComponent titleComponent(Target selection, String content) {
|
||||
return getComponent(content,
|
||||
getColorFromString(config.getTitleFormatting(selection, false)),
|
||||
getStyleFromString(config.getTitleFormatting(selection, true)));
|
||||
}
|
||||
|
||||
protected TextComponent titleNumberComponent(int number) {
|
||||
return getComponent(number + "",
|
||||
getColorFromString(config.getTitleNumberFormatting(false)),
|
||||
getStyleFromString(config.getTitleNumberFormatting(true)));
|
||||
}
|
||||
|
||||
protected TextComponent serverNameComponent() {
|
||||
TextComponent colon = text(":").color(getColorFromString(config.getServerNameFormatting(false)));
|
||||
return getComponent(config.getServerName(),
|
||||
getColorFromString(config.getServerNameFormatting(false)),
|
||||
getStyleFromString(config.getServerNameFormatting(true)))
|
||||
.append(colon);
|
||||
}
|
||||
|
||||
protected TextComponent rankingNumberComponent(String number) {
|
||||
return getComponent(number,
|
||||
getColorFromString(config.getRankNumberFormatting(false)),
|
||||
getStyleFromString(config.getRankNumberFormatting(true)));
|
||||
}
|
||||
|
||||
protected TextComponent dotsComponent(String dots) {
|
||||
return getComponent(dots,
|
||||
getColorFromString(config.getDotsFormatting(false)),
|
||||
getStyleFromString(config.getDotsFormatting(true)));
|
||||
}
|
||||
|
||||
private TextComponent getComponent(String content, TextColor color, @Nullable TextDecoration style) {
|
||||
return style == null ? text(content).color(color) : text(content).color(color).decoration(style, TextDecoration.State.TRUE);
|
||||
}
|
||||
|
||||
/** Replace "_" with " " and capitalize each first letter of the input.
|
||||
@param input String to prettify, case-insensitive*/
|
||||
private String getPrettyName(String input) {
|
||||
if (input == null) return null;
|
||||
StringBuilder capitals = new StringBuilder(input.toLowerCase());
|
||||
capitals.setCharAt(0, Character.toUpperCase(capitals.charAt(0)));
|
||||
while (capitals.indexOf("_") != -1) {
|
||||
MyLogger.replacingUnderscores();
|
||||
|
||||
int index = capitals.indexOf("_");
|
||||
capitals.setCharAt(index + 1, Character.toUpperCase(capitals.charAt(index + 1)));
|
||||
capitals.setCharAt(index, ' ');
|
||||
}
|
||||
return capitals.toString();
|
||||
}
|
||||
|
||||
private TextColor getColorFromString(String configString) {
|
||||
if (configString != null) {
|
||||
try {
|
||||
if (configString.contains("#")) {
|
||||
return TextColor.fromHexString(configString);
|
||||
}
|
||||
else {
|
||||
return getTextColorByName(configString);
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException exception) {
|
||||
Bukkit.getLogger().warning(exception.toString());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TextColor getTextColorByName(String textColor) {
|
||||
Index<String, NamedTextColor> names = NamedTextColor.NAMES;
|
||||
return names.value(textColor);
|
||||
}
|
||||
|
||||
private @Nullable TextDecoration getStyleFromString(@NotNull String configString) {
|
||||
if (configString.equalsIgnoreCase("none")) {
|
||||
return null;
|
||||
}
|
||||
else if (configString.equalsIgnoreCase("magic")) {
|
||||
return TextDecoration.OBFUSCATED;
|
||||
}
|
||||
else {
|
||||
Index<String, TextDecoration> styles = TextDecoration.NAMES;
|
||||
return styles.value(configString);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */
|
||||
private String getSubStatTypeName(Statistic.Type statType) {
|
||||
String subStat = "sub-statistic";
|
||||
if (statType == null) return subStat;
|
||||
switch (statType) {
|
||||
case BLOCK -> subStat = "block";
|
||||
case ENTITY -> subStat = "entity";
|
||||
case ITEM -> subStat = "item";
|
||||
}
|
||||
return subStat;
|
||||
}
|
||||
|
||||
/** Returns the usage-explanation with hovering text */
|
||||
private @NotNull TextComponent helpMsgHover() {
|
||||
TextComponent spaces = text(" "); //4 spaces
|
||||
TextComponent arrow = text("→ ").color(NamedTextColor.GOLD); //alt + 26
|
||||
TextColor arguments = NamedTextColor.YELLOW;
|
||||
|
||||
return Component.newline()
|
||||
.append(getPrefixAsTitle(false))
|
||||
.append(newline())
|
||||
.append(text("Hover over the arguments for more information!").color(NamedTextColor.GRAY).decorate(TextDecoration.ITALIC))
|
||||
.append(newline())
|
||||
.append(text("Usage: ").color(NamedTextColor.GOLD)).append(text("/statistic").color(arguments))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("name").color(arguments)
|
||||
.hoverEvent(HoverEvent.showText(text("The name that describes the statistic").color(hoverBaseColor)
|
||||
.append(newline())
|
||||
.append(text("Example: ").color(accentColor1))
|
||||
.append(text("\"animals_bred\"").color(accentColor2)))))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("sub-statistic").color(arguments)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("Some statistics need an item, block or entity as extra input").color(hoverBaseColor)
|
||||
.append(newline())
|
||||
.append(text("Example: ").color(accentColor1)
|
||||
.append(text("\"mine_block diorite\"").color(accentColor2))))))
|
||||
.append(newline())
|
||||
.append(spaces)
|
||||
.append(text("→").color(NamedTextColor.GOLD)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("Choose one").color(TextColor.fromHexString("#6E3485")))))
|
||||
.append(space())
|
||||
.append(text("me").color(arguments)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("See your own statistic").color(hoverBaseColor))))
|
||||
.append(text(" | ").color(arguments))
|
||||
.append(text("player").color(arguments)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("Choose any player that has played on your server").color(hoverBaseColor))))
|
||||
.append(text(" | ").color(arguments))
|
||||
.append(text("server").color(arguments)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("See the combined total for everyone on your server").color(hoverBaseColor))))
|
||||
.append(text(" | ").color(arguments))
|
||||
.append(text("top").color(arguments)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("See the top ").color(hoverBaseColor)
|
||||
.append(text(config.getTopListMaxSize()).color(hoverBaseColor)))))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("player-name").color(arguments)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("In case you typed ").color(hoverBaseColor)
|
||||
.append(text("\"player\"").color(accentColor2)
|
||||
.append(text(", add the player's name").color(hoverBaseColor))))));
|
||||
}
|
||||
|
||||
/** Returns the usage-explanation without any hovering text.
|
||||
If BukkitVersion is CraftBukkit, this doesn't use unicode symbols or hex colors */
|
||||
private @NotNull TextComponent helpMsgPlain(boolean isConsoleSender) {
|
||||
TextComponent spaces = text(" "); //4 spaces
|
||||
TextComponent arrow = text("→ ").color(NamedTextColor.GOLD); //alt + 26;
|
||||
TextComponent bullet = text("• ").color(NamedTextColor.GOLD); //alt + 7
|
||||
TextColor arguments = NamedTextColor.YELLOW;
|
||||
TextColor accentColor = accentColor2;
|
||||
|
||||
if (isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit")) {
|
||||
arrow = text("-> ").color(NamedTextColor.GOLD);
|
||||
bullet = text("* ").color(NamedTextColor.GOLD);
|
||||
accentColor = NamedTextColor.GOLD;
|
||||
}
|
||||
|
||||
return Component.newline()
|
||||
.append(getPrefixAsTitle(isConsoleSender))
|
||||
.append(newline())
|
||||
.append(text("Type \"/statistic examples\" to see examples!").color(NamedTextColor.GRAY).decorate(TextDecoration.ITALIC))
|
||||
.append(newline())
|
||||
.append(text("Usage: ").color(NamedTextColor.GOLD))
|
||||
.append(text("/statistic").color(arguments))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("name").color(arguments))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("{sub-statistic}").color(arguments))
|
||||
.append(space())
|
||||
.append(text("(a block, item or entity)").color(NamedTextColor.GRAY))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("me | player | server | top").color(arguments))
|
||||
.append(newline())
|
||||
.append(spaces).append(spaces).append(bullet)
|
||||
.append(text("me:").color(accentColor))
|
||||
.append(space()).append(text("your own statistic").color(NamedTextColor.GRAY))
|
||||
.append(newline())
|
||||
.append(spaces).append(spaces).append(bullet)
|
||||
.append(text("player:").color(accentColor))
|
||||
.append(space()).append(text("choose a player").color(NamedTextColor.GRAY))
|
||||
.append(newline())
|
||||
.append(spaces).append(spaces).append(bullet)
|
||||
.append(text("server:").color(accentColor))
|
||||
.append(space()).append(text("everyone on the server combined").color(NamedTextColor.GRAY))
|
||||
.append(newline())
|
||||
.append(spaces).append(spaces).append(bullet)
|
||||
.append(text("top:").color(accentColor))
|
||||
.append(space()).append(text("the top").color(NamedTextColor.GRAY)
|
||||
.append(space()).append(text(config.getTopListMaxSize())))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow)
|
||||
.append(text("{player-name}").color(arguments));
|
||||
}
|
||||
}
|
@ -0,0 +1,293 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.map.MinecraftFont;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
|
||||
/** Composes messages to send to Players or Console. This class is responsible
|
||||
for constructing a final Component with the text content of the desired message.
|
||||
The component parts (with appropriate formatting) are supplied by a ComponentFactory.*/
|
||||
public class MessageWriter {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static ComponentFactory componentFactory;
|
||||
|
||||
public MessageWriter(ConfigHandler c) {
|
||||
config = c;
|
||||
getComponentFactory();
|
||||
}
|
||||
|
||||
public static void updateComponentFactory() {
|
||||
getComponentFactory();
|
||||
}
|
||||
|
||||
private static void getComponentFactory() {
|
||||
if (config.useFestiveFormatting() || config.useRainbowMode()) {
|
||||
componentFactory = new PrideComponentFactory(config);
|
||||
}
|
||||
else {
|
||||
componentFactory = new ComponentFactory(config);
|
||||
}
|
||||
}
|
||||
|
||||
public TextComponent reloadedConfig(boolean isBukkitConsole) {
|
||||
return componentFactory.msg(
|
||||
"Config reloaded!", isBukkitConsole);
|
||||
}
|
||||
|
||||
public TextComponent stillReloading(boolean isBukkitConsole) {
|
||||
return componentFactory.msg(
|
||||
"The plugin is (re)loading, " +
|
||||
"your request will be processed when it is done!", isBukkitConsole);
|
||||
}
|
||||
|
||||
public TextComponent waitAMoment(boolean longWait, boolean isBukkitConsole) {
|
||||
String msg = longWait ? "Calculating statistics, this may take a minute..." :
|
||||
"Calculating statistics, this may take a few moments...";
|
||||
return componentFactory.msg(msg, isBukkitConsole);
|
||||
}
|
||||
|
||||
public TextComponent missingStatName(boolean isBukkitConsole) {
|
||||
return componentFactory.msg(
|
||||
"Please provide a valid statistic name!", isBukkitConsole);
|
||||
}
|
||||
|
||||
public TextComponent missingSubStatName(Statistic.Type statType, boolean isBukkitConsole) {
|
||||
return componentFactory.msg(
|
||||
"Please add a valid " +
|
||||
getSubStatTypeName(statType) +
|
||||
" to look up this statistic!", isBukkitConsole);
|
||||
}
|
||||
|
||||
public TextComponent missingPlayerName(boolean isBukkitConsole) {
|
||||
return componentFactory.msg(
|
||||
"Please specify a valid player-name!", isBukkitConsole);
|
||||
}
|
||||
|
||||
public TextComponent wrongSubStatType(Statistic.Type statType, String subStatEntry, boolean isBukkitConsole) {
|
||||
return componentFactory.msg(
|
||||
"\"" + subStatEntry + "\" is not a valid " + getSubStatTypeName(statType) + "!", isBukkitConsole);
|
||||
}
|
||||
|
||||
public TextComponent unknownError(boolean isBukkitConsole) {
|
||||
return componentFactory.msg(
|
||||
"Something went wrong with your request, " +
|
||||
"please try again or see /statistic for a usage explanation!", isBukkitConsole);
|
||||
}
|
||||
|
||||
|
||||
public TextComponent formatPlayerStat(int stat, @NotNull StatRequest request) {
|
||||
if (!request.isValid()) return unknownError(request.isBukkitConsoleSender());
|
||||
return Component.text()
|
||||
.append(componentFactory.playerName( request.getPlayerName() + ": ", Target.PLAYER))
|
||||
.append(componentFactory.statNumber(stat, Target.PLAYER))
|
||||
.append(space())
|
||||
.append(componentFactory.statName(request))
|
||||
.append(space())
|
||||
.build();
|
||||
}
|
||||
|
||||
public TextComponent formatTopStats(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull StatRequest request) {
|
||||
if (!request.isValid()) return unknownError(request.isBukkitConsoleSender());
|
||||
|
||||
TextComponent.Builder topList = Component.text()
|
||||
.append(newline())
|
||||
.append(componentFactory.pluginPrefix(request.isBukkitConsoleSender()))
|
||||
.append(componentFactory.title(config.getTopStatsTitle(), Target.TOP))
|
||||
.append(space())
|
||||
.append(componentFactory.titleNumber(topStats.size()))
|
||||
.append(space())
|
||||
.append(componentFactory.statName(request));
|
||||
|
||||
boolean useDots = config.useDots();
|
||||
boolean boldNames = config.playerNameIsBold();
|
||||
|
||||
Set<String> playerNames = topStats.keySet();
|
||||
MinecraftFont font = new MinecraftFont();
|
||||
|
||||
int count = 0;
|
||||
for (String playerName : playerNames) {
|
||||
count = count+1;
|
||||
|
||||
topList.append(newline())
|
||||
.append(componentFactory.rankingNumber(count + ". "))
|
||||
.append(componentFactory.playerName(playerName, Target.TOP));
|
||||
|
||||
if (useDots) {
|
||||
topList.append(space());
|
||||
|
||||
int dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/2);
|
||||
if (request.isConsoleSender()) {
|
||||
dots = (int) Math.round((130.0 - font.getWidth(count + ". " + playerName))/6) + 7;
|
||||
}
|
||||
else if (boldNames) {
|
||||
dots = (int) Math.round((130.0 - font.getWidth(count + ". ") - (font.getWidth(playerName) * 1.19))/2);
|
||||
}
|
||||
if (dots >= 1) {
|
||||
topList.append(componentFactory.dots(".".repeat(dots)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
topList.append(componentFactory.playerName(":", Target.TOP));
|
||||
}
|
||||
topList.append(space()).append(componentFactory.statNumber(topStats.get(playerName), Target.TOP));
|
||||
}
|
||||
return topList.build();
|
||||
}
|
||||
|
||||
public TextComponent formatServerStat(long stat, @NotNull StatRequest request) {
|
||||
if (!request.isValid()) return unknownError(request.isBukkitConsoleSender());
|
||||
return Component.text()
|
||||
.append(componentFactory.title(config.getServerTitle(), Target.SERVER))
|
||||
.append(space())
|
||||
.append(componentFactory.serverName(config.getServerName()))
|
||||
.append(space())
|
||||
.append(componentFactory.statNumber(stat, Target.SERVER))
|
||||
.append(space())
|
||||
.append(componentFactory.statName(request))
|
||||
.append(space())
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */
|
||||
private String getSubStatTypeName(Statistic.Type statType) {
|
||||
String subStat = "sub-statistic";
|
||||
if (statType == null) return subStat;
|
||||
switch (statType) {
|
||||
case BLOCK -> subStat = "block";
|
||||
case ENTITY -> subStat = "entity";
|
||||
case ITEM -> subStat = "item";
|
||||
}
|
||||
return subStat;
|
||||
}
|
||||
|
||||
public TextComponent usageExamples(boolean isBukkitConsole) {
|
||||
TextColor mainColor = isBukkitConsole ? PluginColor.GOLD.getConsoleColor() : PluginColor.GOLD.getColor();
|
||||
TextColor accentColor1 = isBukkitConsole ? PluginColor.MEDIUM_GOLD.getConsoleColor() : PluginColor.MEDIUM_GOLD.getColor();
|
||||
TextColor accentColor3 = isBukkitConsole ? PluginColor.LIGHT_YELLOW.getConsoleColor() : PluginColor.LIGHT_YELLOW.getColor();
|
||||
String arrow = isBukkitConsole ? " -> " : " → "; //4 spaces, alt + 26, 1 space
|
||||
|
||||
return Component.newline()
|
||||
.append(componentFactory.prefixTitle(isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(text("Examples: ").color(mainColor))
|
||||
.append(newline())
|
||||
.append(text(arrow).color(mainColor)
|
||||
.append(text("/statistic ")
|
||||
.append(text("animals_bred ").color(accentColor1)
|
||||
.append(text("top").color(accentColor3)))))
|
||||
.append(newline())
|
||||
.append(text(arrow).color(mainColor)
|
||||
.append(text("/statistic ")
|
||||
.append(text("mine_block diorite ").color(accentColor1)
|
||||
.append(text("me").color(accentColor3)))))
|
||||
.append(newline())
|
||||
.append(text(arrow).color(mainColor)
|
||||
.append(text("/statistic ")
|
||||
.append(text("deaths ").color(accentColor1)
|
||||
.append(text("player ").color(accentColor3)
|
||||
.append(text("Artemis_the_gr8"))))));
|
||||
}
|
||||
|
||||
public TextComponent helpMsg(boolean isConsoleSender) {
|
||||
if (isConsoleSender || !config.useHoverText()) {
|
||||
return helpMsgPlain(isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit"));
|
||||
}
|
||||
else {
|
||||
return helpMsgHover();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the usage-explanation with hovering text */
|
||||
private TextComponent helpMsgHover() {
|
||||
String arrow = " →"; //4 spaces, alt + 26
|
||||
return Component.newline()
|
||||
.append(componentFactory.prefixTitle(false))
|
||||
.append(newline())
|
||||
.append(componentFactory.subTitle("Hover over the arguments for more information!"))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart("Usage:", null, "/statistic", null))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(arrow, null, null, null)
|
||||
.append(componentFactory.complexHoverPart("name", PluginColor.LIGHT_GOLD,
|
||||
"The name that describes the statistic",
|
||||
"Example:",
|
||||
"\"animals_bred\"")))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(arrow, null, null, null)
|
||||
.append(componentFactory.complexHoverPart("sub-statistic", PluginColor.LIGHT_GOLD,
|
||||
"Some statistics need an item, block or entity as extra input",
|
||||
"Example:",
|
||||
"\"mine_block diorite\"")))
|
||||
.append(newline())
|
||||
.append(text(" ").color(PluginColor.LIGHT_GOLD.getColor())
|
||||
.append(componentFactory.simpleHoverPart(
|
||||
"→", PluginColor.GOLD,
|
||||
"Choose one", PluginColor.DARK_PURPLE))
|
||||
.append(space())
|
||||
.append(componentFactory.simpleHoverPart(
|
||||
"me",
|
||||
"See your own statistic", PluginColor.LIGHT_BLUE))
|
||||
.append(text(" | "))
|
||||
.append(componentFactory.simpleHoverPart(
|
||||
"player",
|
||||
"Choose any player that has played on your server", PluginColor.LIGHT_BLUE))
|
||||
.append(text(" | "))
|
||||
.append(componentFactory.simpleHoverPart(
|
||||
"server",
|
||||
"See the combined total for everyone on your server", PluginColor.LIGHT_BLUE))
|
||||
.append(text(" | "))
|
||||
.append(componentFactory.simpleHoverPart(
|
||||
"top",
|
||||
"See the top " + config.getTopListMaxSize(), PluginColor.LIGHT_BLUE)))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(arrow, null, null, null)
|
||||
.append(text("player-name").color(PluginColor.LIGHT_GOLD.getColor())
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("In case you typed ").color(PluginColor.LIGHT_BLUE.getColor())
|
||||
.append(text("\"player\"").color(PluginColor.MEDIUM_GOLD.getColor()))
|
||||
.append(text(", add the player's name"))))));
|
||||
}
|
||||
|
||||
/** Returns the usage-explanation without any hovering text.
|
||||
If BukkitVersion is CraftBukkit, this doesn't use unicode symbols or hex colors */
|
||||
private TextComponent helpMsgPlain(boolean isBukkitConsole) {
|
||||
String arrow = isBukkitConsole ? " ->" : " →"; //4 spaces, alt + 26
|
||||
String bullet = isBukkitConsole ? " *" : " •"; //8 spaces, alt + 7
|
||||
return Component.newline()
|
||||
.append(componentFactory.prefixTitle(isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(componentFactory.subTitle("Type \"statistic examples\" to see examples!"))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart("Usage:", null, "/statistic", null, isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(arrow, null, "name", null, isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(arrow, null, "{sub-statistic}", "(a block, item or entity)", isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(arrow, null, "me | player | server | top", null, isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(bullet, "me:", null, "your own statistic", isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(bullet, "player:", null, "choose a player", isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(bullet, "server:", null, "everyone on the server combined", isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(bullet, "top:", null, "the top " + config.getTopListMaxSize(), isBukkitConsole))
|
||||
.append(newline())
|
||||
.append(componentFactory.msgPart(arrow, null, "{player-name}", null, isBukkitConsole));
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
@ -12,20 +11,20 @@ import java.time.Month;
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
|
||||
|
||||
public class PrideMessageFactory extends MessageFactory {
|
||||
public class PrideComponentFactory extends ComponentFactory {
|
||||
|
||||
private static ConfigHandler config;
|
||||
|
||||
public PrideMessageFactory(ConfigHandler c, LanguageKeyHandler l) {
|
||||
super(c, l);
|
||||
public PrideComponentFactory(ConfigHandler c) {
|
||||
super(c);
|
||||
config = c;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected TextComponent getPrefixAsTitle(boolean isConsoleSender) {
|
||||
if (cancelRainbow(isConsoleSender)) {
|
||||
return super.getPrefixAsTitle(isConsoleSender);
|
||||
public TextComponent prefixTitle(boolean isBukkitConsole) {
|
||||
if (cancelRainbow(isBukkitConsole)) {
|
||||
return super.prefixTitle(isBukkitConsole);
|
||||
}
|
||||
else {
|
||||
String title = "<rainbow:16>____________ [PlayerStats] ____________</rainbow>"; //12 underscores
|
||||
@ -36,7 +35,7 @@ public class PrideMessageFactory extends MessageFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TextComponent pluginPrefix(boolean isConsoleSender) {
|
||||
public TextComponent pluginPrefix(boolean isConsoleSender) {
|
||||
if (cancelRainbow(isConsoleSender)) {
|
||||
return super.pluginPrefix(isConsoleSender);
|
||||
}
|
||||
@ -61,8 +60,8 @@ public class PrideMessageFactory extends MessageFactory {
|
||||
/** Don't use rainbow formatting if the rainbow Prefix is disabled,
|
||||
if festive formatting is disabled or it is not pride month,
|
||||
or the commandsender is a Bukkit or Spigot console.*/
|
||||
private boolean cancelRainbow(boolean isConsoleSender) {
|
||||
return !(config.useRainbowPrefix() || (config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE))) ||
|
||||
(isConsoleSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit"));
|
||||
private boolean cancelRainbow(boolean isBukkitConsole) {
|
||||
return !(config.useRainbowMode() || (config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE))) ||
|
||||
(isBukkitConsole);
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.reload;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
@ -12,11 +12,9 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -29,20 +27,18 @@ public class ReloadThread extends Thread {
|
||||
|
||||
private final BukkitAudiences adventure;
|
||||
private static ConfigHandler config;
|
||||
private static MessageFactory messageFactory;
|
||||
private final Main plugin;
|
||||
private static MessageWriter messageWriter;
|
||||
|
||||
private final StatThread statThread;
|
||||
private final CommandSender sender;
|
||||
|
||||
public ReloadThread(BukkitAudiences a, ConfigHandler c, MessageFactory m, Main p, int threshold, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
|
||||
public ReloadThread(BukkitAudiences a, ConfigHandler c, MessageWriter m, int threshold, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
|
||||
this.threshold = threshold;
|
||||
reloadThreadID = ID;
|
||||
|
||||
adventure = a;
|
||||
config = c;
|
||||
messageFactory = m;
|
||||
plugin = p;
|
||||
messageWriter = m;
|
||||
|
||||
statThread = s;
|
||||
sender = se;
|
||||
@ -56,79 +52,68 @@ public class ReloadThread extends Thread {
|
||||
long time = System.currentTimeMillis();
|
||||
MyLogger.threadStart(this.getName());
|
||||
|
||||
//if reload is triggered by /statreload (aka this thread does not have ID number 1)...
|
||||
if (reloadThreadID != 1) {
|
||||
if (statThread != null && statThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.waitingForOtherThread(this.getName(), statThread.getName());
|
||||
statThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
MyLogger.logException(e, "ReloadThread", "run(), trying to join" + statThread.getName());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
plugin.getLogger().info("Reloading!");
|
||||
if (config.reloadConfig()) {
|
||||
|
||||
try {
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(getPlayerMap());
|
||||
}
|
||||
catch (ConcurrentModificationException e) {
|
||||
MyLogger.logException(e, "ReloadThread", "run(), trying to update OfflinePlayerList during a reload");
|
||||
if (sender != null) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.partiallyReloaded(sender instanceof ConsoleCommandSender));
|
||||
}
|
||||
}
|
||||
|
||||
MyLogger.logTimeTakenDefault("ReloadThread", ("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time);
|
||||
if (sender != null) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.reloadedConfig(sender instanceof ConsoleCommandSender));
|
||||
}
|
||||
if (statThread != null && statThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.waitingForOtherThread(this.getName(), statThread.getName());
|
||||
statThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
MyLogger.logException(e, "ReloadThread", "run(), trying to join" + statThread.getName());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
//during first start-up...
|
||||
else {
|
||||
try {
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(getPlayerMap());
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
MyLogger.logTimeTakenDefault("ReloadThread",
|
||||
("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time);
|
||||
}
|
||||
catch (ConcurrentModificationException e) {
|
||||
MyLogger.logException(e, "ReloadThread", "run(), trying to update OfflinePlayerList during first start-up");
|
||||
|
||||
if (reloadThreadID != 1 && config.reloadConfig()) { //during a reload
|
||||
MyLogger.logMsg("Reloading!", false);
|
||||
MyLogger.setDebugLevel(config.getDebugLevel());
|
||||
MessageWriter.updateComponentFactory();
|
||||
loadOfflinePlayers();
|
||||
|
||||
boolean isBukkitConsole = sender instanceof ConsoleCommandSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit");
|
||||
if (sender != null) {
|
||||
adventure.sender(sender).sendMessage(
|
||||
messageWriter.reloadedConfig(isBukkitConsole));
|
||||
}
|
||||
}
|
||||
else { //during first start-up
|
||||
MyLogger.setDebugLevel(config.getDebugLevel());
|
||||
loadOfflinePlayers();
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull ConcurrentHashMap<String, UUID> getPlayerMap() throws ConcurrentModificationException {
|
||||
private void loadOfflinePlayers() {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
OfflinePlayer[] offlinePlayers;
|
||||
if (config.whitelistOnly()) {
|
||||
offlinePlayers = Bukkit.getWhitelistedPlayers().toArray(OfflinePlayer[]::new);
|
||||
MyLogger.logTimeTaken("ReloadThread", "retrieved whitelist", time);
|
||||
MyLogger.logTimeTaken("ReloadThread",
|
||||
"retrieved whitelist", time, DebugLevel.MEDIUM);
|
||||
}
|
||||
else if (config.excludeBanned()) {
|
||||
Set<OfflinePlayer> bannedPlayers = Bukkit.getBannedPlayers();
|
||||
offlinePlayers = Arrays.stream(Bukkit.getOfflinePlayers())
|
||||
.parallel()
|
||||
.filter(offlinePlayer -> !bannedPlayers.contains(offlinePlayer)).toArray(OfflinePlayer[]::new);
|
||||
MyLogger.logTimeTaken("ReloadThread", "retrieved banlist", time);
|
||||
MyLogger.logTimeTaken("ReloadThread",
|
||||
"retrieved banlist", time, DebugLevel.MEDIUM);
|
||||
}
|
||||
else {
|
||||
offlinePlayers = Bukkit.getOfflinePlayers();
|
||||
MyLogger.logTimeTaken("ReloadThread", "retrieved list of Offline Players", time);
|
||||
MyLogger.logTimeTaken("ReloadThread",
|
||||
"retrieved list of Offline Players", time, DebugLevel.MEDIUM);
|
||||
}
|
||||
|
||||
int size = offlinePlayers != null ? offlinePlayers.length : 16;
|
||||
ConcurrentHashMap<String, UUID> playerMap = new ConcurrentHashMap<>(size);
|
||||
|
||||
ReloadAction task = new ReloadAction(threshold, offlinePlayers, config.lastPlayedLimit(), playerMap);
|
||||
ReloadAction task = new ReloadAction(threshold, offlinePlayers, config.getLastPlayedLimit(), playerMap);
|
||||
MyLogger.actionCreated((offlinePlayers != null) ? offlinePlayers.length : 0);
|
||||
|
||||
ForkJoinPool.commonPool().invoke(task);
|
||||
MyLogger.actionFinished(1);
|
||||
MyLogger.logTimeTaken("ReloadThread",
|
||||
("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time);
|
||||
|
||||
return playerMap;
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(playerMap);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
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;
|
||||
@ -57,6 +58,10 @@ public class StatRequest {
|
||||
return sender instanceof ConsoleCommandSender;
|
||||
}
|
||||
|
||||
public boolean isBukkitConsoleSender() {
|
||||
return sender instanceof ConsoleCommandSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit");
|
||||
}
|
||||
|
||||
public void setStatistic(Statistic statistic) {
|
||||
this.statistic = statistic;
|
||||
}
|
||||
|
@ -2,18 +2,15 @@ package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
|
||||
import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageFactory;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -31,11 +28,11 @@ public class StatThread extends Thread {
|
||||
|
||||
private final BukkitAudiences adventure;
|
||||
private static ConfigHandler config;
|
||||
private static MessageFactory messageFactory;
|
||||
private static MessageWriter messageWriter;
|
||||
private final Main plugin;
|
||||
|
||||
//constructor (called on thread creation)
|
||||
public StatThread(BukkitAudiences a, ConfigHandler c, MessageFactory m, Main p, int ID, int threshold, StatRequest s, @Nullable ReloadThread r) {
|
||||
public StatThread(BukkitAudiences a, ConfigHandler c, MessageWriter m, Main p, int ID, int threshold, StatRequest s, @Nullable ReloadThread r) {
|
||||
this.threshold = threshold;
|
||||
|
||||
request = s;
|
||||
@ -43,7 +40,7 @@ public class StatThread extends Thread {
|
||||
|
||||
adventure = a;
|
||||
config = c;
|
||||
messageFactory = m;
|
||||
messageWriter = m;
|
||||
plugin = p;
|
||||
|
||||
this.setName("StatThread-" + ID);
|
||||
@ -55,67 +52,55 @@ public class StatThread extends Thread {
|
||||
public void run() throws IllegalStateException, NullPointerException {
|
||||
MyLogger.threadStart(this.getName());
|
||||
|
||||
if (messageFactory == null || plugin == null) {
|
||||
if (messageWriter == null || plugin == null) {
|
||||
throw new IllegalStateException("Not all classes off the plugin are running!");
|
||||
}
|
||||
if (request == null) {
|
||||
throw new NullPointerException("No statistic request was found!");
|
||||
}
|
||||
|
||||
if (reloadThread != null && reloadThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.waitingForOtherThread(this.getName(), reloadThread.getName());
|
||||
adventure.sender(request.getCommandSender())
|
||||
.sendMessage(messageFactory
|
||||
.stillReloading(request.getCommandSender() instanceof ConsoleCommandSender));
|
||||
.sendMessage(messageWriter
|
||||
.stillReloading(request.isBukkitConsoleSender()));
|
||||
reloadThread.join();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
plugin.getLogger().warning(e.toString());
|
||||
MyLogger.logException(e, "StatThread", "Trying to join" + reloadThread.getName());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
CommandSender sender = request.getCommandSender();
|
||||
boolean isConsoleSencer = sender instanceof ConsoleCommandSender;
|
||||
Target selection = request.getSelection();
|
||||
|
||||
if (selection == Target.TOP || selection == Target.SERVER) {
|
||||
if (ThreadManager.getLastRecordedCalcTime() > 20000) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.waitAMoment(true, isConsoleSencer));
|
||||
if (selection == Target.PLAYER) {
|
||||
adventure.sender(request.getCommandSender()).sendMessage(
|
||||
messageWriter.formatPlayerStat(getIndividualStat(), request));
|
||||
}
|
||||
else {
|
||||
if (ThreadManager.getLastRecordedCalcTime() > 2000) {
|
||||
adventure.sender(request.getCommandSender()).sendMessage(
|
||||
messageWriter.waitAMoment(ThreadManager.getLastRecordedCalcTime() > 20000, request.isBukkitConsoleSender()));
|
||||
}
|
||||
else if (ThreadManager.getLastRecordedCalcTime() > 2000) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.waitAMoment(false, isConsoleSencer));
|
||||
}
|
||||
|
||||
try {
|
||||
if (selection == Target.TOP) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.formatTopStats(getTopStats(), request));
|
||||
adventure.sender(request.getCommandSender()).sendMessage(
|
||||
messageWriter.formatTopStats(getTopStats(), request));
|
||||
} else {
|
||||
adventure.sender(request.getCommandSender()).sendMessage(
|
||||
messageWriter.formatServerStat(getServerTotal(), request));
|
||||
}
|
||||
else {
|
||||
adventure.sender(sender).sendMessage(messageFactory.formatServerStat(getServerTotal(), request));
|
||||
}
|
||||
|
||||
} catch (ConcurrentModificationException e) {
|
||||
if (!isConsoleSencer) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.unknownError(false));
|
||||
if (!request.isConsoleSender()) {
|
||||
adventure.sender(request.getCommandSender()).sendMessage(
|
||||
messageWriter.unknownError(false));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.formatExceptions(e.toString(), isConsoleSencer));
|
||||
MyLogger.logException(e, "StatThread", "run(), trying to calculate or format a top or server statistic");
|
||||
}
|
||||
}
|
||||
|
||||
else if (selection == Target.PLAYER) {
|
||||
try {
|
||||
adventure.sender(sender).sendMessage(
|
||||
messageFactory.formatPlayerStat(getIndividualStat(), request));
|
||||
|
||||
} catch (UnsupportedOperationException | NullPointerException e) {
|
||||
adventure.sender(sender).sendMessage(messageFactory.formatExceptions(e.toString(), isConsoleSencer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, Integer> getTopStats() throws ConcurrentModificationException, NullPointerException {
|
||||
private LinkedHashMap<String, Integer> 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));
|
||||
@ -127,7 +112,7 @@ public class StatThread extends Thread {
|
||||
}
|
||||
|
||||
//invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players in the list)
|
||||
private @NotNull ConcurrentHashMap<String, Integer> getAllStats() throws ConcurrentModificationException, NullPointerException {
|
||||
private @NotNull ConcurrentHashMap<String, Integer> getAllStats() throws ConcurrentModificationException {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
int size = OfflinePlayerHandler.getOfflinePlayerCount() != 0 ? (int) (OfflinePlayerHandler.getOfflinePlayerCount() * 1.05) : 16;
|
||||
@ -141,20 +126,21 @@ public class StatThread extends Thread {
|
||||
try {
|
||||
commonPool.invoke(task);
|
||||
} catch (ConcurrentModificationException e) {
|
||||
plugin.getLogger().warning("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!");
|
||||
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.logTimeTakenDefault("StatThread", "calculated all stats", time);
|
||||
MyLogger.logTimeTaken("StatThread", "calculated all stats", time);
|
||||
|
||||
return playerStats;
|
||||
}
|
||||
|
||||
//gets the actual statistic data for an individual player
|
||||
private int getIndividualStat() throws UnsupportedOperationException, NullPointerException {
|
||||
/** 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) {
|
||||
switch (request.getStatistic().getType()) {
|
||||
@ -172,6 +158,6 @@ public class StatThread extends Thread {
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new NullPointerException("The player you are trying to request either does not exist, or is not on the list for statistic lookups!");
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -50,29 +50,26 @@ public class TopStatAction extends RecursiveAction {
|
||||
}
|
||||
}
|
||||
|
||||
private void getStatsDirectly() throws UnsupportedOperationException {
|
||||
try {
|
||||
Iterator<String> iterator = playerNames.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
do {
|
||||
String playerName = iterator.next();
|
||||
MyLogger.actionRunning(Thread.currentThread().getName(), playerName, 2);
|
||||
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());
|
||||
}
|
||||
if (statistic > 0) {
|
||||
playerStats.put(playerName, statistic);
|
||||
}
|
||||
private void getStatsDirectly() {
|
||||
Iterator<String> iterator = playerNames.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
do {
|
||||
String playerName = iterator.next();
|
||||
MyLogger.actionRunning(Thread.currentThread().getName(), playerName, 2);
|
||||
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());
|
||||
}
|
||||
} while (iterator.hasNext());
|
||||
}
|
||||
} catch (NoSuchElementException ignored) {
|
||||
if (statistic > 0) {
|
||||
playerStats.put(playerName, statistic);
|
||||
}
|
||||
}
|
||||
} while (iterator.hasNext());
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ public class EnumHandler {
|
||||
private final static List<String> entitySubStatNames;
|
||||
private final static List<String> subStatNames;
|
||||
|
||||
static{
|
||||
static {
|
||||
blockNames = Arrays.stream(Material.values())
|
||||
.filter(Material::isBlock)
|
||||
.map(Material::toString)
|
||||
|
@ -22,7 +22,7 @@ public class MyLogger {
|
||||
private static final AtomicInteger playersIndex;
|
||||
private static ConcurrentHashMap<String, Integer> threadNames;
|
||||
|
||||
static{
|
||||
static {
|
||||
Plugin plugin = Bukkit.getPluginManager().getPlugin("PlayerStats");
|
||||
logger = (plugin != null) ? plugin.getLogger() : Bukkit.getLogger();
|
||||
debugLevel = DebugLevel.LOW;
|
||||
@ -35,17 +35,6 @@ public class MyLogger {
|
||||
private MyLogger() {
|
||||
}
|
||||
|
||||
|
||||
/** Accesses the playersIndex to up it by 1 and return its previous value. */
|
||||
private static int nextPlayersIndex() {
|
||||
return playersIndex.getAndIncrement();
|
||||
}
|
||||
|
||||
/** Returns true if the playersIndex is 10, or any subsequent increment of 10. */
|
||||
private static boolean incrementOfTen() {
|
||||
return (playersIndex.get() == 10 || (playersIndex.get() > 10 && playersIndex.get() % 10 == 0));
|
||||
}
|
||||
|
||||
/** Sets the desired debugging level.
|
||||
<p>1 = low (only show unexpected errors)</p>
|
||||
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
|
||||
@ -63,6 +52,30 @@ public class MyLogger {
|
||||
}
|
||||
}
|
||||
|
||||
public static void logMsg(String content, boolean logAsWarning) {
|
||||
logMsg(content, DebugLevel.LOW, logAsWarning);
|
||||
}
|
||||
|
||||
public static void logMsg(String content, DebugLevel logThreshold) {
|
||||
logMsg(content, logThreshold, false);
|
||||
}
|
||||
|
||||
public static void logMsg(String content, DebugLevel logThreshold, boolean logAsWarning) {
|
||||
switch (logThreshold) {
|
||||
case LOW -> log(content, logAsWarning);
|
||||
case MEDIUM -> {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
log(content, logAsWarning);
|
||||
}
|
||||
}
|
||||
case HIGH -> {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
log(content, logAsWarning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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.
|
||||
@ -79,7 +92,7 @@ public class MyLogger {
|
||||
}
|
||||
}
|
||||
|
||||
/** If DebugLevel is MEDIUM or HIGH, logs when the while loop in MessageFactory, getLanguageKey is being run. */
|
||||
/** If DebugLevel is MEDIUM or HIGH, logs when the while loop in MessageWriter, getLanguageKey is being run. */
|
||||
public static void replacingUnderscores() {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
logger.info("Replacing underscores and capitalizing names...");
|
||||
@ -171,21 +184,54 @@ public class MyLogger {
|
||||
}
|
||||
}
|
||||
|
||||
/** Output to console how long a certain task has taken if DebugLevel is MEDIUM or HIGH.
|
||||
@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) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
logger.info(className + " " + methodName + ": " + (System.currentTimeMillis() - startTime) + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 logTimeTakenDefault(String className, String methodName, long startTime) {
|
||||
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
|
||||
@param startTime Timestamp marking the beginning of the task
|
||||
@param logThreshold the DebugLevel threshold */
|
||||
public static void logTimeTaken(String className, String methodName, long startTime, DebugLevel logThreshold) {
|
||||
switch (logThreshold) {
|
||||
case LOW -> printTime(className, methodName, startTime);
|
||||
case MEDIUM -> {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
printTime(className, methodName, startTime);
|
||||
}
|
||||
}
|
||||
case HIGH -> {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
printTime(className, methodName, startTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void log(String content, boolean logAsWarning) {
|
||||
if (logAsWarning) {
|
||||
logger.warning(content);
|
||||
} else {
|
||||
logger.info(content);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printTime(String className, String methodName, long startTime) {
|
||||
logger.info(className + " " + methodName + ": " + (System.currentTimeMillis() - startTime) + "ms");
|
||||
}
|
||||
|
||||
/** Accesses the playersIndex to up it by 1 and return its previous value. */
|
||||
private static int nextPlayersIndex() {
|
||||
return playersIndex.getAndIncrement();
|
||||
}
|
||||
|
||||
/** Returns true if the playersIndex is 10, or any subsequent increment of 10. */
|
||||
private static boolean incrementOfTen() {
|
||||
return (playersIndex.get() == 10 || (playersIndex.get() > 10 && playersIndex.get() % 10 == 0));
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ public class OfflinePlayerHandler {
|
||||
private static ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||
private static ArrayList<String> playerNames;
|
||||
|
||||
static{
|
||||
static {
|
||||
offlinePlayerUUIDs = new ConcurrentHashMap<>();
|
||||
playerNames = new ArrayList<>();
|
||||
}
|
||||
|
@ -74,13 +74,13 @@ your-server-name: 'this server'
|
||||
# # ------------------------------ # #
|
||||
|
||||
top-list:
|
||||
title: '#FFD52B'
|
||||
title: '#FFEA40'
|
||||
title-style: none
|
||||
|
||||
title-number: gold
|
||||
title-number-style: none
|
||||
|
||||
stat-names: '#FFD52B'
|
||||
stat-names: '#FFEA40'
|
||||
stat-names-style: none
|
||||
|
||||
sub-stat-names: yellow
|
||||
|
Loading…
Reference in New Issue
Block a user