mirror of
https://github.com/itHotL/PlayerStats.git
synced 2024-11-25 12:25:36 +01:00
commit
11a78decae
@ -4,7 +4,7 @@
|
||||
<groupId>io.github.ithotl</groupId>
|
||||
<artifactId>PlayerStats</artifactId>
|
||||
<name>PlayerStats</name>
|
||||
<version>1.8</version>
|
||||
<version>2.0</version>
|
||||
<description>Statistics Plugin</description>
|
||||
<url>https://www.spigotmc.org/resources/playerstats.102347/</url>
|
||||
<developers>
|
||||
@ -49,7 +49,7 @@
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer>
|
||||
<mainClass>com.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
<mainClass>com.artemis.the.gr8.playerstats.core.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<artifactSet>
|
||||
@ -60,15 +60,15 @@
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>com.artemis.the.gr8.lib.kyori</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.playerstats.lib.kyori</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.tchristofferson</pattern>
|
||||
<shadedPattern>com.artemis.the.gr8.util.tchristofferson</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.playerstats.lib.tchristofferson</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>com.artemis.the.gr8.util.bstats</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.playerstats.lib.bstats</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<filters>
|
||||
@ -107,7 +107,7 @@
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<phase>deploy</phase>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
@ -120,7 +120,7 @@
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>deploy</phase>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
@ -133,7 +133,7 @@
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<phase>deploy</phase>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
|
16
pom.xml
16
pom.xml
@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>io.github.ithotl</groupId>
|
||||
<artifactId>PlayerStats</artifactId>
|
||||
<version>1.8</version>
|
||||
<version>2.0</version>
|
||||
|
||||
<name>PlayerStats</name>
|
||||
<description>Statistics Plugin</description>
|
||||
@ -141,7 +141,7 @@
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
<mainClass>com.artemis.the.gr8.playerstats.core.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<artifactSet>
|
||||
@ -152,15 +152,15 @@
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>com.artemis.the.gr8.lib.kyori</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.playerstats.lib.kyori</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.tchristofferson</pattern>
|
||||
<shadedPattern>com.artemis.the.gr8.util.tchristofferson</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.playerstats.lib.tchristofferson</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>com.artemis.the.gr8.util.bstats</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.playerstats.lib.bstats</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<filters>
|
||||
@ -200,7 +200,7 @@
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<phase>deploy</phase>
|
||||
<phase>verify</phase> <!-- change to verify when deploying -->
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
@ -214,7 +214,7 @@
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>deploy</phase>
|
||||
<phase>verify</phase> <!-- change to verify when deploying -->
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
@ -228,7 +228,7 @@
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<phase>deploy</phase>
|
||||
<phase>verify</phase> <!-- change to verify when deploying -->
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
|
@ -1,189 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.PlayerStats;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.api.PlayerStatsAPI;
|
||||
import com.artemis.the.gr8.playerstats.commands.ReloadCommand;
|
||||
import com.artemis.the.gr8.playerstats.commands.ShareCommand;
|
||||
import com.artemis.the.gr8.playerstats.commands.StatCommand;
|
||||
import com.artemis.the.gr8.playerstats.commands.TabCompleter;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.listeners.JoinListener;
|
||||
import com.artemis.the.gr8.playerstats.msg.InternalFormatter;
|
||||
import com.artemis.the.gr8.playerstats.msg.MessageBuilder;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* PlayerStats' Main class
|
||||
*/
|
||||
public final class Main extends JavaPlugin {
|
||||
|
||||
private static Main instance;
|
||||
private static BukkitAudiences adventure;
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static LanguageKeyHandler languageKeyHandler;
|
||||
private static OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static EnumHandler enumHandler;
|
||||
|
||||
private static OutputManager outputManager;
|
||||
private static ShareManager shareManager;
|
||||
private static StatCalculator statCalculator;
|
||||
private static ThreadManager threadManager;
|
||||
|
||||
private static PlayerStats playerStatsAPI;
|
||||
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
//initialize all the Managers, singletons, ConfigHandler and the API
|
||||
initializeMainClasses();
|
||||
setupMetrics();
|
||||
|
||||
//register all commands and the tabCompleter
|
||||
PluginCommand statcmd = this.getCommand("statistic");
|
||||
if (statcmd != null) {
|
||||
statcmd.setExecutor(new StatCommand(outputManager, threadManager));
|
||||
statcmd.setTabCompleter(new TabCompleter(enumHandler, offlinePlayerHandler));
|
||||
}
|
||||
PluginCommand reloadcmd = this.getCommand("statisticreload");
|
||||
if (reloadcmd != null) reloadcmd.setExecutor(new ReloadCommand(threadManager));
|
||||
PluginCommand sharecmd = this.getCommand("statisticshare");
|
||||
if (sharecmd != null) sharecmd.setExecutor(new ShareCommand(shareManager, outputManager));
|
||||
|
||||
//register the listener
|
||||
Bukkit.getPluginManager().registerEvents(new JoinListener(threadManager), this);
|
||||
|
||||
//finish up
|
||||
this.getLogger().info("Enabled PlayerStats!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (adventure != null) {
|
||||
adventure.close();
|
||||
adventure = null;
|
||||
}
|
||||
this.getLogger().info("Disabled PlayerStats!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Adventure's BukkitAudiences object
|
||||
* @throws IllegalStateException if PlayerStats is not enabled
|
||||
*/
|
||||
public static @NotNull BukkitAudiences getAdventure() throws IllegalStateException {
|
||||
if (adventure == null) {
|
||||
throw new IllegalStateException("Tried to access Adventure without PlayerStats being enabled!");
|
||||
}
|
||||
return adventure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PlayerStats' ConfigHandler
|
||||
* @throws IllegalStateException if PlayerStats is not enabled
|
||||
*/
|
||||
public static @NotNull ConfigHandler getConfigHandler() throws IllegalStateException {
|
||||
if (config == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public static @NotNull OfflinePlayerHandler getOfflinePlayerHandler() throws IllegalStateException {
|
||||
if (offlinePlayerHandler == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return offlinePlayerHandler;
|
||||
}
|
||||
|
||||
public static @NotNull LanguageKeyHandler getLanguageKeyHandler() {
|
||||
if (languageKeyHandler == null) {
|
||||
languageKeyHandler = new LanguageKeyHandler(instance);
|
||||
}
|
||||
return languageKeyHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the EnumHandler. If there is no EnumHandler, one will be created.
|
||||
* @return PlayerStat's EnumHandler
|
||||
*/
|
||||
public static @NotNull EnumHandler getEnumHandler() {
|
||||
if (enumHandler == null) {
|
||||
enumHandler = new EnumHandler();
|
||||
}
|
||||
return enumHandler;
|
||||
}
|
||||
|
||||
public static @NotNull StatCalculator getStatCalculator() throws IllegalStateException {
|
||||
if (statCalculator == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return statCalculator;
|
||||
}
|
||||
|
||||
public static @NotNull InternalFormatter getStatFormatter() throws IllegalStateException {
|
||||
if (outputManager == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return outputManager;
|
||||
}
|
||||
|
||||
public static @NotNull PlayerStats getPlayerStatsAPI() throws IllegalStateException {
|
||||
if (playerStatsAPI == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return playerStatsAPI;
|
||||
}
|
||||
|
||||
private void initializeMainClasses() {
|
||||
instance = this;
|
||||
adventure = BukkitAudiences.create(this);
|
||||
|
||||
config = new ConfigHandler(this);
|
||||
enumHandler = new EnumHandler();
|
||||
languageKeyHandler = new LanguageKeyHandler(instance);
|
||||
offlinePlayerHandler = new OfflinePlayerHandler();
|
||||
|
||||
shareManager = new ShareManager(config);
|
||||
statCalculator = new StatCalculator(offlinePlayerHandler);
|
||||
outputManager = new OutputManager(adventure, config, shareManager);
|
||||
threadManager = new ThreadManager(config, statCalculator, outputManager);
|
||||
|
||||
MessageBuilder apiMessageBuilder = MessageBuilder.defaultBuilder(config);
|
||||
playerStatsAPI = new PlayerStatsAPI(apiMessageBuilder, offlinePlayerHandler);
|
||||
}
|
||||
|
||||
private void setupMetrics() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Metrics metrics = new Metrics(instance, 15923);
|
||||
final boolean placeholderExpansionActive;
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
|
||||
PlaceholderExpansion expansion = PlaceholderAPIPlugin
|
||||
.getInstance()
|
||||
.getLocalExpansionManager()
|
||||
.getExpansion("playerstats");
|
||||
placeholderExpansionActive = expansion != null;
|
||||
} else {
|
||||
placeholderExpansionActive = false;
|
||||
}
|
||||
metrics.addCustomChart(new SimplePie("using_placeholder_expansion", () -> placeholderExpansionActive ? "yes" : "no"));
|
||||
}
|
||||
}.runTaskLaterAsynchronously(this, 200);
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* The ThreadManager is in charge of the Threads that PlayerStats
|
||||
* can utilize. It keeps track of past and currently active Threads,
|
||||
* to ensure a Player cannot start multiple Threads at the same time
|
||||
* (thereby limiting them to one stat-lookup at a time). It also
|
||||
* passes appropriate references along to the {@link StatThread}
|
||||
* or {@link ReloadThread}, to ensure those will never run at the
|
||||
* same time.
|
||||
*/
|
||||
public final class ThreadManager {
|
||||
|
||||
private final static int threshold = 10;
|
||||
private int statThreadID;
|
||||
private int reloadThreadID;
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static OutputManager outputManager;
|
||||
private static StatCalculator statCalculator;
|
||||
|
||||
private ReloadThread lastActiveReloadThread;
|
||||
private StatThread lastActiveStatThread;
|
||||
private final HashMap<String, Thread> statThreads;
|
||||
private static long lastRecordedCalcTime;
|
||||
|
||||
public ThreadManager(ConfigHandler config, StatCalculator statCalculator, OutputManager outputManager) {
|
||||
ThreadManager.config = config;
|
||||
ThreadManager.outputManager = outputManager;
|
||||
ThreadManager.statCalculator = statCalculator;
|
||||
|
||||
statThreads = new HashMap<>();
|
||||
statThreadID = 0;
|
||||
reloadThreadID = 0;
|
||||
lastRecordedCalcTime = 0;
|
||||
|
||||
startReloadThread(null);
|
||||
}
|
||||
|
||||
public static int getTaskThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public void startReloadThread(CommandSender sender) {
|
||||
if (lastActiveReloadThread == null || !lastActiveReloadThread.isAlive()) {
|
||||
reloadThreadID += 1;
|
||||
|
||||
lastActiveReloadThread = new ReloadThread(config, outputManager, reloadThreadID, lastActiveStatThread, sender);
|
||||
lastActiveReloadThread.start();
|
||||
}
|
||||
else {
|
||||
MyLogger.logLowLevelMsg("Another reloadThread is already running! (" + lastActiveReloadThread.getName() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
public void startStatThread(RequestSettings requestSettings) {
|
||||
statThreadID += 1;
|
||||
String cmdSender = requestSettings.getCommandSender().getName();
|
||||
|
||||
if (config.limitStatRequests() && statThreads.containsKey(cmdSender)) {
|
||||
Thread runningThread = statThreads.get(cmdSender);
|
||||
if (runningThread.isAlive()) {
|
||||
outputManager.sendFeedbackMsg(requestSettings.getCommandSender(), StandardMessage.REQUEST_ALREADY_RUNNING);
|
||||
} else {
|
||||
startNewStatThread(requestSettings);
|
||||
}
|
||||
} else {
|
||||
startNewStatThread(requestSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the duration in milliseconds of the last top-stat-lookup
|
||||
* (or of loading the offline-player-list if no look-ups have been done yet).
|
||||
*/
|
||||
public static void recordCalcTime(long time) {
|
||||
lastRecordedCalcTime = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration in milliseconds of the last top-stat-lookup
|
||||
* (or of loading the offline-player-list if no look-ups have been done yet).
|
||||
*/
|
||||
public static long getLastRecordedCalcTime() {
|
||||
return lastRecordedCalcTime;
|
||||
}
|
||||
|
||||
private void startNewStatThread(RequestSettings requestSettings) {
|
||||
lastActiveStatThread = new StatThread(outputManager, statCalculator, statThreadID, requestSettings, lastActiveReloadThread);
|
||||
statThreads.put(requestSettings.getCommandSender().getName(), lastActiveStatThread);
|
||||
lastActiveStatThread.start();
|
||||
}
|
||||
}
|
@ -1,49 +1,47 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import com.artemis.the.gr8.playerstats.core.Main;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The outgoing API that represents the core functionality of PlayerStats!
|
||||
*
|
||||
* <p> To work with it, you'll need to call PlayerStats.{@link #getAPI()} and get an instance of
|
||||
* {@link PlayerStatsAPI}. You can then use this object to access any of the further methods.
|
||||
*
|
||||
* <p> Since calculating a top or server statistics can take some time, I strongly
|
||||
* encourage you to call {@link StatRequest#execute()} asynchronously.
|
||||
* Otherwise, the main Thread will have to wait until all calculations are done,
|
||||
* and this can severely impact server performance.
|
||||
* <p> To work with it, you'll need to call PlayerStats.{@link #getAPI()}
|
||||
* and get an instance of PlayerStats. You can then use this object to
|
||||
* access any of the further methods.
|
||||
*
|
||||
* @see StatManager
|
||||
* @see ApiFormatter
|
||||
* @see StatTextFormatter
|
||||
* @see StatNumberFormatter
|
||||
*/
|
||||
public interface PlayerStats {
|
||||
|
||||
/** Gets an instance of the {@link PlayerStatsAPI}.
|
||||
/** Gets an instance of the PlayerStatsAPI.
|
||||
|
||||
* @return the PlayerStats API
|
||||
* @throws IllegalStateException if PlayerStats is not loaded on the server when this method is called*/
|
||||
* @throws IllegalStateException if PlayerStats is not loaded on
|
||||
* the server when this method is called
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
static @NotNull PlayerStats getAPI() throws IllegalStateException {
|
||||
return Main.getPlayerStatsAPI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current version of PlayerStatsAPI.
|
||||
* Use this method to ensure the correct version of
|
||||
* PlayerStats is running on the server before
|
||||
* accessing further API methods, to prevent
|
||||
* <code>ClassDefNotFoundExceptions</code>.
|
||||
* Gets the version number of the PlayerStats API
|
||||
* that's present for this instance of PlayerStats.
|
||||
* This number equals the major version number
|
||||
* of PlayerStats. For v1.7.2, for example,
|
||||
* the API version will be 1.
|
||||
*
|
||||
* @return the version of PlayerStatsAPI present on the server
|
||||
* @return the API version number
|
||||
*/
|
||||
default String getVersion() {
|
||||
return "1.8";
|
||||
}
|
||||
String getVersion();
|
||||
|
||||
StatManager getStatManager();
|
||||
|
||||
ApiFormatter getFormatter();
|
||||
StatTextFormatter getStatTextFormatter();
|
||||
|
||||
StatNumberFormatter getStatNumberFormatter();
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.*;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
|
||||
import static org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
/** The implementation of the API Interface */
|
||||
public final class PlayerStatsAPI implements PlayerStats, StatManager {
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static ApiFormatter apiFormatter;
|
||||
|
||||
@Internal
|
||||
public PlayerStatsAPI(ApiFormatter formatter, OfflinePlayerHandler offlinePlayers) {
|
||||
apiFormatter = formatter;
|
||||
offlinePlayerHandler = offlinePlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiFormatter getFormatter() {
|
||||
return apiFormatter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatManager getStatManager() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatRequest playerStatRequest(String playerName) {
|
||||
RequestSettings request = RequestHandler.getBasicPlayerStatRequest(playerName);
|
||||
return new PlayerStatRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest serverStatRequest() {
|
||||
RequestSettings request = RequestHandler.getBasicServerStatRequest();
|
||||
return new ServerStatRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest topStatRequest(int topListSize) {
|
||||
RequestSettings request = RequestHandler.getBasicTopStatRequest(topListSize);
|
||||
return new TopStatRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest totalTopStatRequest() {
|
||||
int playerCount = offlinePlayerHandler.getOfflinePlayerCount();
|
||||
return topStatRequest(playerCount);
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
@ -9,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Creates an executable {@link StatRequest}. This Request holds all
|
||||
* the information PlayerStats needs to work with, and is used by the {@link StatCalculator}
|
||||
* the information PlayerStats needs to work with, and is used
|
||||
* to get the desired statistic data.
|
||||
*/
|
||||
public interface RequestGenerator<T> {
|
||||
|
@ -1,28 +1,55 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* Turns user input into a {@link StatRequest} that can be used to get statistic data
|
||||
*/
|
||||
public interface StatManager {
|
||||
|
||||
/** Checks if the player belonging to this name
|
||||
* is on PlayerStats' exclude-list (meaning this
|
||||
* player is not counted for the server total, and
|
||||
* does not show in top results).
|
||||
*
|
||||
* @param playerName the name of the player to check
|
||||
* @return true if this player is on the exclude-list
|
||||
*/
|
||||
boolean isExcludedPlayer(String playerName);
|
||||
|
||||
/** Gets a RequestGenerator that can be used to create a PlayerStatRequest.
|
||||
* This RequestGenerator will make sure all default settings
|
||||
* for a player-statistic-lookup are configured.
|
||||
*
|
||||
* @param playerName the player whose statistic is being requested
|
||||
* @return the RequestGenerator */
|
||||
RequestGenerator<Integer> playerStatRequest(String playerName);
|
||||
RequestGenerator<Integer> createPlayerStatRequest(String playerName);
|
||||
|
||||
/**
|
||||
* Executes this StatRequest. This calculation can take some time,
|
||||
* so don't call this from the main Thread if you can help it!
|
||||
*
|
||||
* @return a StatResult containing the value of this lookup, both as
|
||||
* numerical value and as formatted message
|
||||
* @see PlayerStats
|
||||
* @see StatResult
|
||||
*/
|
||||
StatResult<Integer> executePlayerStatRequest(StatRequest<Integer> request);
|
||||
|
||||
/** Gets a RequestGenerator that can be used to create a ServerStatRequest.
|
||||
* This RequestGenerator will make sure all default settings
|
||||
* for a server-statistic-lookup are configured.
|
||||
*
|
||||
* @return the RequestGenerator*/
|
||||
RequestGenerator<Long> serverStatRequest();
|
||||
RequestGenerator<Long> createServerStatRequest();
|
||||
|
||||
/**
|
||||
* Executes this StatRequest. This calculation can take some time,
|
||||
* so don't call this from the main Thread if you can help it!
|
||||
*
|
||||
* @return a StatResult containing the value of this lookup, both as
|
||||
* numerical value and as formatted message
|
||||
* @see PlayerStats
|
||||
* @see StatResult
|
||||
*/
|
||||
StatResult<Long> executeServerStatRequest(StatRequest<Long> request);
|
||||
|
||||
/** Gets a RequestGenerator that can be used to create a TopStatRequest
|
||||
* for a top-list of the specified size. This RequestGenerator will
|
||||
@ -30,7 +57,7 @@ public interface StatManager {
|
||||
*
|
||||
* @param topListSize how big the top-x should be (10 by default)
|
||||
* @return the RequestGenerator*/
|
||||
RequestGenerator<LinkedHashMap<String, Integer>> topStatRequest(int topListSize);
|
||||
RequestGenerator<LinkedHashMap<String, Integer>> createTopStatRequest(int topListSize);
|
||||
|
||||
/** Gets a RequestGenerator that can be used to create a TopStatRequest
|
||||
* for all offline players on the server (those that are included by
|
||||
@ -38,5 +65,16 @@ public interface StatManager {
|
||||
* all default settings for a top-statistic-lookup are configured.
|
||||
*
|
||||
* @return the RequestGenerator*/
|
||||
RequestGenerator<LinkedHashMap<String, Integer>> totalTopStatRequest();
|
||||
RequestGenerator<LinkedHashMap<String, Integer>> createTotalTopStatRequest();
|
||||
|
||||
/**
|
||||
* Executes this StatRequest. This calculation can take some time,
|
||||
* so don't call this from the main Thread if you can help it!
|
||||
*
|
||||
* @return a StatResult containing the value of this lookup, both as
|
||||
* numerical value and as formatted message
|
||||
* @see PlayerStats
|
||||
* @see StatResult
|
||||
*/
|
||||
StatResult<LinkedHashMap<String, Integer>> executeTopRequest(StatRequest<LinkedHashMap<String, Integer>> request);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Unit;
|
||||
|
||||
public interface StatNumberFormatter {
|
||||
|
||||
String formatDefaultNumber(long number);
|
||||
|
||||
String formatDamageNumber(long number, Unit statUnit);
|
||||
|
||||
String formatDistanceNumber(long number, Unit statUnit);
|
||||
|
||||
String formatTimeNumber(long number, Unit biggestTimeUnit, Unit smallestTimeUnit);
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Target;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Holds all the information PlayerStats needs to perform
|
||||
* a lookup, and can be executed by the {@link StatManager}
|
||||
* to get the results.
|
||||
*/
|
||||
public abstract class StatRequest<T> {
|
||||
|
||||
private final Settings settings;
|
||||
|
||||
protected StatRequest(CommandSender requester) {
|
||||
settings = new Settings(requester);
|
||||
}
|
||||
|
||||
public abstract boolean isValid();
|
||||
|
||||
/**
|
||||
* Use this method to view the settings that have
|
||||
* been configured for this StatRequest.
|
||||
*/
|
||||
public Settings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
protected void configureForPlayer(String playerName) {
|
||||
this.settings.target = Target.PLAYER;
|
||||
this.settings.playerName = playerName;
|
||||
}
|
||||
|
||||
protected void configureForServer() {
|
||||
this.settings.target = Target.SERVER;
|
||||
}
|
||||
|
||||
protected void configureForTop(int topListSize) {
|
||||
this.settings.target = Target.TOP;
|
||||
this.settings.topListSize = topListSize;
|
||||
}
|
||||
|
||||
protected void configureUntyped(@NotNull Statistic statistic) {
|
||||
if (statistic.getType() != Statistic.Type.UNTYPED) {
|
||||
throw new IllegalArgumentException("This statistic is not of Type.Untyped");
|
||||
}
|
||||
this.settings.statistic = statistic;
|
||||
}
|
||||
|
||||
protected void configureBlockOrItemType(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
|
||||
Statistic.Type type = statistic.getType();
|
||||
if (type == Statistic.Type.BLOCK && material.isBlock()) {
|
||||
this.settings.block = material;
|
||||
}
|
||||
else if (type == Statistic.Type.ITEM && material.isItem()){
|
||||
this.settings.item = material;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Either this statistic is not of Type.Block or Type.Item, or no valid block or item has been provided");
|
||||
}
|
||||
this.settings.statistic = statistic;
|
||||
this.settings.subStatEntryName = material.toString();
|
||||
}
|
||||
|
||||
protected void configureEntityType(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
|
||||
if (statistic.getType() != Statistic.Type.ENTITY) {
|
||||
throw new IllegalArgumentException("This statistic is not of Type.Entity");
|
||||
}
|
||||
this.settings.statistic = statistic;
|
||||
this.settings.entity = entityType;
|
||||
this.settings.subStatEntryName = entityType.toString();
|
||||
}
|
||||
|
||||
protected boolean hasMatchingSubStat() {
|
||||
if (settings.statistic == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (settings.statistic.getType()) {
|
||||
case BLOCK -> {
|
||||
return settings.block != null;
|
||||
}
|
||||
case ENTITY -> {
|
||||
return settings.entity != null;
|
||||
}
|
||||
case ITEM -> {
|
||||
return settings.item != null;
|
||||
}
|
||||
default -> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static final class Settings {
|
||||
private final CommandSender sender;
|
||||
private Statistic statistic;
|
||||
private String playerName;
|
||||
private Target target;
|
||||
private int topListSize;
|
||||
|
||||
private String subStatEntryName;
|
||||
private EntityType entity;
|
||||
private Material block;
|
||||
private Material item;
|
||||
|
||||
/**
|
||||
* @param sender the CommandSender who prompted this RequestGenerator
|
||||
*/
|
||||
private Settings(@NotNull CommandSender sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public @NotNull CommandSender getCommandSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public boolean isConsoleSender() {
|
||||
return sender instanceof ConsoleCommandSender;
|
||||
}
|
||||
|
||||
public Statistic getStatistic() {
|
||||
return statistic;
|
||||
}
|
||||
|
||||
public @Nullable String getSubStatEntryName() {
|
||||
return subStatEntryName;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public @NotNull Target getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public int getTopListSize() {
|
||||
return this.topListSize;
|
||||
}
|
||||
|
||||
public EntityType getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
public Material getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
public Material getItem() {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.ApiFormatter;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
@ -25,54 +24,54 @@ import net.kyori.adventure.text.TextComponent;
|
||||
* <br> [2.] [player-name] [.....] [formatted-number]
|
||||
* <br> [3.] etc...
|
||||
* </ul>
|
||||
|
||||
* <p>
|
||||
* By default, the resulting message is a {@link TextComponent}, which can be
|
||||
* sent directly to a Minecraft client or console with the Adventure library.
|
||||
* To send a Component, you need to get a {@link BukkitAudiences} object,
|
||||
* and use that to send the desired Component. Normally you would have to add
|
||||
* Adventure as a dependency to your project, but since the library is included
|
||||
* in PlayerStats, you can access it through the PlayerStatsAPI. Information
|
||||
* on how to get and use the BukkitAudiences object can be found on
|
||||
* and use that to send the desired Component. Information on how to get
|
||||
* and use the BukkitAudiences object can be found on
|
||||
* <a href="https://docs.adventure.kyori.net/platform/bukkit.html">Adventure's website</a>.
|
||||
*
|
||||
* <p>You can also use the provided {@link #getFormattedString()} method to get the
|
||||
* <p>You can also use the provided {@link #formattedString()} method to get the
|
||||
* same information in String-format. Don't use Adventure's <code>#content()</code>
|
||||
* or <code>#toString()</code> methods on the Components - those won't get the actual
|
||||
* message. And finally, if you want the results to be formatted differently,
|
||||
* you can get an instance of the {@link ApiFormatter}.
|
||||
* you can get an instance of the {@link StatTextFormatter}.
|
||||
*/
|
||||
public interface StatResult<T> {
|
||||
public record StatResult<T>(T value, TextComponent formattedComponent, String formattedString) {
|
||||
|
||||
/**
|
||||
* Gets the raw number for the completed stat-lookup this {@link StatResult}
|
||||
* stores.
|
||||
* Gets the raw number for the completed stat-lookup this {@link StatResult} stores.
|
||||
*
|
||||
* @return {@code Integer} for playerStat, {@code Long} for serverStat,
|
||||
* and {@code LinkedHashMap<String, Integer>} for topStat
|
||||
* @return {@code Integer} for playerStat, {@code Long} for serverStat, and {@code LinkedHashMap<String, Integer>}
|
||||
* for topStat
|
||||
*/
|
||||
T getNumericalValue();
|
||||
T getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatted message for the completed stat-lookup this
|
||||
* StatResult stores.
|
||||
|
||||
* @return a {@code TextComponent} message containing the formatted number.
|
||||
* This message follows the same style/color/language settings that are
|
||||
* specified in the PlayerStats config. See class description for more
|
||||
* Gets the formatted message for the completed stat-lookup this StatResult stores.
|
||||
*
|
||||
* @return a {@code TextComponent} message containing the formatted number. This message follows the same
|
||||
* style/color/language settings that are specified in the PlayerStats config. See class description for more
|
||||
* information.
|
||||
* @see StatResult
|
||||
*/
|
||||
TextComponent getFormattedTextComponent();
|
||||
TextComponent getFormattedTextComponent() {
|
||||
return formattedComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatted message for the completed stat-lookup this
|
||||
* StatResult stores.
|
||||
|
||||
* @return a String message containing the formatted number. This message
|
||||
* follows the same style and color settings that are specified in the
|
||||
* PlayerStats config, but it is not translatable (it is always plain English).
|
||||
* See class description for more information.
|
||||
* Gets the formatted message for the completed stat-lookup this StatResult stores.
|
||||
*
|
||||
* @return a String message containing the formatted number. This message follows the same style and color settings
|
||||
* that are specified in the PlayerStats config, but it is not translatable (it is always plain English). See class
|
||||
* description for more information.
|
||||
* @see StatResult
|
||||
*/
|
||||
String getFormattedString();
|
||||
@Override
|
||||
public String formattedString() {
|
||||
return formattedString;
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.NumberFormatter;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.StatResult;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Unit;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Statistic;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -16,7 +13,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
* @see StatResult
|
||||
*/
|
||||
public interface ApiFormatter {
|
||||
public interface StatTextFormatter {
|
||||
|
||||
/**
|
||||
* Turns a TextComponent into its String representation. This method is equipped
|
||||
@ -28,19 +25,7 @@ public interface ApiFormatter {
|
||||
* but with color, style and formatting. TranslatableComponents will be turned into
|
||||
* plain English.
|
||||
*/
|
||||
default String TextComponentToString(TextComponent component) {
|
||||
return ComponentUtils.getTranslatableComponentSerializer()
|
||||
.serialize(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link NumberFormatter} to format raw numbers into something more readable.
|
||||
*
|
||||
* @return the <code>NumberFormatter</code>
|
||||
*/
|
||||
default NumberFormatter getNumberFormatter() {
|
||||
return new NumberFormatter();
|
||||
}
|
||||
String textComponentToString(TextComponent component);
|
||||
|
||||
/**
|
||||
* Gets the default prefix PlayerStats uses.
|
@ -1,4 +1,4 @@
|
||||
package com.artemis.the.gr8.playerstats.enums;
|
||||
package com.artemis.the.gr8.playerstats.api.enums;
|
||||
|
||||
/**
|
||||
* This enum represents the targets PlayerStats accepts
|
@ -1,4 +1,4 @@
|
||||
package com.artemis.the.gr8.playerstats.enums;
|
||||
package com.artemis.the.gr8.playerstats.api.enums;
|
||||
|
||||
import org.bukkit.Statistic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -185,10 +185,12 @@ public enum Unit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most suitable Unit for this number.
|
||||
* Gets the largest Unit this number can be expressed in as a whole number.
|
||||
* For example, for Type TIME a value of 80.000 would return Unit.HOUR
|
||||
* (80.000 ticks equals 4.000 seconds, 67 minutes, or 1 hour)
|
||||
*
|
||||
* @param type the Unit.Type of the statistic this number belongs to
|
||||
* @param number the statistic number as returned by Player.getStatistic()
|
||||
* @param type the Unit.Type of this statistic
|
||||
* @param number the statistic value in ticks as returned by Player.getStatistic()
|
||||
* @return the Unit
|
||||
*/
|
||||
public static Unit getMostSuitableUnit(Unit.Type type, long number) {
|
@ -1,79 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.commands;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class StatCommand implements CommandExecutor {
|
||||
|
||||
private static ThreadManager threadManager;
|
||||
private static OutputManager outputManager;
|
||||
|
||||
public StatCommand(OutputManager m, ThreadManager t) {
|
||||
threadManager = t;
|
||||
outputManager = m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
if (args.length == 0 || args[0].equalsIgnoreCase("help")) { //in case of less than 1 argument or "help", display the help message
|
||||
outputManager.sendHelp(sender);
|
||||
}
|
||||
else if (args[0].equalsIgnoreCase("examples") ||
|
||||
args[0].equalsIgnoreCase("example")) { //in case of "statistic examples", show examples
|
||||
outputManager.sendExamples(sender);
|
||||
}
|
||||
else {
|
||||
RequestSettings baseRequest = RequestHandler.getBasicInternalStatRequest(sender);
|
||||
RequestHandler requestHandler = new RequestHandler(baseRequest);
|
||||
|
||||
RequestSettings completedRequest = requestHandler.getRequestFromArgs(args);
|
||||
if (completedRequest.isValid()) {
|
||||
threadManager.startStatThread(completedRequest);
|
||||
} else {
|
||||
sendFeedback(completedRequest);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a given {@link RequestSettings} object does not result in a valid
|
||||
* statistic look-up, this will send a feedback message to the CommandSender
|
||||
* that made the request. The following is checked:
|
||||
* <ul>
|
||||
* <li>Is a <code>statistic</code> set?
|
||||
* <li>Is a <code>subStatEntry</code> needed, and if so, is a corresponding Material/EntityType present?
|
||||
* <li>If the <code>target</code> is Player, is a valid <code>playerName</code> provided?
|
||||
* </ul>
|
||||
*
|
||||
* @param requestSettings the RequestSettings to give feedback on
|
||||
*/
|
||||
private void sendFeedback(RequestSettings requestSettings) {
|
||||
CommandSender sender = requestSettings.getCommandSender();
|
||||
|
||||
if (requestSettings.getStatistic() == null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_STAT_NAME);
|
||||
}
|
||||
else if (requestSettings.getTarget() == Target.PLAYER && requestSettings.getPlayerName() == null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_PLAYER_NAME);
|
||||
}
|
||||
else {
|
||||
Statistic.Type type = requestSettings.getStatistic().getType();
|
||||
if (type != Statistic.Type.UNTYPED && requestSettings.getSubStatEntryName() == null) {
|
||||
outputManager.sendFeedbackMsgMissingSubStat(sender, type);
|
||||
} else {
|
||||
outputManager.sendFeedbackMsgWrongSubStat(sender, type, requestSettings.getSubStatEntryName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.commands;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.commands.cmdutils.TabCompleteHelper;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
|
||||
private final EnumHandler enumHandler;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private final TabCompleteHelper tabCompleteHelper;
|
||||
|
||||
private final List<String> commandOptions;
|
||||
|
||||
public TabCompleter(EnumHandler enumHandler, OfflinePlayerHandler offlinePlayerHandler) {
|
||||
this.enumHandler = enumHandler;
|
||||
this.offlinePlayerHandler = offlinePlayerHandler;
|
||||
tabCompleteHelper = new TabCompleteHelper(enumHandler);
|
||||
|
||||
commandOptions = new ArrayList<>();
|
||||
commandOptions.add("top");
|
||||
commandOptions.add("player");
|
||||
commandOptions.add("server");
|
||||
commandOptions.add("me");
|
||||
|
||||
}
|
||||
|
||||
//args[0] = statistic (length = 1)
|
||||
//args[1] = commandOption (top/player/me) OR substatistic (block/item/entitytype) (length = 2)
|
||||
//args[2] = executorName OR commandOption (top/player/me) (length = 3)
|
||||
//args[3] = executorName (length = 4)
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
List<String> tabSuggestions = new ArrayList<>();
|
||||
|
||||
if (args.length >= 1) {
|
||||
String currentArg = args[args.length -1];
|
||||
|
||||
if (args.length == 1) { //after typing "stat", suggest a list of viable statistics
|
||||
tabSuggestions = getFirstArgSuggestions(args[0]);
|
||||
}
|
||||
|
||||
else { //after checking if args[0] is a viable statistic, suggest substatistic OR commandOptions
|
||||
String previousArg = args[args.length -2];
|
||||
|
||||
if (enumHandler.isStatistic(previousArg)) {
|
||||
Statistic stat = EnumHandler.getStatEnum(previousArg);
|
||||
if (stat != null) {
|
||||
tabSuggestions = getTabSuggestions(getRelevantList(stat), currentArg);
|
||||
}
|
||||
}
|
||||
|
||||
//if previous arg = "player"
|
||||
else if (previousArg.equalsIgnoreCase("player")) {
|
||||
|
||||
if (args.length >= 3 && enumHandler.isEntityStatistic(args[args.length-3])) {
|
||||
tabSuggestions = commandOptions; //if arg before "player" was entity-stat, suggest commandOptions
|
||||
}
|
||||
else { //otherwise "player" is target-flag: suggest playerNames
|
||||
tabSuggestions = getTabSuggestions(offlinePlayerHandler.getOfflinePlayerNames(), currentArg);
|
||||
}
|
||||
}
|
||||
|
||||
//after a substatistic, suggest commandOptions
|
||||
else if (enumHandler.isSubStatEntry(previousArg)) {
|
||||
tabSuggestions = commandOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tabSuggestions;
|
||||
}
|
||||
|
||||
private List<String> getFirstArgSuggestions(String currentArg) {
|
||||
List<String> suggestions = enumHandler.getStatNames();
|
||||
suggestions.add("examples");
|
||||
suggestions.add("help");
|
||||
return getTabSuggestions(suggestions, currentArg);
|
||||
}
|
||||
|
||||
private List<String> getTabSuggestions(List<String> completeList, String currentArg) {
|
||||
return completeList.stream()
|
||||
.filter(item -> item.toLowerCase(Locale.ENGLISH).contains(currentArg.toLowerCase(Locale.ENGLISH)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<String> getRelevantList(Statistic stat) {
|
||||
switch (stat.getType()) {
|
||||
case BLOCK -> {
|
||||
return tabCompleteHelper.getAllBlockNames();
|
||||
}
|
||||
case ITEM -> {
|
||||
if (stat == Statistic.BREAK_ITEM) {
|
||||
return tabCompleteHelper.getItemBrokenSuggestions();
|
||||
} else {
|
||||
return tabCompleteHelper.getAllItemNames();
|
||||
}
|
||||
}
|
||||
case ENTITY -> {
|
||||
return tabCompleteHelper.getEntitySuggestions();
|
||||
}
|
||||
default -> {
|
||||
return commandOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.commands.cmdutils;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class TabCompleteHelper {
|
||||
|
||||
private final EnumHandler enumHandler;
|
||||
private static List<String> itemBrokenSuggestions;
|
||||
private static List<String> entitySuggestions;
|
||||
|
||||
public TabCompleteHelper(EnumHandler enumHandler) {
|
||||
this.enumHandler = enumHandler;
|
||||
prepareLists();
|
||||
}
|
||||
|
||||
public List<String> getAllItemNames() {
|
||||
return enumHandler.getItemNames();
|
||||
}
|
||||
|
||||
public List<String> getItemBrokenSuggestions() {
|
||||
return itemBrokenSuggestions;
|
||||
}
|
||||
|
||||
public List<String> getAllBlockNames() {
|
||||
return enumHandler.getBlockNames();
|
||||
}
|
||||
|
||||
public List<String> getEntitySuggestions() {
|
||||
return entitySuggestions;
|
||||
}
|
||||
|
||||
|
||||
private static void prepareLists() {
|
||||
//breaking an item means running its durability negative
|
||||
itemBrokenSuggestions = Arrays.stream(Material.values())
|
||||
.parallel()
|
||||
.filter(Material::isItem)
|
||||
.filter(item -> item.getMaxDurability() != 0)
|
||||
.map(Material::toString)
|
||||
.map(string -> string.toLowerCase(Locale.ENGLISH))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
//the only statistics dealing with entities are killed_entity and entity_killed_by
|
||||
entitySuggestions = Arrays.stream(EntityType.values())
|
||||
.parallel()
|
||||
.filter(EntityType::isAlive)
|
||||
.map(EntityType::toString)
|
||||
.map(string -> string.toLowerCase(Locale.ENGLISH))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.config;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.tchristofferson.configupdater.ConfigUpdater;
|
||||
|
||||
public final class ConfigUpdateHandler {
|
||||
|
||||
/**
|
||||
* Add new key-value pairs to the config without losing comments,
|
||||
* using <a href="https://github.com/tchristofferson/Config-Updater">tchristofferson's Config-Updater</a>
|
||||
*/
|
||||
public ConfigUpdateHandler(Main plugin, File configFile, int configVersion) {
|
||||
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configFile);
|
||||
updateTopListDefault(configuration);
|
||||
updateDefaultColors(configuration);
|
||||
configuration.set("config-version", configVersion);
|
||||
try {
|
||||
configuration.save(configFile);
|
||||
ConfigUpdater.update(plugin, configFile.getName(), configFile);
|
||||
MyLogger.logLowLevelMsg("Your config has been updated to version " + configVersion +
|
||||
", but all of your custom settings should still be there!");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the value for "top-list" to migrate the config file from
|
||||
* versions 1 or 2 to version 3 and above.
|
||||
*/
|
||||
private void updateTopListDefault(YamlConfiguration configuration) {
|
||||
String oldTitle = configuration.getString("top-list-title");
|
||||
if (oldTitle != null && oldTitle.equalsIgnoreCase("Top [x]")) {
|
||||
configuration.set("top-list-title", "Top");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts some of the default colors to migrate from versions 2
|
||||
* or 3 to version 4 and above.
|
||||
*/
|
||||
private void updateDefaultColors(YamlConfiguration configuration) {
|
||||
updateColor(configuration, "top-list.title", "yellow", "#FFD52B");
|
||||
updateColor(configuration, "top-list.title", "#FFEA40", "#FFD52B");
|
||||
updateColor(configuration, "top-list.stat-names", "yellow", "#FFD52B");
|
||||
updateColor(configuration, "top-list.stat-names", "#FFEA40", "#FFD52B");
|
||||
updateColor(configuration, "top-list.sub-stat-names", "#FFD52B", "yellow");
|
||||
|
||||
updateColor(configuration, "individual-statistics.stat-names", "yellow", "#FFD52B");
|
||||
updateColor(configuration, "individual-statistics.sub-stat-names", "#FFD52B", "yellow");
|
||||
updateColor(configuration, "total-server.title", "gold", "#55AAFF");
|
||||
updateColor(configuration, "total-server.server-name", "gold", "#55AAFF");
|
||||
updateColor(configuration, "total-server.stat-names", "yellow", "#FFD52B");
|
||||
updateColor(configuration, "total-server.sub-stat-names", "#FFD52B", "yellow");
|
||||
}
|
||||
|
||||
private void updateColor(YamlConfiguration configuration, String path, String oldValue, String newValue) {
|
||||
String configString = configuration.getString(path);
|
||||
if (configString != null && configString.equalsIgnoreCase(oldValue)) {
|
||||
configuration.set(path, newValue);
|
||||
}
|
||||
}
|
||||
}
|
189
src/main/java/com/artemis/the/gr8/playerstats/core/Main.java
Normal file
189
src/main/java/com/artemis/the/gr8/playerstats/core/Main.java
Normal file
@ -0,0 +1,189 @@
|
||||
package com.artemis.the.gr8.playerstats.core;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.PlayerStats;
|
||||
import com.artemis.the.gr8.playerstats.api.StatNumberFormatter;
|
||||
import com.artemis.the.gr8.playerstats.api.StatTextFormatter;
|
||||
import com.artemis.the.gr8.playerstats.api.StatManager;
|
||||
import com.artemis.the.gr8.playerstats.core.commands.*;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.msgutils.NumberFormatter;
|
||||
import com.artemis.the.gr8.playerstats.core.multithreading.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.core.statrequest.RequestManager;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.listeners.JoinListener;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.msgutils.LanguageKeyHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.sharing.ShareManager;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.OfflinePlayerHandler;
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* PlayerStats' Main class
|
||||
*/
|
||||
public final class Main extends JavaPlugin implements PlayerStats {
|
||||
|
||||
private static JavaPlugin pluginInstance;
|
||||
private static PlayerStats playerStatsAPI;
|
||||
private static BukkitAudiences adventure;
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static ThreadManager threadManager;
|
||||
private static LanguageKeyHandler languageKeyHandler;
|
||||
private static OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
private static RequestManager requestManager;
|
||||
private static OutputManager outputManager;
|
||||
private static ShareManager shareManager;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
initializeMainClasses();
|
||||
registerCommands();
|
||||
setupMetrics();
|
||||
|
||||
//register the listener
|
||||
Bukkit.getPluginManager().registerEvents(new JoinListener(threadManager), this);
|
||||
|
||||
//finish up
|
||||
this.getLogger().info("Enabled PlayerStats!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (adventure != null) {
|
||||
adventure.close();
|
||||
adventure = null;
|
||||
}
|
||||
this.getLogger().info("Disabled PlayerStats!");
|
||||
}
|
||||
|
||||
public void reloadPlugin() {
|
||||
config.reload();
|
||||
MyLogger.setDebugLevel(config.getDebugLevel());
|
||||
languageKeyHandler.reload();
|
||||
offlinePlayerHandler.reload();
|
||||
outputManager.updateSettings();
|
||||
shareManager.updateSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the JavaPlugin instance associated with PlayerStats
|
||||
* @throws IllegalStateException if PlayerStats is not enabled
|
||||
*/
|
||||
public static @NotNull JavaPlugin getPluginInstance() throws IllegalStateException {
|
||||
if (pluginInstance == null) {
|
||||
throw new IllegalStateException("PlayerStats is not loaded!");
|
||||
}
|
||||
return pluginInstance;
|
||||
}
|
||||
|
||||
public static @NotNull PlayerStats getPlayerStatsAPI() throws IllegalStateException {
|
||||
if (playerStatsAPI == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return playerStatsAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all classes that need initializing,
|
||||
* and store references to classes that are
|
||||
* needed for the Command classes or the API.
|
||||
*/
|
||||
private void initializeMainClasses() {
|
||||
pluginInstance = this;
|
||||
playerStatsAPI = this;
|
||||
adventure = BukkitAudiences.create(this);
|
||||
|
||||
config = ConfigHandler.getInstance();
|
||||
languageKeyHandler = LanguageKeyHandler.getInstance();
|
||||
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
|
||||
shareManager = ShareManager.getInstance();
|
||||
|
||||
outputManager = new OutputManager(adventure);
|
||||
requestManager = new RequestManager(outputManager);
|
||||
threadManager = new ThreadManager(this, outputManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all commands and assign the tabCompleter
|
||||
* to the relevant commands.
|
||||
*/
|
||||
private void registerCommands() {
|
||||
TabCompleter tabCompleter = new TabCompleter();
|
||||
|
||||
PluginCommand statcmd = this.getCommand("statistic");
|
||||
if (statcmd != null) {
|
||||
statcmd.setExecutor(new StatCommand(outputManager, threadManager));
|
||||
statcmd.setTabCompleter(tabCompleter);
|
||||
}
|
||||
PluginCommand excludecmd = this.getCommand("statisticexclude");
|
||||
if (excludecmd != null) {
|
||||
excludecmd.setExecutor(new ExcludeCommand(outputManager));
|
||||
excludecmd.setTabCompleter(tabCompleter);
|
||||
}
|
||||
|
||||
PluginCommand reloadcmd = this.getCommand("statisticreload");
|
||||
if (reloadcmd != null) {
|
||||
reloadcmd.setExecutor(new ReloadCommand(threadManager));
|
||||
}
|
||||
PluginCommand sharecmd = this.getCommand("statisticshare");
|
||||
if (sharecmd != null) {
|
||||
sharecmd.setExecutor(new ShareCommand(outputManager));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup bstats
|
||||
*/
|
||||
private void setupMetrics() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Metrics metrics = new Metrics(pluginInstance, 15923);
|
||||
final boolean placeholderExpansionActive;
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
|
||||
PlaceholderExpansion expansion = PlaceholderAPIPlugin
|
||||
.getInstance()
|
||||
.getLocalExpansionManager()
|
||||
.getExpansion("playerstats");
|
||||
placeholderExpansionActive = expansion != null;
|
||||
} else {
|
||||
placeholderExpansionActive = false;
|
||||
}
|
||||
metrics.addCustomChart(new SimplePie("using_placeholder_expansion", () -> placeholderExpansionActive ? "yes" : "no"));
|
||||
}
|
||||
}.runTaskLaterAsynchronously(this, 200);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getVersion() {
|
||||
return String.valueOf(this.getDescription().getVersion().charAt(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatManager getStatManager() {
|
||||
return requestManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatTextFormatter getStatTextFormatter() {
|
||||
return outputManager.getMainMessageBuilder();
|
||||
}
|
||||
|
||||
@Contract(" -> new")
|
||||
@Override
|
||||
public @NotNull StatNumberFormatter getStatNumberFormatter() {
|
||||
return new NumberFormatter();
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.artemis.the.gr8.playerstats.core.commands;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.core.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.OfflinePlayerHandler;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public final class ExcludeCommand implements CommandExecutor {
|
||||
|
||||
private static OutputManager outputManager;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
public ExcludeCommand(OutputManager outputManager) {
|
||||
ExcludeCommand.outputManager = outputManager;
|
||||
this.offlinePlayerHandler = OfflinePlayerHandler.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (args.length == 0) {
|
||||
outputManager.sendExcludeInfo(sender);
|
||||
}
|
||||
else if (args.length == 1) {
|
||||
switch (args[0]) {
|
||||
case "info" -> outputManager.sendExcludeInfo(sender);
|
||||
case "list" -> {
|
||||
ArrayList<String> excludedPlayers = offlinePlayerHandler.getExcludedPlayerNames();
|
||||
outputManager.sendExcludedList(sender, excludedPlayers);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (args[0]) {
|
||||
case "add" -> {
|
||||
if (offlinePlayerHandler.addPlayerToExcludeList(args[1])) {
|
||||
outputManager.sendFeedbackMsgPlayerExcluded(sender, args[1]);
|
||||
} else {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.EXCLUDE_FAILED);
|
||||
}
|
||||
}
|
||||
case "remove" -> {
|
||||
if (offlinePlayerHandler.removePlayerFromExcludeList(args[1])) {
|
||||
outputManager.sendFeedbackMsgPlayerIncluded(sender, args[1]);
|
||||
} else {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.INCLUDE_FAILED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.artemis.the.gr8.playerstats.commands;
|
||||
package com.artemis.the.gr8.playerstats.core.commands;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.core.multithreading.ThreadManager;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
@ -11,12 +11,12 @@ public final class ReloadCommand implements CommandExecutor {
|
||||
|
||||
private static ThreadManager threadManager;
|
||||
|
||||
public ReloadCommand(ThreadManager t) {
|
||||
threadManager = t;
|
||||
public ReloadCommand(ThreadManager threadManager) {
|
||||
ReloadCommand.threadManager = threadManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
threadManager.startReloadThread(sender);
|
||||
return true;
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package com.artemis.the.gr8.playerstats.commands;
|
||||
package com.artemis.the.gr8.playerstats.core.commands;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ShareManager;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.InternalStatResult;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.core.sharing.ShareManager;
|
||||
import com.artemis.the.gr8.playerstats.core.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.core.sharing.StoredResult;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@ -12,17 +12,17 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ShareCommand implements CommandExecutor {
|
||||
|
||||
private static ShareManager shareManager;
|
||||
private static OutputManager outputManager;
|
||||
private static ShareManager shareManager;
|
||||
|
||||
public ShareCommand(ShareManager s, OutputManager m) {
|
||||
shareManager = s;
|
||||
outputManager = m;
|
||||
public ShareCommand(OutputManager outputManager) {
|
||||
ShareCommand.outputManager = outputManager;
|
||||
shareManager = ShareManager.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) {
|
||||
if (args.length == 1 && ShareManager.isEnabled()) {
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, @NotNull String[] args) {
|
||||
if (args.length == 1 && shareManager.isEnabled()) {
|
||||
int shareCode;
|
||||
try {
|
||||
shareCode = Integer.parseInt(args[0]);
|
||||
@ -37,7 +37,7 @@ public final class ShareCommand implements CommandExecutor {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.STILL_ON_SHARE_COOLDOWN);
|
||||
}
|
||||
else {
|
||||
InternalStatResult result = shareManager.getStatResult(sender.getName(), shareCode);
|
||||
StoredResult result = shareManager.getStatResult(sender.getName(), shareCode);
|
||||
if (result == null) { //at this point the only possible cause of formattedComponent being null is the request being older than 25 player-requests ago
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.STAT_RESULTS_TOO_OLD);
|
||||
} else {
|
@ -0,0 +1,270 @@
|
||||
package com.artemis.the.gr8.playerstats.core.commands;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import com.artemis.the.gr8.playerstats.core.multithreading.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.core.statrequest.PlayerStatRequest;
|
||||
import com.artemis.the.gr8.playerstats.core.statrequest.ServerStatRequest;
|
||||
import com.artemis.the.gr8.playerstats.core.statrequest.TopStatRequest;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.OfflinePlayerHandler;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class StatCommand implements CommandExecutor {
|
||||
|
||||
private static final Pattern pattern = Pattern.compile("top|server|me|player");
|
||||
|
||||
private static ThreadManager threadManager;
|
||||
private static OutputManager outputManager;
|
||||
private final ConfigHandler config;
|
||||
private final EnumHandler enumHandler;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
public StatCommand(OutputManager outputManager, ThreadManager threadManager) {
|
||||
StatCommand.threadManager = threadManager;
|
||||
StatCommand.outputManager = outputManager;
|
||||
|
||||
config = ConfigHandler.getInstance();
|
||||
enumHandler = EnumHandler.getInstance();
|
||||
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (args.length == 0 ||
|
||||
args[0].equalsIgnoreCase("help") ||
|
||||
args[0].equalsIgnoreCase("info")) {
|
||||
outputManager.sendHelp(sender);
|
||||
}
|
||||
else if (args[0].equalsIgnoreCase("examples") ||
|
||||
args[0].equalsIgnoreCase("example")) {
|
||||
outputManager.sendExamples(sender);
|
||||
}
|
||||
else {
|
||||
ArgProcessor processor = new ArgProcessor(sender, args);
|
||||
if (processor.request != null && processor.request.isValid()) {
|
||||
threadManager.startStatThread(processor.request);
|
||||
} else {
|
||||
sendFeedback(sender, processor);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the provided args and sends an appropriate
|
||||
* feedback message to the CommandSender that called the
|
||||
* stat command. The following is checked:
|
||||
* <ul>
|
||||
* <li>Is a <code>statistic</code> set?
|
||||
* <li>Is a <code>subStatEntry</code> needed, and if so,
|
||||
* is a corresponding Material/EntityType present?
|
||||
* <li>If the <code>target</code> is Player, is a valid
|
||||
* <code>playerName</code> provided?
|
||||
* </ul>
|
||||
*
|
||||
* @param sender the CommandSender to send feedback to
|
||||
* @param processor the ArgProcessor object that holds
|
||||
* the analyzed args
|
||||
*/
|
||||
private void sendFeedback(CommandSender sender, @NotNull ArgProcessor processor) {
|
||||
if (processor.statistic == null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_STAT_NAME);
|
||||
}
|
||||
else if (processor.target == Target.PLAYER) {
|
||||
if (processor.playerName == null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_PLAYER_NAME);
|
||||
} else if (offlinePlayerHandler.isExcludedPlayer(processor.playerName) &&
|
||||
!config.allowPlayerLookupsForExcludedPlayers()) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.PLAYER_IS_EXCLUDED);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Statistic.Type type = processor.statistic.getType();
|
||||
String statType = enumHandler.getSubStatTypeName(type);
|
||||
|
||||
if (type != Statistic.Type.UNTYPED && processor.subStatName == null) {
|
||||
outputManager.sendFeedbackMsgMissingSubStat(sender, statType);
|
||||
} else {
|
||||
outputManager.sendFeedbackMsgWrongSubStat(sender, statType, processor.subStatName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ArgProcessor {
|
||||
|
||||
private final CommandSender sender;
|
||||
private String[] argsToProcess;
|
||||
|
||||
private Statistic statistic;
|
||||
private String subStatName;
|
||||
private Target target;
|
||||
private String playerName;
|
||||
private StatRequest<?> request;
|
||||
|
||||
private ArgProcessor(CommandSender sender, String[] args) {
|
||||
this.sender = sender;
|
||||
this.argsToProcess = args;
|
||||
|
||||
extractStatistic();
|
||||
extractSubStatistic();
|
||||
extractTarget();
|
||||
combineProcessedArgsIntoRequest();
|
||||
}
|
||||
|
||||
private void combineProcessedArgsIntoRequest() {
|
||||
if (statistic == null ||
|
||||
target == Target.PLAYER && playerName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
RequestGenerator<?> requestGenerator =
|
||||
switch (target) {
|
||||
case PLAYER -> new PlayerStatRequest(sender, playerName);
|
||||
case SERVER -> new ServerStatRequest(sender);
|
||||
case TOP -> new TopStatRequest(sender, config.getTopListMaxSize());
|
||||
};
|
||||
|
||||
switch (statistic.getType()) {
|
||||
case UNTYPED -> request = requestGenerator.untyped(statistic);
|
||||
case BLOCK -> {
|
||||
Material block = enumHandler.getBlockEnum(subStatName);
|
||||
if (block != null) {
|
||||
request = requestGenerator.blockOrItemType(statistic, block);
|
||||
}
|
||||
}
|
||||
case ITEM -> {
|
||||
Material item = enumHandler.getItemEnum(subStatName);
|
||||
if (item != null) {
|
||||
request = requestGenerator.blockOrItemType(statistic, item);
|
||||
}
|
||||
}
|
||||
case ENTITY -> {
|
||||
EntityType entity = enumHandler.getEntityEnum(subStatName);
|
||||
if (entity != null) {
|
||||
request = requestGenerator.entityType(statistic, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void extractTarget() {
|
||||
String targetArg = null;
|
||||
for (String arg : argsToProcess) {
|
||||
Matcher matcher = pattern.matcher(arg);
|
||||
if (matcher.find()) {
|
||||
targetArg = matcher.group();
|
||||
switch (targetArg) {
|
||||
case "me" -> {
|
||||
if (sender instanceof Player) {
|
||||
target = Target.PLAYER;
|
||||
playerName = sender.getName();
|
||||
} else {
|
||||
target = Target.SERVER;
|
||||
}
|
||||
}
|
||||
case "player" -> {
|
||||
target = Target.PLAYER;
|
||||
playerName = tryToFindPlayerName(argsToProcess);
|
||||
}
|
||||
case "server" -> target = Target.SERVER;
|
||||
case "top" -> target = Target.TOP;
|
||||
}
|
||||
argsToProcess = removeArg(targetArg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetArg == null) {
|
||||
String playerName = tryToFindPlayerName(argsToProcess);
|
||||
if (playerName != null) {
|
||||
target = Target.PLAYER;
|
||||
this.playerName = playerName;
|
||||
} else {
|
||||
target = Target.TOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void extractStatistic() {
|
||||
String statName = null;
|
||||
for (String arg : argsToProcess) {
|
||||
if (enumHandler.isStatistic(arg)) {
|
||||
statName = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (statName != null) {
|
||||
statistic = enumHandler.getStatEnum(statName);
|
||||
argsToProcess = removeArg(statName);
|
||||
}
|
||||
}
|
||||
|
||||
private void extractSubStatistic() {
|
||||
if (statistic == null ||
|
||||
statistic.getType() == Statistic.Type.UNTYPED ||
|
||||
argsToProcess.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String subStatName = null;
|
||||
List<String> subStats = Arrays.stream(argsToProcess)
|
||||
.filter(enumHandler::isSubStatEntry)
|
||||
.toList();
|
||||
if (subStats.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
else if (subStats.size() == 1) {
|
||||
subStatName = subStats.get(0);
|
||||
}
|
||||
else {
|
||||
for (String arg : subStats) {
|
||||
if (!arg.equalsIgnoreCase("player")) {
|
||||
subStatName = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (subStatName == null) {
|
||||
subStatName = "player";
|
||||
}
|
||||
}
|
||||
this.subStatName = subStatName;
|
||||
argsToProcess = removeArg(subStatName);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private @Nullable String tryToFindPlayerName(@NotNull String[] args) {
|
||||
for (String arg : args) {
|
||||
if (offlinePlayerHandler.isIncludedPlayer(arg) || offlinePlayerHandler.isExcludedPlayer(arg)) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String[] removeArg(String argToRemove) {
|
||||
ArrayList<String> currentArgs = new ArrayList<>(Arrays.asList(argsToProcess));
|
||||
currentArgs.remove(argToRemove);
|
||||
return currentArgs.toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
package com.artemis.the.gr8.playerstats.core.commands;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.core.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.OfflinePlayerHandler;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private final EnumHandler enumHandler;
|
||||
|
||||
private List<String> statCommandTargets;
|
||||
private List<String> excludeCommandOptions;
|
||||
private List<String> itemsThatCanBreak;
|
||||
private List<String> entitiesThatCanDie;
|
||||
|
||||
public TabCompleter() {
|
||||
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
|
||||
enumHandler = EnumHandler.getInstance();
|
||||
prepareLists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (command.getName().equalsIgnoreCase("statistic")) {
|
||||
return getStatCommandSuggestions(args);
|
||||
}
|
||||
else if (command.getName().equalsIgnoreCase("statisticexclude")) {
|
||||
return getExcludeCommandSuggestions(args);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable List<String> getExcludeCommandSuggestions(@NotNull String[] args) {
|
||||
if (args.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> tabSuggestions = new ArrayList<>();
|
||||
if (args.length == 1) {
|
||||
tabSuggestions = excludeCommandOptions;
|
||||
}
|
||||
else if (args.length == 2) {
|
||||
tabSuggestions = switch (args[0]) {
|
||||
case "add" -> offlinePlayerHandler.getIncludedOfflinePlayerNames();
|
||||
case "remove" -> offlinePlayerHandler.getExcludedPlayerNames();
|
||||
default -> tabSuggestions;
|
||||
};
|
||||
}
|
||||
return getDynamicTabSuggestions(tabSuggestions, args[args.length-1]);
|
||||
}
|
||||
|
||||
private @Nullable List<String> getStatCommandSuggestions(@NotNull String[] args) {
|
||||
if (args.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> tabSuggestions = new ArrayList<>();
|
||||
if (args.length == 1) {
|
||||
tabSuggestions = firstStatCommandArgSuggestions();
|
||||
}
|
||||
else {
|
||||
String previousArg = args[args.length-2];
|
||||
|
||||
//after checking if args[0] is a viable statistic, suggest sub-stat or targets
|
||||
if (enumHandler.isStatistic(previousArg)) {
|
||||
Statistic stat = enumHandler.getStatEnum(previousArg);
|
||||
if (stat != null) {
|
||||
tabSuggestions = suggestionsAfterFirstStatCommandArg(stat);
|
||||
}
|
||||
}
|
||||
else if (previousArg.equalsIgnoreCase("player")) {
|
||||
if (args.length >= 3 && enumHandler.isEntityStatistic(args[args.length-3])) {
|
||||
tabSuggestions = statCommandTargets; //if arg before "player" was entity-sub-stat, suggest targets
|
||||
}
|
||||
else { //otherwise "player" is the target: suggest playerNames
|
||||
tabSuggestions = offlinePlayerHandler.getIncludedOfflinePlayerNames();
|
||||
}
|
||||
}
|
||||
|
||||
//after a substatistic, suggest targets
|
||||
else if (enumHandler.isSubStatEntry(previousArg)) {
|
||||
tabSuggestions = statCommandTargets;
|
||||
}
|
||||
}
|
||||
return getDynamicTabSuggestions(tabSuggestions, args[args.length-1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* These tabSuggestions take into account that the commandSender
|
||||
* will have been typing, so they are filtered for the letters
|
||||
* that have already been typed.
|
||||
*/
|
||||
private List<String> getDynamicTabSuggestions(@NotNull List<String> completeList, String currentArg) {
|
||||
return completeList.stream()
|
||||
.filter(item -> item.toLowerCase(Locale.ENGLISH).contains(currentArg.toLowerCase(Locale.ENGLISH)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private @NotNull List<String> firstStatCommandArgSuggestions() {
|
||||
List<String> suggestions = enumHandler.getAllStatNames();
|
||||
suggestions.add("examples");
|
||||
suggestions.add("info");
|
||||
suggestions.add("help");
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
private List<String> suggestionsAfterFirstStatCommandArg(@NotNull Statistic stat) {
|
||||
switch (stat.getType()) {
|
||||
case BLOCK -> {
|
||||
return enumHandler.getAllBlockNames();
|
||||
}
|
||||
case ITEM -> {
|
||||
if (stat == Statistic.BREAK_ITEM) {
|
||||
return itemsThatCanBreak;
|
||||
} else {
|
||||
return enumHandler.getAllItemNames();
|
||||
}
|
||||
}
|
||||
case ENTITY -> {
|
||||
return entitiesThatCanDie;
|
||||
}
|
||||
default -> {
|
||||
return statCommandTargets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareLists() {
|
||||
statCommandTargets = List.of("top", "player", "server", "me");
|
||||
excludeCommandOptions = List.of("add", "list", "remove", "info");
|
||||
|
||||
//breaking an item means running its durability negative
|
||||
itemsThatCanBreak = Arrays.stream(Material.values())
|
||||
.parallel()
|
||||
.filter(Material::isItem)
|
||||
.filter(item -> item.getMaxDurability() != 0)
|
||||
.map(Material::toString)
|
||||
.map(string -> string.toLowerCase(Locale.ENGLISH))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
//the only statistics dealing with entities are killed_entity and entity_killed_by
|
||||
entitiesThatCanDie = Arrays.stream(EntityType.values())
|
||||
.parallel()
|
||||
.filter(EntityType::isAlive)
|
||||
.map(EntityType::toString)
|
||||
.map(string -> string.toLowerCase(Locale.ENGLISH))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
@ -1,39 +1,54 @@
|
||||
package com.artemis.the.gr8.playerstats.config;
|
||||
package com.artemis.the.gr8.playerstats.core.config;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.FileHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
/** Handles all PlayerStats' config-settings. */
|
||||
public final class ConfigHandler {
|
||||
public final class ConfigHandler extends FileHandler {
|
||||
|
||||
private static Main plugin;
|
||||
private static int configVersion;
|
||||
|
||||
private File configFile;
|
||||
private static volatile ConfigHandler instance;
|
||||
private final int configVersion;
|
||||
private FileConfiguration config;
|
||||
|
||||
public ConfigHandler(Main plugin) {
|
||||
ConfigHandler.plugin = plugin;
|
||||
configVersion = 6;
|
||||
|
||||
saveDefaultConfig();
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
checkConfigVersion();
|
||||
private ConfigHandler() {
|
||||
super("config.yml");
|
||||
config = super.getFileConfiguration();
|
||||
|
||||
configVersion = 7;
|
||||
checkAndUpdateConfigVersion();
|
||||
MyLogger.setDebugLevel(getDebugLevel());
|
||||
}
|
||||
|
||||
public static ConfigHandler getInstance() {
|
||||
ConfigHandler localVar = instance;
|
||||
if (localVar != null) {
|
||||
return localVar;
|
||||
}
|
||||
|
||||
synchronized (ConfigHandler.class) {
|
||||
if (instance == null) {
|
||||
instance = new ConfigHandler();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
super.reload();
|
||||
config = super.getFileConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the number that "config-version" returns to see if the
|
||||
* config needs updating, and if so, send it to the {@link ConfigUpdateHandler}.
|
||||
* config needs updating, and if so, updates it.
|
||||
* <br>
|
||||
* <br>PlayerStats 1.1: "config-version" doesn't exist.
|
||||
* <br>PlayerStats 1.2: "config-version" is 2.
|
||||
@ -42,41 +57,17 @@ public final class ConfigHandler {
|
||||
* <br>PlayerStats 1.5: "config-version" is 5.
|
||||
* <br>PlayerStats 1.6 and up: "config-version" is 6.
|
||||
*/
|
||||
private void checkConfigVersion() {
|
||||
private void checkAndUpdateConfigVersion() {
|
||||
if (!config.contains("config-version") || config.getInt("config-version") != configVersion) {
|
||||
new ConfigUpdateHandler(plugin, configFile, configVersion);
|
||||
reloadConfig();
|
||||
}
|
||||
}
|
||||
DefaultValueGetter defaultValueGetter = new DefaultValueGetter(config);
|
||||
Map<String, Object> defaultValues = defaultValueGetter.getValuesToAdjust();
|
||||
defaultValues.put("config-version", configVersion);
|
||||
|
||||
/**
|
||||
* Create a config file if none exists yet
|
||||
* (from the config.yml in the plugin's resources).
|
||||
*/
|
||||
private void saveDefaultConfig() {
|
||||
config = plugin.getConfig();
|
||||
plugin.saveDefaultConfig();
|
||||
configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||
}
|
||||
super.addValues(defaultValues);
|
||||
reload();
|
||||
|
||||
/**
|
||||
* Reloads the config from file, or creates a new file with default values
|
||||
* if there is none. Also reads the value for debug-level and passes it
|
||||
* on to {@link MyLogger}.
|
||||
*
|
||||
* @return true if the config has been reloaded from disk, false if it failed
|
||||
*/
|
||||
public boolean reloadConfig() {
|
||||
if (!configFile.exists()) {
|
||||
saveDefaultConfig();
|
||||
}
|
||||
try {
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
return true;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
MyLogger.logException(e, "ConfigHandler", "reloadConfig");
|
||||
return false;
|
||||
MyLogger.logLowLevelMsg("Your config has been updated to version " + configVersion +
|
||||
", but all of your custom settings should still be there!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,6 +132,14 @@ public final class ConfigHandler {
|
||||
return config.getInt("number-of-days-since-last-joined", 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to allow the /stat player command for excluded players.
|
||||
* @return the config setting (default: true)
|
||||
*/
|
||||
public boolean allowPlayerLookupsForExcludedPlayers() {
|
||||
return config.getBoolean("allow-player-lookups-for-excluded-players", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use TranslatableComponents wherever possible.
|
||||
*
|
@ -0,0 +1,56 @@
|
||||
package com.artemis.the.gr8.playerstats.core.config;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class DefaultValueGetter {
|
||||
|
||||
private final FileConfiguration config;
|
||||
private final Map<String, Object> defaultValuesToAdjust;
|
||||
|
||||
public DefaultValueGetter(FileConfiguration configuration) {
|
||||
config = configuration;
|
||||
defaultValuesToAdjust = new HashMap<>();
|
||||
}
|
||||
|
||||
public Map<String, Object> getValuesToAdjust() {
|
||||
checkTopListDefault();
|
||||
checkDefaultColors();
|
||||
return defaultValuesToAdjust;
|
||||
}
|
||||
|
||||
private void checkTopListDefault() {
|
||||
String oldTitle = config.getString("top-list-title");
|
||||
if (oldTitle != null && oldTitle.equalsIgnoreCase("Top [x]")) {
|
||||
defaultValuesToAdjust.put("top-list-title", "Top");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts some of the default colors to migrate from versions 2
|
||||
* or 3 to version 4 and above.
|
||||
*/
|
||||
private void checkDefaultColors() {
|
||||
addValueIfNeeded("top-list.title", "yellow", "#FFD52B");
|
||||
addValueIfNeeded("top-list.title", "#FFEA40", "#FFD52B");
|
||||
addValueIfNeeded("top-list.stat-names", "yellow", "#FFD52B");
|
||||
addValueIfNeeded("top-list.stat-names", "#FFEA40", "#FFD52B");
|
||||
addValueIfNeeded("top-list.sub-stat-names", "#FFD52B", "yellow");
|
||||
|
||||
addValueIfNeeded("individual-statistics.stat-names", "yellow", "#FFD52B");
|
||||
addValueIfNeeded("individual-statistics.sub-stat-names", "#FFD52B", "yellow");
|
||||
addValueIfNeeded("total-server.title", "gold", "#55AAFF");
|
||||
addValueIfNeeded("total-server.server-name", "gold", "#55AAFF");
|
||||
addValueIfNeeded("total-server.stat-names", "yellow", "#FFD52B");
|
||||
addValueIfNeeded("total-server.sub-stat-names", "#FFD52B", "yellow");
|
||||
}
|
||||
|
||||
private void addValueIfNeeded(String path, String oldValue, String newValue) {
|
||||
String configString = config.getString(path);
|
||||
if (configString != null && configString.equalsIgnoreCase(oldValue)) {
|
||||
defaultValuesToAdjust.put(path, newValue);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.artemis.the.gr8.playerstats.enums;
|
||||
package com.artemis.the.gr8.playerstats.core.enums;
|
||||
|
||||
/**
|
||||
* Represents the debugging level that PlayerStats can use.
|
@ -0,0 +1,80 @@
|
||||
package com.artemis.the.gr8.playerstats.core.enums;
|
||||
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This enum represents the colorscheme PlayerStats uses in its output messages.
|
||||
* The first set of colors is used throughout the plugin, while the set of NAME-colors
|
||||
* represents the colors that player-names can be in the "shared by player-name"
|
||||
* section of shared statistics
|
||||
*/
|
||||
public enum PluginColor {
|
||||
/**
|
||||
* ChatColor Gray (#AAAAAA)
|
||||
*/
|
||||
GRAY (NamedTextColor.GRAY),
|
||||
|
||||
/**
|
||||
* A Dark Purple that is mainly used for title-underscores (#6E3485).
|
||||
*/
|
||||
DARK_PURPLE (TextColor.fromHexString("#6E3485")),
|
||||
|
||||
/**
|
||||
* A Light Purple that is meant to simulate the color of a clicked link.
|
||||
* Used for the "Hover Here" part of shared statistics (#845EC2)
|
||||
* */
|
||||
LIGHT_PURPLE (TextColor.fromHexString("#845EC2")),
|
||||
|
||||
/**
|
||||
* A Light Blue that is used for the share-button and feedback message accents (#55C6FF).
|
||||
*/
|
||||
LIGHT_BLUE (TextColor.fromHexString("#55C6FF")),
|
||||
|
||||
/**
|
||||
* A very light blue that is used for feedback messages and hover-text (#ADE7FF)
|
||||
*/
|
||||
LIGHTEST_BLUE(TextColor.fromHexString("#ADE7FF")),
|
||||
|
||||
/**
|
||||
* ChatColor Gold (#FFAA00)
|
||||
*/
|
||||
GOLD (NamedTextColor.GOLD),
|
||||
|
||||
/**
|
||||
* A Medium Gold that is used for the example message and for hover-text accents (#FFD52B).
|
||||
*/
|
||||
MEDIUM_GOLD (TextColor.fromHexString("#FFD52B")),
|
||||
|
||||
/**
|
||||
* A Light Gold that is used for the example message and for hover-text accents (#FFEA40).
|
||||
*/
|
||||
LIGHT_GOLD (TextColor.fromHexString("#FFEA40")),
|
||||
|
||||
/**
|
||||
* The color of vanilla Minecraft hearts (#FF1313).
|
||||
*/
|
||||
RED (TextColor.fromHexString("#FF1313"));
|
||||
|
||||
|
||||
private final TextColor color;
|
||||
|
||||
PluginColor(TextColor color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TextColor value belonging to the corresponding enum constant.
|
||||
*/
|
||||
public TextColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest NamedTextColor for the corresponding enum constant.
|
||||
*/
|
||||
public @NotNull TextColor getConsoleColor() {
|
||||
return NamedTextColor.nearestTo(color);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.artemis.the.gr8.playerstats.enums;
|
||||
package com.artemis.the.gr8.playerstats.core.enums;
|
||||
|
||||
/**
|
||||
* All standard messages PlayerStats can send as feedback.
|
||||
@ -8,11 +8,16 @@ package com.artemis.the.gr8.playerstats.enums;
|
||||
public enum StandardMessage {
|
||||
RELOADED_CONFIG,
|
||||
STILL_RELOADING,
|
||||
EXCLUDE_FAILED,
|
||||
INCLUDE_FAILED,
|
||||
MISSING_STAT_NAME,
|
||||
MISSING_PLAYER_NAME,
|
||||
PLAYER_IS_EXCLUDED,
|
||||
WAIT_A_MOMENT,
|
||||
WAIT_A_MINUTE,
|
||||
REQUEST_ALREADY_RUNNING,
|
||||
STILL_ON_SHARE_COOLDOWN,
|
||||
RESULTS_ALREADY_SHARED,
|
||||
STAT_RESULTS_TOO_OLD,
|
||||
UNKNOWN_ERROR,
|
||||
UNKNOWN_ERROR
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.artemis.the.gr8.playerstats.listeners;
|
||||
package com.artemis.the.gr8.playerstats.core.listeners;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.core.multithreading.ThreadManager;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
@ -1,25 +1,21 @@
|
||||
package com.artemis.the.gr8.playerstats.msg;
|
||||
package com.artemis.the.gr8.playerstats.core.msg;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.api.ApiFormatter;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentFactory;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ExampleMessage;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.HelpMessage;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.BukkitConsoleComponentFactory;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.PrideComponentFactory;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.*;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.api.StatTextFormatter;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.components.*;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.msgutils.*;
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Unit;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -38,47 +34,43 @@ import static net.kyori.adventure.text.Component.*;
|
||||
* @see PrideComponentFactory
|
||||
* @see BukkitConsoleComponentFactory
|
||||
*/
|
||||
public final class MessageBuilder implements ApiFormatter {
|
||||
public final class MessageBuilder implements StatTextFormatter {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private boolean useHoverText;
|
||||
private boolean isConsoleBuilder;
|
||||
private final ConfigHandler config;
|
||||
private final boolean useHoverText;
|
||||
|
||||
private final ComponentFactory componentFactory;
|
||||
private final LanguageKeyHandler languageKeyHandler;
|
||||
private final NumberFormatter formatter;
|
||||
private final ComponentSerializer serializer;
|
||||
|
||||
private MessageBuilder(ConfigHandler config) {
|
||||
this (config, new ComponentFactory(config));
|
||||
}
|
||||
|
||||
private MessageBuilder(ConfigHandler configHandler, ComponentFactory factory) {
|
||||
config = configHandler;
|
||||
useHoverText = config.useHoverText();
|
||||
private MessageBuilder(ComponentFactory factory) {
|
||||
config = ConfigHandler.getInstance();
|
||||
languageKeyHandler = LanguageKeyHandler.getInstance();
|
||||
componentFactory = factory;
|
||||
|
||||
if (componentFactory.isConsoleFactory()) {
|
||||
useHoverText = false;
|
||||
} else {
|
||||
useHoverText = config.useHoverText();
|
||||
}
|
||||
formatter = new NumberFormatter();
|
||||
languageKeyHandler = Main.getLanguageKeyHandler();
|
||||
serializer = new ComponentSerializer();
|
||||
}
|
||||
|
||||
public static MessageBuilder defaultBuilder(ConfigHandler config) {
|
||||
return new MessageBuilder(config);
|
||||
@Contract(" -> new")
|
||||
public static @NotNull MessageBuilder defaultBuilder() {
|
||||
return new MessageBuilder(new ComponentFactory());
|
||||
}
|
||||
|
||||
public static MessageBuilder fromComponentFactory(ConfigHandler config, ComponentFactory factory) {
|
||||
return new MessageBuilder(config, factory);
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull MessageBuilder fromComponentFactory(ComponentFactory factory) {
|
||||
return new MessageBuilder(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this {@link MessageBuilder} should use hoverText.
|
||||
* By default, this follows the setting specified in the {@link ConfigHandler}.
|
||||
*/
|
||||
public void toggleHoverUse(boolean desiredSetting) {
|
||||
useHoverText = desiredSetting;
|
||||
}
|
||||
|
||||
public void setConsoleBuilder(boolean isConsoleBuilder) {
|
||||
this.isConsoleBuilder = isConsoleBuilder;
|
||||
@Override
|
||||
public @NotNull String textComponentToString(TextComponent component) {
|
||||
return serializer.getTranslatableComponentSerializer().serialize(component);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -87,9 +79,9 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getRainbowPluginPrefix() {
|
||||
PrideComponentFactory pride = new PrideComponentFactory(config);
|
||||
return pride.rainbowPrefix();
|
||||
public @NotNull TextComponent getRainbowPluginPrefix() {
|
||||
PrideComponentFactory pride = new PrideComponentFactory();
|
||||
return pride.pluginPrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -98,70 +90,81 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getRainbowPluginPrefixAsTitle() {
|
||||
PrideComponentFactory pride = new PrideComponentFactory(config);
|
||||
public @NotNull TextComponent getRainbowPluginPrefixAsTitle() {
|
||||
PrideComponentFactory pride = new PrideComponentFactory();
|
||||
return pride.pluginPrefixAsTitle();
|
||||
}
|
||||
|
||||
public TextComponent reloadedConfig() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content("Config reloaded!"));
|
||||
public @NotNull TextComponent reloadedConfig() {
|
||||
return composePluginMessage("Config reloaded!");
|
||||
}
|
||||
|
||||
public TextComponent stillReloading() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"The plugin is (re)loading, your request will be processed when it is done!"));
|
||||
public @NotNull TextComponent stillReloading() {
|
||||
return composePluginMessage("The plugin is (re)loading, your request will be processed when it is done!");
|
||||
}
|
||||
|
||||
public TextComponent waitAMoment(boolean longWait) {
|
||||
String msg = longWait ? "Calculating statistics, this may take a minute..." :
|
||||
"Calculating statistics, this may take a few moments...";
|
||||
public @NotNull TextComponent excludeSuccess(String playerName) {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(msg));
|
||||
.append(componentFactory.message().content("Excluded ")
|
||||
.append(componentFactory.messageAccent().content(playerName))
|
||||
.append(text("!")));
|
||||
}
|
||||
|
||||
public TextComponent missingStatName() {
|
||||
public @NotNull TextComponent excludeFailed() {
|
||||
return composePluginMessage("This player is already hidden from /stat results!");
|
||||
}
|
||||
|
||||
public @NotNull TextComponent includeSuccess(String playerName) {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please provide a valid statistic name!"));
|
||||
.append(componentFactory.message().content("Removed ")
|
||||
.append(componentFactory.messageAccent().content(playerName))
|
||||
.append(text(" from the exclude-list!")));
|
||||
}
|
||||
|
||||
public TextComponent missingSubStatName(Statistic.Type statType) {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please add a valid " + EnumHandler.getSubStatTypeName(statType) + " to look up this statistic!"));
|
||||
public @NotNull TextComponent includeFailed() {
|
||||
return composePluginMessage("This is not a player that has been excluded with the /statexclude command!");
|
||||
}
|
||||
|
||||
public TextComponent missingPlayerName() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please specify a valid player-name!"));
|
||||
public @NotNull TextComponent waitAMinute() {
|
||||
return composePluginMessage("Calculating statistics, this may take a minute...");
|
||||
}
|
||||
|
||||
public TextComponent wrongSubStatType(Statistic.Type statType, String subStatName) {
|
||||
public @NotNull TextComponent waitAMoment() {
|
||||
return composePluginMessage("Calculating statistics, this may take a few moments...");
|
||||
}
|
||||
|
||||
public @NotNull TextComponent missingStatName() {
|
||||
return composePluginMessage("Please provide a valid statistic name!");
|
||||
}
|
||||
|
||||
public @NotNull TextComponent missingSubStatName(String statType) {
|
||||
return composePluginMessage("Please add a valid " + statType + " to look up this statistic!");
|
||||
}
|
||||
|
||||
public @NotNull TextComponent missingPlayerName() {
|
||||
return composePluginMessage("Please specify a valid player-name!");
|
||||
}
|
||||
|
||||
public @NotNull TextComponent playerIsExcluded() {
|
||||
return composePluginMessage("This player is excluded from /stat results!");
|
||||
}
|
||||
|
||||
public @NotNull TextComponent wrongSubStatType(String statType, String subStatName) {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.messageAccent().content("\"" + subStatName + "\""))
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"is not a valid " + EnumHandler.getSubStatTypeName(statType) + "!"));
|
||||
"is not a valid " + statType + "!"));
|
||||
}
|
||||
|
||||
public TextComponent requestAlreadyRunning() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please wait for your previous lookup to finish!"));
|
||||
public @NotNull TextComponent requestAlreadyRunning() {
|
||||
return composePluginMessage("Please wait for your previous lookup to finish!");
|
||||
}
|
||||
|
||||
public TextComponent stillOnShareCoolDown() {
|
||||
public @NotNull TextComponent stillOnShareCoolDown() {
|
||||
int waitTime = config.getStatShareWaitingTime();
|
||||
String minutes = waitTime == 1 ? " minute" : " minutes";
|
||||
|
||||
@ -175,68 +178,88 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
.append(text("between sharing!")));
|
||||
}
|
||||
|
||||
public TextComponent resultsAlreadyShared() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content("You already shared these results!"));
|
||||
public @NotNull TextComponent resultsAlreadyShared() {
|
||||
return composePluginMessage("You already shared these results!");
|
||||
}
|
||||
|
||||
public TextComponent statResultsTooOld() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"It has been too long since you looked up this statistic, please repeat the original command!"));
|
||||
public @NotNull TextComponent statResultsTooOld() {
|
||||
return composePluginMessage("It has been too long since you looked up " +
|
||||
"this statistic, please repeat the original command!");
|
||||
}
|
||||
|
||||
public TextComponent unknownError() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Something went wrong with your request, " +
|
||||
"please try again or see /statistic for a usage explanation!"));
|
||||
public @NotNull TextComponent unknownError() {
|
||||
return composePluginMessage("Something went wrong with your request, " +
|
||||
"please try again or see /statistic for a usage explanation!");
|
||||
}
|
||||
|
||||
public TextComponent usageExamples() {
|
||||
private @NotNull TextComponent composePluginMessage(String content) {
|
||||
return getPluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(content));
|
||||
}
|
||||
|
||||
@Contract(" -> new")
|
||||
public @NotNull TextComponent usageExamples() {
|
||||
return ExampleMessage.construct(componentFactory);
|
||||
}
|
||||
|
||||
public TextComponent helpMsg() {
|
||||
int listSize = config.getTopListMaxSize();
|
||||
if (!isConsoleBuilder && useHoverText) {
|
||||
if (useHoverText) {
|
||||
return HelpMessage.constructHoverMsg(componentFactory, listSize);
|
||||
} else {
|
||||
return HelpMessage.constructPlainMsg(componentFactory, listSize);
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull TextComponent excludeInfoMsg() {
|
||||
return ExcludeInfoMessage.construct(componentFactory);
|
||||
}
|
||||
|
||||
public @NotNull TextComponent excludedList(@NotNull ArrayList<String> excludedPlayerNames) {
|
||||
TextComponent.Builder excludedList = text()
|
||||
.append(newline())
|
||||
.append(getPluginPrefixAsTitle()
|
||||
.append(newline())
|
||||
.append(componentFactory.subTitle("All players that are currently excluded: ")));
|
||||
|
||||
excludedPlayerNames.forEach(playerName -> excludedList
|
||||
.append(newline())
|
||||
.append(componentFactory.arrow()
|
||||
.append(space())
|
||||
.append(componentFactory.infoMessageAccent().content(playerName))));
|
||||
|
||||
return excludedList.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getStatTitle(Statistic statistic, @Nullable String subStatName) {
|
||||
public @NotNull TextComponent getStatTitle(Statistic statistic, @Nullable String subStatName) {
|
||||
return getTopStatTitleComponent(0, statistic, subStatName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getStatTitle(Statistic statistic, Unit unit) {
|
||||
public @NotNull TextComponent getStatTitle(Statistic statistic, Unit unit) {
|
||||
return getTopStatTitleComponent(0, statistic, null, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getTopStatTitle(int topListSize, Statistic statistic, @Nullable String subStatName) {
|
||||
public @NotNull TextComponent getTopStatTitle(int topListSize, Statistic statistic, @Nullable String subStatName) {
|
||||
return getTopStatTitleComponent(topListSize, statistic, subStatName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getTopStatTitle(int topStatSize, Statistic statistic, Unit unit) {
|
||||
public @NotNull TextComponent getTopStatTitle(int topStatSize, Statistic statistic, Unit unit) {
|
||||
return getTopStatTitleComponent(topStatSize, statistic, null, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatTopStatLine(int positionInTopList, String playerName, long statNumber, Statistic statistic) {
|
||||
public @NotNull TextComponent formatTopStatLine(int positionInTopList, String playerName, long statNumber, Statistic statistic) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.TOP, statistic);
|
||||
return getTopStatLineComponent(positionInTopList, playerName, statNumberComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatTopStatLine(int positionInTopList, String playerName, long statNumber, Unit unit) {
|
||||
public @NotNull TextComponent formatTopStatLine(int positionInTopList, String playerName, long statNumber, Unit unit) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.TOP, unit);
|
||||
return getTopStatLineComponent(positionInTopList, playerName, statNumberComponent);
|
||||
}
|
||||
@ -245,55 +268,55 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
* Time-number does not hover
|
||||
*/
|
||||
@Override
|
||||
public TextComponent formatTopStatLineForTypeTime(int positionInTopList, String playerName, long statNumber, Unit bigUnit, Unit smallUnit) {
|
||||
public @NotNull TextComponent formatTopStatLineForTypeTime(int positionInTopList, String playerName, long statNumber, Unit bigUnit, Unit smallUnit) {
|
||||
TextComponent statNumberComponent = getBasicTimeNumberComponent(statNumber, Target.TOP, bigUnit, smallUnit);
|
||||
return getTopStatLineComponent(positionInTopList, playerName, statNumberComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStat(long statNumber, Statistic statistic) {
|
||||
public @NotNull TextComponent formatServerStat(long statNumber, Statistic statistic) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.SERVER, statistic);
|
||||
return getServerStatComponent(statNumberComponent, statistic, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStat(long statNumber, Statistic statistic, String subStatName) {
|
||||
public @NotNull TextComponent formatServerStat(long statNumber, Statistic statistic, String subStatName) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.SERVER, statistic);
|
||||
return getServerStatComponent(statNumberComponent, statistic, subStatName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStat(long statNumber, Statistic statistic, Unit unit) {
|
||||
public @NotNull TextComponent formatServerStat(long statNumber, Statistic statistic, Unit unit) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.SERVER, unit);
|
||||
return getServerStatComponent(statNumberComponent, statistic, null, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStatForTypeTime(long statNumber, Statistic statistic, Unit bigUnit, Unit smallUnit) {
|
||||
public @NotNull TextComponent formatServerStatForTypeTime(long statNumber, Statistic statistic, Unit bigUnit, Unit smallUnit) {
|
||||
TextComponent statNumberComponent = getBasicTimeNumberComponent(statNumber, Target.SERVER, bigUnit, smallUnit);
|
||||
return getServerStatComponent(statNumberComponent, statistic, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic) {
|
||||
public @NotNull TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.PLAYER, statistic);
|
||||
return getPlayerStatComponent(playerName, statNumberComponent, statistic, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic, Unit unit) {
|
||||
public @NotNull TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic, Unit unit) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.PLAYER, unit);
|
||||
return getPlayerStatComponent(playerName, statNumberComponent, statistic, null, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic, String subStatName) {
|
||||
public @NotNull TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic, String subStatName) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.PLAYER, statistic);
|
||||
return getPlayerStatComponent(playerName, statNumberComponent, statistic, subStatName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatPlayerStatForTypeTime(String playerName, int statNumber, Statistic statistic, Unit bigUnit, Unit smallUnit) {
|
||||
public @NotNull TextComponent formatPlayerStatForTypeTime(String playerName, int statNumber, Statistic statistic, Unit bigUnit, Unit smallUnit) {
|
||||
TextComponent statNumberComponent = getBasicTimeNumberComponent(statNumber, Target.PLAYER, bigUnit, smallUnit);
|
||||
return getPlayerStatComponent(playerName, statNumberComponent, statistic, null, null);
|
||||
}
|
||||
@ -309,7 +332,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
* <br>- If both parameters are null, the formattedComponent will be returned
|
||||
* as is.
|
||||
*/
|
||||
public BiFunction<Integer, CommandSender, TextComponent> formattedPlayerStatFunction(int stat, @NotNull RequestSettings request) {
|
||||
public @NotNull FormattingFunction formattedPlayerStatFunction(int stat, @NotNull StatRequest.Settings request) {
|
||||
TextComponent playerStat = formatPlayerStat(request.getPlayerName(), stat, request.getStatistic(), request.getSubStatEntryName());
|
||||
return getFormattingFunction(playerStat, Target.PLAYER);
|
||||
}
|
||||
@ -325,7 +348,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
* <br>- If both parameters are null, the formattedComponent will be returned
|
||||
* as is.
|
||||
*/
|
||||
public BiFunction<Integer, CommandSender, TextComponent> formattedServerStatFunction(long stat, @NotNull RequestSettings request) {
|
||||
public @NotNull FormattingFunction formattedServerStatFunction(long stat, @NotNull StatRequest.Settings request) {
|
||||
TextComponent serverStat = formatServerStat(stat, request.getStatistic(), request.getSubStatEntryName());
|
||||
return getFormattingFunction(serverStat, Target.SERVER);
|
||||
}
|
||||
@ -341,13 +364,13 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
* <br>- If both parameters are null, the formattedComponent will be returned
|
||||
* as is.
|
||||
*/
|
||||
public BiFunction<Integer, CommandSender, TextComponent> formattedTopStatFunction(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull RequestSettings request) {
|
||||
public @NotNull FormattingFunction formattedTopStatFunction(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull StatRequest.Settings request) {
|
||||
final TextComponent title = getTopStatTitle(topStats.size(), request.getStatistic(), request.getSubStatEntryName());
|
||||
final TextComponent list = getTopStatListComponent(topStats, request.getStatistic());
|
||||
final boolean useEnters = config.useEnters(Target.TOP, false);
|
||||
final boolean useEntersForShared = config.useEnters(Target.TOP, true);
|
||||
|
||||
return (shareCode, sender) -> {
|
||||
BiFunction<Integer, CommandSender, TextComponent> biFunction = (shareCode, sender) -> {
|
||||
TextComponent.Builder topBuilder = text();
|
||||
|
||||
//if we're adding a share-button
|
||||
@ -391,9 +414,10 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
}
|
||||
return topBuilder.build();
|
||||
};
|
||||
return new FormattingFunction(biFunction);
|
||||
}
|
||||
|
||||
private TextComponent getPlayerStatComponent(String playerName, TextComponent statNumberComponent, Statistic statistic, @Nullable String subStatName, @Nullable Unit unit) {
|
||||
private @NotNull TextComponent getPlayerStatComponent(String playerName, TextComponent statNumberComponent, Statistic statistic, @Nullable String subStatName, @Nullable Unit unit) {
|
||||
TextComponent statUnit = (unit == null) ?
|
||||
getStatUnitComponent(statistic, Target.PLAYER) :
|
||||
getStatUnitComponent(unit, Target.PLAYER);
|
||||
@ -409,7 +433,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent getServerStatComponent(TextComponent statNumber, Statistic statistic, @Nullable String subStatName, @Nullable Unit unit) {
|
||||
private @NotNull TextComponent getServerStatComponent(TextComponent statNumber, Statistic statistic, @Nullable String subStatName, @Nullable Unit unit) {
|
||||
String serverTitle = config.getServerTitle();
|
||||
String serverName = config.getServerName();
|
||||
TextComponent statUnit = (unit == null) ?
|
||||
@ -428,7 +452,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent getTopStatTitleComponent(int topListSize, Statistic statistic, @Nullable String subStatName, @Nullable Unit unit) {
|
||||
private @NotNull TextComponent getTopStatTitleComponent(int topListSize, Statistic statistic, @Nullable String subStatName, @Nullable Unit unit) {
|
||||
TextComponent statUnit = (unit == null) ?
|
||||
getStatUnitComponent(statistic, Target.TOP) :
|
||||
getStatUnitComponent(unit, Target.TOP);
|
||||
@ -450,7 +474,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
private TextComponent getTopStatListComponent(LinkedHashMap<String, Integer> topStats, Statistic statistic) {
|
||||
private @NotNull TextComponent getTopStatListComponent(@NotNull LinkedHashMap<String, Integer> topStats, Statistic statistic) {
|
||||
TextComponent.Builder topList = Component.text();
|
||||
Set<String> playerNames = topStats.keySet();
|
||||
boolean useDots = config.useDots();
|
||||
@ -472,7 +496,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
return topList.build();
|
||||
}
|
||||
|
||||
private TextComponent getTopStatLineComponent(int positionInTopList, String playerName, TextComponent statNumberComponent) {
|
||||
private @NotNull TextComponent getTopStatLineComponent(int positionInTopList, String playerName, TextComponent statNumberComponent) {
|
||||
boolean useDots = config.useDots();
|
||||
String fullPlayerName = useDots ? playerName : playerName + ":";
|
||||
|
||||
@ -498,26 +522,29 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
}
|
||||
|
||||
private TextComponent getStatAndSubStatNameComponent(Statistic statistic, @Nullable String subStatName, Target target) {
|
||||
if (config.useTranslatableComponents()) {
|
||||
EnumHandler enumHandler = EnumHandler.getInstance();
|
||||
|
||||
String statKey = languageKeyHandler.getStatKey(statistic);
|
||||
String subStatKey = switch (statistic.getType()) {
|
||||
case UNTYPED -> null;
|
||||
case ENTITY -> languageKeyHandler.getEntityKey(EnumHandler.getEntityEnum(subStatName));
|
||||
case BLOCK -> languageKeyHandler.getBlockKey(EnumHandler.getBlockEnum(subStatName));
|
||||
case ITEM -> languageKeyHandler.getItemKey(EnumHandler.getItemEnum(subStatName));
|
||||
case ENTITY -> languageKeyHandler.getEntityKey(enumHandler.getEntityEnum(subStatName));
|
||||
case BLOCK -> languageKeyHandler.getBlockKey(enumHandler.getBlockEnum(subStatName));
|
||||
case ITEM -> languageKeyHandler.getItemKey(enumHandler.getItemEnum(subStatName));
|
||||
};
|
||||
if (subStatKey == null) {
|
||||
subStatKey = StringUtils.prettify(subStatName);
|
||||
}
|
||||
|
||||
if (config.useTranslatableComponents()) {
|
||||
return componentFactory.statAndSubStatNameTranslatable(statKey, subStatKey, target);
|
||||
}
|
||||
|
||||
String prettyStatName = StringUtils.prettify(statistic.toString());
|
||||
String prettySubStatName = StringUtils.prettify(subStatName);
|
||||
String prettyStatName = languageKeyHandler.convertLanguageKeyToDisplayName(statKey);
|
||||
String prettySubStatName = languageKeyHandler.convertLanguageKeyToDisplayName(subStatKey);
|
||||
return componentFactory.statAndSubStatName(prettyStatName, prettySubStatName, target);
|
||||
}
|
||||
|
||||
private TextComponent getStatNumberComponent(long statNumber, Target target, Unit unit) {
|
||||
private TextComponent getStatNumberComponent(long statNumber, Target target, @NotNull Unit unit) {
|
||||
return switch (unit.getType()) {
|
||||
case TIME -> getBasicTimeNumberComponent(statNumber, target, unit, null);
|
||||
case DAMAGE -> getDamageNumberComponent(statNumber, target, unit);
|
||||
@ -581,7 +608,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
ArrayList<Unit> unitRange = getTimeUnitRange(statNumber);
|
||||
if (unitRange.size() <= 1 || (useHoverText && unitRange.size() <= 3)) {
|
||||
MyLogger.logWarning("There is something wrong with the time-units you specified, please check your config!");
|
||||
return componentFactory.timeNumber(formatter.formatNumber(statNumber), target);
|
||||
return componentFactory.timeNumber(formatter.formatDefaultNumber(statNumber), target);
|
||||
}
|
||||
else {
|
||||
String mainNumber = formatter.formatTimeNumber(statNumber, unitRange.get(0), unitRange.get(1));
|
||||
@ -603,7 +630,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
}
|
||||
|
||||
private TextComponent getDefaultNumberComponent(long statNumber, Target target) {
|
||||
return componentFactory.statNumber(formatter.formatNumber(statNumber), target);
|
||||
return componentFactory.statNumber(formatter.formatDefaultNumber(statNumber), target);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -618,7 +645,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
return getStatUnitComponent(unit, target);
|
||||
}
|
||||
|
||||
private TextComponent getStatUnitComponent(Unit unit, Target target) {
|
||||
private TextComponent getStatUnitComponent(@NotNull Unit unit, Target target) {
|
||||
return switch (unit.getType()) {
|
||||
case DAMAGE -> getDamageUnitComponent(unit, target);
|
||||
case DISTANCE -> getDistanceUnitComponent(unit, target);
|
||||
@ -629,7 +656,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
/**
|
||||
* Provides its own space in front of it!
|
||||
*/
|
||||
private TextComponent getDistanceUnitComponent(Unit unit, Target target) {
|
||||
private @NotNull TextComponent getDistanceUnitComponent(Unit unit, Target target) {
|
||||
if (config.useTranslatableComponents()) {
|
||||
String unitKey = languageKeyHandler.getUnitKey(unit);
|
||||
if (unitKey != null) {
|
||||
@ -644,18 +671,12 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
/**
|
||||
* Provides its own space in front of it!
|
||||
*/
|
||||
private TextComponent getDamageUnitComponent(Unit unit, Target target) {
|
||||
private @NotNull TextComponent getDamageUnitComponent(Unit unit, Target target) {
|
||||
if (unit == Unit.HEART) {
|
||||
TextComponent heartUnit;
|
||||
if (isConsoleBuilder) {
|
||||
heartUnit = componentFactory.consoleHeart();
|
||||
} else if (useHoverText) {
|
||||
heartUnit = componentFactory.clientHeartWithHoverText();
|
||||
} else {
|
||||
heartUnit = componentFactory.clientHeart(false);
|
||||
}
|
||||
return Component.space()
|
||||
.append(heartUnit);
|
||||
TextComponent heartUnit = useHoverText ?
|
||||
componentFactory.heartBetweenBracketsWithHoverText() :
|
||||
componentFactory.heartBetweenBrackets();
|
||||
return Component.space().append(heartUnit);
|
||||
}
|
||||
return Component.space()
|
||||
.append(componentFactory.statUnit(unit.getLabel(), target));
|
||||
@ -671,11 +692,11 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
return componentFactory.sharerName(sender.getName());
|
||||
}
|
||||
|
||||
private BiFunction<Integer, CommandSender, TextComponent> getFormattingFunction(@NotNull TextComponent statResult, Target target) {
|
||||
private @NotNull FormattingFunction getFormattingFunction(@NotNull TextComponent statResult, Target target) {
|
||||
boolean useEnters = config.useEnters(target, false);
|
||||
boolean useEntersForShared = config.useEnters(target, true);
|
||||
|
||||
return (shareCode, sender) -> {
|
||||
BiFunction<Integer, CommandSender, TextComponent> biFunction = (shareCode, sender) -> {
|
||||
TextComponent.Builder statBuilder = text();
|
||||
|
||||
//if we're adding a share-button
|
||||
@ -706,10 +727,11 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
}
|
||||
return statBuilder.build();
|
||||
};
|
||||
return new FormattingFunction(biFunction);
|
||||
}
|
||||
|
||||
private int getNumberOfDotsToAlign(String displayText) {
|
||||
if (isConsoleBuilder) {
|
||||
if (componentFactory.isConsoleFactory()) {
|
||||
return FontUtils.getNumberOfDotsToAlignForConsole(displayText);
|
||||
} else if (config.playerNameIsBold()) {
|
||||
return FontUtils.getNumberOfDotsToAlignForBoldText(displayText);
|
||||
@ -725,7 +747,7 @@ public final class MessageBuilder implements ApiFormatter {
|
||||
* <p>2. maxHoverUnit</p>
|
||||
* <p>3. minHoverUnit</p>
|
||||
*/
|
||||
private ArrayList<Unit> getTimeUnitRange(long statNumber) {
|
||||
private @NotNull ArrayList<Unit> getTimeUnitRange(long statNumber) {
|
||||
ArrayList<Unit> unitRange = new ArrayList<>();
|
||||
if (!config.autoDetectTimeUnit(false)) {
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(false)));
|
@ -0,0 +1,220 @@
|
||||
package com.artemis.the.gr8.playerstats.core.msg;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.StatTextFormatter;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.components.*;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.msgutils.FormattingFunction;
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.artemis.the.gr8.playerstats.core.enums.StandardMessage.*;
|
||||
|
||||
/**
|
||||
* This class manages all PlayerStats output. It is the only
|
||||
* place where messages are sent. It gets its messages from a
|
||||
* {@link MessageBuilder} configured for either a Console or
|
||||
* for Players (mainly to deal with the lack of hover-text,
|
||||
* and for Bukkit consoles to make up for the lack of hex-colors).
|
||||
*/
|
||||
public final class OutputManager {
|
||||
|
||||
private static BukkitAudiences adventure;
|
||||
private static EnumMap<StandardMessage, Function<MessageBuilder, TextComponent>> standardMessages;
|
||||
|
||||
private final ConfigHandler config;
|
||||
private MessageBuilder messageBuilder;
|
||||
private MessageBuilder consoleMessageBuilder;
|
||||
|
||||
public OutputManager(BukkitAudiences adventure) {
|
||||
OutputManager.adventure = adventure;
|
||||
config = ConfigHandler.getInstance();
|
||||
|
||||
getMessageBuilders();
|
||||
prepareFunctions();
|
||||
}
|
||||
|
||||
public void updateSettings() {
|
||||
getMessageBuilders();
|
||||
}
|
||||
|
||||
public StatTextFormatter getMainMessageBuilder() {
|
||||
return messageBuilder;
|
||||
}
|
||||
|
||||
public @NotNull String textComponentToString(TextComponent component) {
|
||||
return messageBuilder.textComponentToString(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a TextComponent with the following parts:
|
||||
* <br>[player-name]: [number] [stat-name] {sub-stat-name}
|
||||
*/
|
||||
public @NotNull FormattingFunction formatPlayerStat(@NotNull StatRequest.Settings requestSettings, int playerStat) {
|
||||
return getMessageBuilder(requestSettings.getCommandSender())
|
||||
.formattedPlayerStatFunction(playerStat, requestSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a TextComponent with the following parts:
|
||||
* <br>[Total on] [server-name]: [number] [stat-name] [sub-stat-name]
|
||||
*/
|
||||
public @NotNull FormattingFunction formatServerStat(@NotNull StatRequest.Settings requestSettings, long serverStat) {
|
||||
return getMessageBuilder(requestSettings.getCommandSender())
|
||||
.formattedServerStatFunction(serverStat, requestSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a TextComponent with the following parts:
|
||||
* <br>[PlayerStats] [Top 10] [stat-name] [sub-stat-name]
|
||||
* <br> [1.] [player-name] [number]
|
||||
* <br> [2.] [player-name] [number]
|
||||
* <br> [3.] etc...
|
||||
*/
|
||||
public @NotNull FormattingFunction formatTopStats(@NotNull StatRequest.Settings requestSettings, @NotNull LinkedHashMap<String, Integer> topStats) {
|
||||
return getMessageBuilder(requestSettings.getCommandSender())
|
||||
.formattedTopStatFunction(topStats, requestSettings);
|
||||
}
|
||||
|
||||
public void sendFeedbackMsg(@NotNull CommandSender sender, StandardMessage message) {
|
||||
if (message != null) {
|
||||
adventure.sender(sender).sendMessage(standardMessages.get(message)
|
||||
.apply(getMessageBuilder(sender)));
|
||||
}
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgPlayerExcluded(@NotNull CommandSender sender, String playerName) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.excludeSuccess(playerName));
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgPlayerIncluded(@NotNull CommandSender sender, String playerName) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.includeSuccess(playerName));
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgMissingSubStat(@NotNull CommandSender sender, String statType) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.missingSubStatName(statType));
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgWrongSubStat(@NotNull CommandSender sender, String statType, @Nullable String subStatName) {
|
||||
if (subStatName == null) {
|
||||
sendFeedbackMsgMissingSubStat(sender, statType);
|
||||
} else {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.wrongSubStatType(statType, subStatName));
|
||||
}
|
||||
}
|
||||
|
||||
public void sendExamples(@NotNull CommandSender sender) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.usageExamples());
|
||||
}
|
||||
|
||||
public void sendHelp(@NotNull CommandSender sender) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.helpMsg());
|
||||
}
|
||||
|
||||
public void sendExcludeInfo(@NotNull CommandSender sender) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.excludeInfoMsg());
|
||||
}
|
||||
|
||||
public void sendExcludedList(@NotNull CommandSender sender, ArrayList<String> excludedPlayerNames) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.excludedList(excludedPlayerNames));
|
||||
}
|
||||
|
||||
public void sendToAllPlayers(@NotNull TextComponent component) {
|
||||
adventure.players().sendMessage(component);
|
||||
}
|
||||
|
||||
public void sendToCommandSender(@NotNull CommandSender sender, @NotNull TextComponent component) {
|
||||
adventure.sender(sender).sendMessage(component);
|
||||
}
|
||||
|
||||
private MessageBuilder getMessageBuilder(CommandSender sender) {
|
||||
return sender instanceof ConsoleCommandSender ? consoleMessageBuilder : messageBuilder;
|
||||
}
|
||||
|
||||
private void getMessageBuilders() {
|
||||
messageBuilder = getClientMessageBuilder();
|
||||
consoleMessageBuilder = getConsoleMessageBuilder();
|
||||
}
|
||||
|
||||
private MessageBuilder getClientMessageBuilder() {
|
||||
ComponentFactory festiveFactory = getFestiveFactory();
|
||||
if (festiveFactory == null) {
|
||||
return MessageBuilder.defaultBuilder();
|
||||
}
|
||||
return MessageBuilder.fromComponentFactory(festiveFactory);
|
||||
}
|
||||
|
||||
private @NotNull MessageBuilder getConsoleMessageBuilder() {
|
||||
MessageBuilder consoleBuilder;
|
||||
if (isBukkit()) {
|
||||
consoleBuilder = MessageBuilder.fromComponentFactory(new BukkitConsoleComponentFactory());
|
||||
} else {
|
||||
consoleBuilder = MessageBuilder.fromComponentFactory(new ConsoleComponentFactory());
|
||||
}
|
||||
return consoleBuilder;
|
||||
}
|
||||
|
||||
private @Nullable ComponentFactory getFestiveFactory() {
|
||||
if (config.useRainbowMode()) {
|
||||
return new PrideComponentFactory();
|
||||
}
|
||||
else if (config.useFestiveFormatting()) {
|
||||
return switch (LocalDate.now().getMonth()) {
|
||||
case JUNE -> new PrideComponentFactory();
|
||||
case OCTOBER -> new HalloweenComponentFactory();
|
||||
case SEPTEMBER -> {
|
||||
if (LocalDate.now().getDayOfMonth() == 12) {
|
||||
yield new BirthdayComponentFactory();
|
||||
}
|
||||
yield null;
|
||||
}
|
||||
case DECEMBER -> new WinterComponentFactory();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isBukkit() {
|
||||
return Bukkit.getName().equalsIgnoreCase("CraftBukkit");
|
||||
}
|
||||
|
||||
private void prepareFunctions() {
|
||||
standardMessages = new EnumMap<>(StandardMessage.class);
|
||||
|
||||
standardMessages.put(RELOADED_CONFIG, MessageBuilder::reloadedConfig);
|
||||
standardMessages.put(STILL_RELOADING, MessageBuilder::stillReloading);
|
||||
standardMessages.put(EXCLUDE_FAILED, MessageBuilder::excludeFailed);
|
||||
standardMessages.put(INCLUDE_FAILED, MessageBuilder::includeFailed);
|
||||
standardMessages.put(MISSING_STAT_NAME, MessageBuilder::missingStatName);
|
||||
standardMessages.put(MISSING_PLAYER_NAME, MessageBuilder::missingPlayerName);
|
||||
standardMessages.put(PLAYER_IS_EXCLUDED, MessageBuilder::playerIsExcluded);
|
||||
standardMessages.put(WAIT_A_MOMENT, MessageBuilder::waitAMoment);
|
||||
standardMessages.put(WAIT_A_MINUTE, MessageBuilder::waitAMinute);
|
||||
standardMessages.put(REQUEST_ALREADY_RUNNING, MessageBuilder::requestAlreadyRunning);
|
||||
standardMessages.put(STILL_ON_SHARE_COOLDOWN, MessageBuilder::stillOnShareCoolDown);
|
||||
standardMessages.put(RESULTS_ALREADY_SHARED, MessageBuilder::resultsAlreadyShared);
|
||||
standardMessages.put(STAT_RESULTS_TOO_OLD, MessageBuilder::statResultsTooOld);
|
||||
standardMessages.put(UNKNOWN_ERROR, MessageBuilder::unknownError);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public final class BirthdayComponentFactory extends ComponentFactory {
|
||||
|
||||
public BirthdayComponentFactory() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent pluginPrefixAsTitle() {
|
||||
return miniMessageToComponent(
|
||||
"<gradient:#a405e3:#f74040:#f73b3b:#ff9300:#f74040:#a405e3>" +
|
||||
"<#FF9300>\ud83d\udd25</#FF9300> __________ [PlayerStats] __________ " +
|
||||
"<#FF9300>\ud83d\udd25</#FF9300></gradient>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent pluginPrefix() {
|
||||
return miniMessageToComponent(
|
||||
"<gradient:#a405e3:#f74040:#ff9300>[PlayerStats]</gradient>");
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.enums.PluginColor;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
@ -16,10 +15,10 @@ import static net.kyori.adventure.text.Component.text;
|
||||
* a Bukkit Console. Bukkit consoles don't support hex colors,
|
||||
* unlike Paper consoles.
|
||||
*/
|
||||
public class BukkitConsoleComponentFactory extends ComponentFactory {
|
||||
public final class BukkitConsoleComponentFactory extends ComponentFactory {
|
||||
|
||||
public BukkitConsoleComponentFactory(ConfigHandler config) {
|
||||
super(config);
|
||||
public BukkitConsoleComponentFactory() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -29,21 +28,44 @@ public class BukkitConsoleComponentFactory extends ComponentFactory {
|
||||
UNDERSCORE = PluginColor.DARK_PURPLE.getConsoleColor();
|
||||
HEARTS = PluginColor.RED.getConsoleColor();
|
||||
|
||||
MSG_MAIN = PluginColor.MEDIUM_BLUE.getConsoleColor();
|
||||
MSG_ACCENT = PluginColor.BLUE.getConsoleColor();
|
||||
FEEDBACK_MSG = PluginColor.LIGHTEST_BLUE.getConsoleColor();
|
||||
FEEDBACK_MSG_ACCENT = PluginColor.LIGHT_BLUE.getConsoleColor();
|
||||
|
||||
MSG_MAIN_2 = PluginColor.GOLD.getConsoleColor();
|
||||
MSG_ACCENT_2A = PluginColor.MEDIUM_GOLD.getConsoleColor();
|
||||
MSG_ACCENT_2B = PluginColor.LIGHT_YELLOW.getConsoleColor();
|
||||
INFO_MSG = PluginColor.GOLD.getConsoleColor();
|
||||
INFO_MSG_ACCENT_DARKEST = PluginColor.MEDIUM_GOLD.getConsoleColor();
|
||||
INFO_MSG_ACCENT_MEDIUM = PluginColor.LIGHT_GOLD.getConsoleColor();
|
||||
INFO_MSG_ACCENT_LIGHTEST = PluginColor.LIGHTEST_BLUE.getConsoleColor();
|
||||
|
||||
MSG_HOVER = PluginColor.LIGHT_BLUE.getConsoleColor();
|
||||
MSG_HOVER = PluginColor.LIGHTEST_BLUE.getConsoleColor();
|
||||
MSG_CLICKED = PluginColor.LIGHT_PURPLE.getConsoleColor();
|
||||
MSG_HOVER_ACCENT = PluginColor.LIGHT_GOLD.getConsoleColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextColor getSharerNameColor() {
|
||||
return PluginColor.NAME_5.getConsoleColor();
|
||||
public boolean isConsoleFactory() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent heart() {
|
||||
return text()
|
||||
.content(String.valueOf('\u2665'))
|
||||
.color(HEARTS)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent arrow() {
|
||||
return text(" ->").color(INFO_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent bulletPoint() {
|
||||
return text(" *").color(INFO_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent bulletPointIndented() {
|
||||
return text(" *").color(INFO_MSG);
|
||||
}
|
||||
|
||||
@Override
|
@ -1,11 +1,11 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.msg.MessageBuilder;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.enums.PluginColor;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.msgutils.LanguageKeyHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.MessageBuilder;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
@ -14,9 +14,11 @@ 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.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.util.HSVLike;
|
||||
import net.kyori.adventure.util.Index;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -39,20 +41,20 @@ public class ComponentFactory {
|
||||
protected TextColor UNDERSCORE; //dark_purple
|
||||
protected TextColor HEARTS; //red
|
||||
|
||||
protected TextColor MSG_MAIN; //medium_blue
|
||||
protected TextColor MSG_ACCENT; //blue
|
||||
protected TextColor FEEDBACK_MSG; //lightest_blue
|
||||
protected TextColor FEEDBACK_MSG_ACCENT; //light_blue
|
||||
|
||||
protected TextColor MSG_MAIN_2; //gold
|
||||
protected TextColor MSG_ACCENT_2A; //medium_gold
|
||||
protected TextColor MSG_ACCENT_2B; //light_yellow
|
||||
protected TextColor INFO_MSG; //gold
|
||||
protected TextColor INFO_MSG_ACCENT_DARKEST; //medium_gold
|
||||
protected TextColor INFO_MSG_ACCENT_MEDIUM; //light_gold
|
||||
protected TextColor INFO_MSG_ACCENT_LIGHTEST; //lightest_blue
|
||||
|
||||
protected TextColor MSG_HOVER; //light_blue
|
||||
protected TextColor MSG_HOVER; //lightest_blue
|
||||
protected TextColor MSG_CLICKED; //light_purple
|
||||
protected TextColor MSG_HOVER_ACCENT; //light_gold
|
||||
|
||||
|
||||
public ComponentFactory(ConfigHandler c) {
|
||||
config = c;
|
||||
public ComponentFactory() {
|
||||
config = ConfigHandler.getInstance();
|
||||
prepareColors();
|
||||
}
|
||||
|
||||
@ -62,23 +64,31 @@ public class ComponentFactory {
|
||||
UNDERSCORE = PluginColor.DARK_PURPLE.getColor();
|
||||
HEARTS = PluginColor.RED.getColor();
|
||||
|
||||
MSG_MAIN = PluginColor.MEDIUM_BLUE.getColor();
|
||||
MSG_ACCENT = PluginColor.BLUE.getColor();
|
||||
FEEDBACK_MSG = PluginColor.LIGHTEST_BLUE.getColor();
|
||||
FEEDBACK_MSG_ACCENT = PluginColor.LIGHT_BLUE.getColor();
|
||||
|
||||
MSG_MAIN_2 = PluginColor.GOLD.getColor();
|
||||
MSG_ACCENT_2A = PluginColor.MEDIUM_GOLD.getColor();
|
||||
MSG_ACCENT_2B = PluginColor.LIGHT_YELLOW.getColor();
|
||||
INFO_MSG = PluginColor.GOLD.getColor();
|
||||
INFO_MSG_ACCENT_DARKEST = PluginColor.MEDIUM_GOLD.getColor();
|
||||
INFO_MSG_ACCENT_MEDIUM = PluginColor.LIGHT_GOLD.getColor();
|
||||
INFO_MSG_ACCENT_LIGHTEST = PluginColor.LIGHTEST_BLUE.getColor();
|
||||
|
||||
MSG_HOVER = PluginColor.LIGHT_BLUE.getColor();
|
||||
MSG_HOVER_ACCENT = PluginColor.LIGHT_GOLD.getColor();
|
||||
MSG_HOVER = PluginColor.LIGHTEST_BLUE.getColor();
|
||||
MSG_CLICKED = PluginColor.LIGHT_PURPLE.getColor();
|
||||
}
|
||||
|
||||
public TextColor getExampleNameColor() {
|
||||
return MSG_ACCENT_2B;
|
||||
@Contract("_ -> new")
|
||||
protected @NotNull TextComponent miniMessageToComponent(String input) {
|
||||
return text()
|
||||
.append(MiniMessage.miniMessage().deserialize(input))
|
||||
.build();
|
||||
}
|
||||
public TextColor getSharerNameColor() {
|
||||
return getColorFromString(config.getSharerNameDecoration(false));
|
||||
|
||||
public boolean isConsoleFactory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public TextComponent getExampleName() {
|
||||
return text("Artemis_the_gr8").color(FEEDBACK_MSG);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,11 +105,10 @@ public class ComponentFactory {
|
||||
* Returns [PlayerStats] surrounded by underscores on both sides.
|
||||
*/
|
||||
public TextComponent pluginPrefixAsTitle() {
|
||||
//12 underscores for both console and in-game
|
||||
return text("____________").color(UNDERSCORE)
|
||||
return text("____________").color(UNDERSCORE) //12 underscores
|
||||
.append(text(" ")) //4 spaces
|
||||
.append(pluginPrefix())
|
||||
.append(text(" ")) //4 spaces
|
||||
.append(text(" "))
|
||||
.append(text("____________"));
|
||||
}
|
||||
|
||||
@ -116,11 +125,15 @@ public class ComponentFactory {
|
||||
* with color Medium_Blue.
|
||||
*/
|
||||
public TextComponent message() {
|
||||
return text().color(MSG_MAIN).build();
|
||||
return text().color(FEEDBACK_MSG).build();
|
||||
}
|
||||
|
||||
public TextComponent messageAccent() {
|
||||
return text().color(MSG_ACCENT).build();
|
||||
return text().color(FEEDBACK_MSG_ACCENT).build();
|
||||
}
|
||||
|
||||
public TextComponent infoMessageAccent() {
|
||||
return text().color(INFO_MSG_ACCENT_MEDIUM).build();
|
||||
}
|
||||
|
||||
public TextComponent title(String content, Target target) {
|
||||
@ -163,17 +176,17 @@ public class ComponentFactory {
|
||||
|
||||
public TextComponent sharerName(String sharerName) {
|
||||
return getComponent(sharerName,
|
||||
getSharerNameColor(),
|
||||
getColorFromString(config.getSharerNameDecoration(false)),
|
||||
getStyleFromString(config.getSharerNameDecoration(true)));
|
||||
}
|
||||
|
||||
public TextComponent shareButton(int shareCode) {
|
||||
return surroundWithBrackets(
|
||||
text("Share")
|
||||
.color(MSG_HOVER)
|
||||
.color(FEEDBACK_MSG_ACCENT)
|
||||
.clickEvent(ClickEvent.runCommand("/statshare " + shareCode))
|
||||
.hoverEvent(HoverEvent.showText(text("Click here to share this statistic in chat!")
|
||||
.color(MSG_HOVER_ACCENT))));
|
||||
.color(INFO_MSG_ACCENT_MEDIUM))));
|
||||
}
|
||||
|
||||
public TextComponent sharedByMessage(Component playerName) {
|
||||
@ -225,10 +238,10 @@ public class ComponentFactory {
|
||||
getStyleFromString(config.getStatNameDecoration(target, true)));
|
||||
|
||||
TextComponent subStat = subStatNameTranslatable(subStatKey, target);
|
||||
if (LanguageKeyHandler.isKeyForKillEntity(statKey)) {
|
||||
if (LanguageKeyHandler.isNormalKeyForKillEntity(statKey)) {
|
||||
return totalStatNameBuilder.append(killEntityBuilder(subStat)).build();
|
||||
}
|
||||
else if (LanguageKeyHandler.isKeyForEntityKilledBy(statKey)) {
|
||||
else if (LanguageKeyHandler.isNormalKeyForEntityKilledBy(statKey)) {
|
||||
return totalStatNameBuilder.append(entityKilledByBuilder(subStat)).build();
|
||||
}
|
||||
else {
|
||||
@ -265,7 +278,7 @@ public class ComponentFactory {
|
||||
}
|
||||
|
||||
public TextComponent damageNumberWithHeartUnitInHoverText(String mainNumber, String hoverNumber, Target target) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, null, null, clientHeart(true), target);
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, null, null, heart(), target);
|
||||
}
|
||||
|
||||
public TextComponent distanceNumber(String prettyNumber, Target target) {
|
||||
@ -298,34 +311,37 @@ public class ComponentFactory {
|
||||
return surroundWithBrackets(statUnit);
|
||||
}
|
||||
|
||||
public TextComponent clientHeart(boolean isDisplayedInHoverText) {
|
||||
TextComponent basicHeartComponent = basicHeartComponent('\u2764');
|
||||
if (isDisplayedInHoverText) {
|
||||
return basicHeartComponent;
|
||||
}
|
||||
return surroundWithBrackets(basicHeartComponent);
|
||||
public TextComponent heart() {
|
||||
return text()
|
||||
.content(String.valueOf('\u2764'))
|
||||
.color(HEARTS)
|
||||
.build();
|
||||
}
|
||||
|
||||
public TextComponent clientHeartWithHoverText() {
|
||||
TextComponent basicHeartComponent = basicHeartComponent('\u2764')
|
||||
public TextComponent heartBetweenBrackets() {
|
||||
return surroundWithBrackets(heart());
|
||||
}
|
||||
|
||||
public TextComponent heartBetweenBracketsWithHoverText() {
|
||||
TextComponent heart = heart()
|
||||
.toBuilder()
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text(Unit.HEART.getLabel())
|
||||
.color(MSG_HOVER_ACCENT)))
|
||||
.color(INFO_MSG_ACCENT_MEDIUM)))
|
||||
.build();
|
||||
return surroundWithBrackets(basicHeartComponent);
|
||||
return surroundWithBrackets(heart);
|
||||
}
|
||||
|
||||
public TextComponent consoleHeart() {
|
||||
return surroundWithBrackets(basicHeartComponent('\u2665'));
|
||||
public TextComponent arrow() {
|
||||
return text(" →").color(INFO_MSG); //4 spaces, alt + 26
|
||||
}
|
||||
|
||||
//console can do u2665, u2764 looks better in-game
|
||||
private TextComponent basicHeartComponent(char heartChar) {
|
||||
return Component.text()
|
||||
.content(String.valueOf(heartChar))
|
||||
.color(HEARTS)
|
||||
.build();
|
||||
public TextComponent bulletPoint() {
|
||||
return text(" •").color(INFO_MSG); //4 spaces, alt + 7
|
||||
}
|
||||
|
||||
public TextComponent bulletPointIndented() {
|
||||
return text(" •").color(INFO_MSG); //8 spaces, alt + 7
|
||||
}
|
||||
|
||||
/**
|
||||
@ -368,9 +384,9 @@ public class ComponentFactory {
|
||||
*
|
||||
* @return a TranslatableComponent Builder with the subStat Component as args.
|
||||
*/
|
||||
private TranslatableComponent.Builder killEntityBuilder(@NotNull TextComponent subStat) {
|
||||
private @NotNull TranslatableComponent.Builder killEntityBuilder(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key(LanguageKeyHandler.getAlternativeKeyForKillEntity()) //"Killed %s"
|
||||
.key(LanguageKeyHandler.getCustomKeyForKillEntity()) //"Killed %s"
|
||||
.args(subStat);
|
||||
}
|
||||
|
||||
@ -382,16 +398,16 @@ public class ComponentFactory {
|
||||
* @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 entityKilledByBuilder(@NotNull TextComponent subStat) {
|
||||
private @NotNull TranslatableComponent.Builder entityKilledByBuilder(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key(LanguageKeyHandler.getAlternativeKeyForEntityKilledBy()) //"Number of Deaths"
|
||||
.key(LanguageKeyHandler.getCustomKeyForEntityKilledBy()) //"Number of Deaths"
|
||||
.append(space())
|
||||
.append(translatable()
|
||||
.key(LanguageKeyHandler.getAlternativeKeyForEntityKilledByArg()) //"by %s"
|
||||
.key(LanguageKeyHandler.getCustomKeyForEntityKilledByArg()) //"by %s"
|
||||
.args(subStat));
|
||||
}
|
||||
|
||||
private TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber,
|
||||
private @NotNull TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber,
|
||||
@Nullable String hoverUnitName,
|
||||
@Nullable String hoverUnitKey,
|
||||
@Nullable TextComponent heartComponent, Target target) {
|
||||
@ -415,7 +431,7 @@ public class ComponentFactory {
|
||||
return getComponent(mainNumber, baseColor, style).hoverEvent(HoverEvent.showText(hoverText));
|
||||
}
|
||||
|
||||
private TextComponent surroundWithBrackets(TextComponent component) {
|
||||
private @NotNull TextComponent surroundWithBrackets(TextComponent component) {
|
||||
return getComponent(null, BRACKETS, null)
|
||||
.append(text("["))
|
||||
.append(component)
|
||||
@ -465,7 +481,7 @@ public class ComponentFactory {
|
||||
return names.value(textColor);
|
||||
}
|
||||
|
||||
private TextColor getLighterColor(TextColor color) {
|
||||
private @NotNull TextColor getLighterColor(@NotNull TextColor color) {
|
||||
float multiplier = (float) ((100 - config.getHoverTextAmountLighter()) / 100.0);
|
||||
HSVLike oldColor = HSVLike.fromRGB(color.red(), color.green(), color.blue());
|
||||
HSVLike newColor = HSVLike.hsvLike(oldColor.h(), oldColor.s() * multiplier, oldColor.v());
|
@ -0,0 +1,24 @@
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public final class ConsoleComponentFactory extends ComponentFactory {
|
||||
|
||||
public ConsoleComponentFactory() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsoleFactory() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent heart() {
|
||||
return Component.text()
|
||||
.content(String.valueOf('\u2665'))
|
||||
.color(HEARTS)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
@ -22,34 +23,32 @@ public final class ExampleMessage implements TextComponent {
|
||||
exampleMessage = buildMessage(factory);
|
||||
}
|
||||
|
||||
public static ExampleMessage construct(ComponentFactory factory) {
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull ExampleMessage construct(ComponentFactory factory) {
|
||||
return new ExampleMessage(factory);
|
||||
}
|
||||
|
||||
private TextComponent buildMessage(ComponentFactory factory) {
|
||||
String arrow = factory instanceof BukkitConsoleComponentFactory ? " -> " : " → "; //4 spaces, alt + 26, 1 space
|
||||
|
||||
private @NotNull TextComponent buildMessage(@NotNull ComponentFactory factory) {
|
||||
return Component.newline()
|
||||
.append(factory.pluginPrefixAsTitle())
|
||||
.append(Component.newline())
|
||||
.append(text("Examples: ").color(factory.MSG_MAIN_2))
|
||||
.append(factory.subTitle("Examples: "))
|
||||
.append(Component.newline())
|
||||
.append(text(arrow).color(factory.MSG_MAIN_2)
|
||||
.append(text("/statistic ")
|
||||
.append(text("animals_bred ").color(factory.MSG_ACCENT_2A)
|
||||
.append(text("top").color(factory.MSG_ACCENT_2B)))))
|
||||
.append(factory.arrow()).append(Component.space())
|
||||
.append(text("/stat ").color(factory.INFO_MSG)
|
||||
.append(text("animals_bred ").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.append(text("top").color(factory.INFO_MSG_ACCENT_LIGHTEST))))
|
||||
.append(Component.newline())
|
||||
.append(text(arrow).color(factory.MSG_MAIN_2)
|
||||
.append(text("/statistic ")
|
||||
.append(text("mine_block diorite ").color(factory.MSG_ACCENT_2A)
|
||||
.append(text("me").color(factory.MSG_ACCENT_2B)))))
|
||||
.append(factory.arrow()).append(Component.space())
|
||||
.append(text("/stat ").color(factory.INFO_MSG)
|
||||
.append(text("mine_block diorite ").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.append(text("me").color(factory.INFO_MSG_ACCENT_LIGHTEST))))
|
||||
.append(Component.newline())
|
||||
.append(text(arrow).color(factory.MSG_MAIN_2)
|
||||
.append(text("/statistic ")
|
||||
.append(text("deaths ").color(factory.MSG_ACCENT_2A)
|
||||
.append(text("player ").color(factory.MSG_ACCENT_2B)
|
||||
.append(text("Artemis_the_gr8")
|
||||
.color(factory.getExampleNameColor()))))));
|
||||
.append(factory.arrow()).append(Component.space())
|
||||
.append(text("/stat ").color(factory.INFO_MSG)
|
||||
.append(text("deaths ").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.append(text("player ").color(factory.INFO_MSG_ACCENT_LIGHTEST)
|
||||
.append(factory.getExampleName()))));
|
||||
}
|
||||
|
||||
@Override
|
@ -0,0 +1,109 @@
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
public final class ExcludeInfoMessage implements TextComponent {
|
||||
|
||||
private final TextComponent excludeInfo;
|
||||
|
||||
private ExcludeInfoMessage(ComponentFactory factory) {
|
||||
excludeInfo = buildMessage(factory);
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull ExcludeInfoMessage construct(ComponentFactory factory) {
|
||||
return new ExcludeInfoMessage(factory);
|
||||
}
|
||||
|
||||
private @NotNull TextComponent buildMessage(@NotNull ComponentFactory factory) {
|
||||
return Component.newline()
|
||||
.append(factory.pluginPrefixAsTitle())
|
||||
.append(Component.newline())
|
||||
.append(factory.subTitle("Hover over the arguments for more information!"))
|
||||
.append(Component.newline())
|
||||
.append(text("Usage: ").color(factory.INFO_MSG)
|
||||
.append(text("/statexclude").color(factory.INFO_MSG_ACCENT_MEDIUM)))
|
||||
.append(Component.newline())
|
||||
.append(factory.bulletPoint()).append(Component.space())
|
||||
.append(text("add ").color(factory.INFO_MSG_ACCENT_DARKEST)
|
||||
.append(text("{player-name}").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("Excludes this player from /stat results").color(factory.INFO_MSG_ACCENT_LIGHTEST))))
|
||||
.append(Component.newline())
|
||||
.append(factory.bulletPoint()).append(Component.space())
|
||||
.append(text("remove ").color(factory.INFO_MSG_ACCENT_DARKEST)
|
||||
.append(text("{player-name}").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("Includes this player in /stat results again").color(factory.INFO_MSG_ACCENT_LIGHTEST))))
|
||||
.append(Component.newline())
|
||||
.append(factory.bulletPoint()).append(Component.space())
|
||||
.append(text("list").color(factory.INFO_MSG_ACCENT_DARKEST)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("See a list of all currently excluded players").color(factory.INFO_MSG_ACCENT_LIGHTEST))))
|
||||
.append(Component.newline())
|
||||
.append(Component.newline())
|
||||
.append(text("Excluded players are:")
|
||||
.color(factory.INFO_MSG))
|
||||
.append(Component.newline())
|
||||
.append(factory.arrow()).append(Component.space())
|
||||
.append(text("not visible in the top 10").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(Component.newline())
|
||||
.append(factory.arrow()).append(Component.space())
|
||||
.append(text("not counted for the server total").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(Component.newline())
|
||||
.append(factory.arrow()).append(Component.space())
|
||||
.append(text("hidden").color(factory.INFO_MSG_ACCENT_LIGHTEST)
|
||||
.hoverEvent(HoverEvent.showText(text("All statistics are still stored and tracked by the")
|
||||
.append(Component.newline())
|
||||
.append(text("server, this command does not delete anything!"))
|
||||
.color(factory.INFO_MSG_ACCENT_LIGHTEST))))
|
||||
.append(text(" - not removed")
|
||||
.color(factory.INFO_MSG_ACCENT_MEDIUM));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String content() {
|
||||
return excludeInfo.content();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TextComponent content(@NotNull String content) {
|
||||
return excludeInfo.content(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Builder toBuilder() {
|
||||
return excludeInfo.toBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Unmodifiable @NotNull List<Component> children() {
|
||||
return excludeInfo.children();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TextComponent children(@NotNull List<? extends ComponentLike> children) {
|
||||
return excludeInfo.children(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Style style() {
|
||||
return excludeInfo.style();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TextComponent style(@NotNull Style style) {
|
||||
return excludeInfo.style(style);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public final class HalloweenComponentFactory extends ComponentFactory {
|
||||
|
||||
|
||||
public HalloweenComponentFactory() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent pluginPrefixAsTitle() {
|
||||
return miniMessageToComponent(
|
||||
"<gradient:#ff9300:#f74040:#f73b3b:#ff9300:#f74040:#ff9300>" +
|
||||
"<white>\u2620</white> __________ [PlayerStats] __________ " +
|
||||
"<white>\u2620</white></gradient>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent pluginPrefix() {
|
||||
return miniMessageToComponent(
|
||||
"<gradient:#f74040:gold:#f74040>[PlayerStats]</gradient>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent sharerName(String sharerName) {
|
||||
return miniMessageToComponent(decorateWithRandomGradient(sharerName));
|
||||
}
|
||||
|
||||
private @NotNull String decorateWithRandomGradient(@NotNull String input) {
|
||||
Random random = new Random();
|
||||
String colorString = switch (random.nextInt(6)) {
|
||||
case 0 -> "<gradient:#fcad23:red>";
|
||||
case 1 -> "<gradient:#fcad23:#f967b2:#F79438:#ffe30f>";
|
||||
case 2 -> "<gradient:red:#fcad23:red>";
|
||||
case 3 -> "<gradient:#f28e30:#f5cb42:#f74040>";
|
||||
case 4 -> "<gradient:#F79438:#f967b2>";
|
||||
case 5 -> "<gradient:#f967b2:#fcad23:#f967b2>";
|
||||
default -> "<gradient:#fcad23:#f967b2:#F74040>";
|
||||
};
|
||||
return colorString + input + "</gradient>";
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
@ -6,6 +6,7 @@ import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
@ -28,116 +29,105 @@ public final class HelpMessage implements TextComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public static HelpMessage constructPlainMsg(ComponentFactory factory, int listSize) {
|
||||
@Contract("_, _ -> new")
|
||||
public static @NotNull HelpMessage constructPlainMsg(ComponentFactory factory, int listSize) {
|
||||
return new HelpMessage(factory, false, listSize);
|
||||
}
|
||||
|
||||
public static HelpMessage constructHoverMsg(ComponentFactory factory, int listSize) {
|
||||
@Contract("_, _ -> new")
|
||||
public static @NotNull HelpMessage constructHoverMsg(ComponentFactory factory, int listSize) {
|
||||
return new HelpMessage(factory, true, listSize);
|
||||
}
|
||||
|
||||
private TextComponent buildPlainMsg(ComponentFactory factory, int listSize) {
|
||||
String arrowSymbol = "→"; //alt + 26
|
||||
String bulletSymbol = "•"; //alt + 7
|
||||
|
||||
if (factory instanceof BukkitConsoleComponentFactory) {
|
||||
arrowSymbol = "->";
|
||||
bulletSymbol = "*";
|
||||
}
|
||||
TextComponent spaces = text(" "); //4 spaces
|
||||
TextComponent arrow = text(arrowSymbol).color(factory.MSG_MAIN_2);
|
||||
TextComponent bullet = text(bulletSymbol).color(factory.MSG_MAIN_2);
|
||||
|
||||
private @NotNull TextComponent buildPlainMsg(ComponentFactory factory, int listSize) {
|
||||
return Component.newline()
|
||||
.append(factory.pluginPrefixAsTitle())
|
||||
.append(newline())
|
||||
.append(text("Type \"/statistic examples\" to see examples!").color(factory.BRACKETS).decorate(TextDecoration.ITALIC))
|
||||
.append(newline())
|
||||
.append(text("Usage:").color(factory.MSG_MAIN_2)).append(space())
|
||||
.append(text("/statistic").color(factory.MSG_HOVER_ACCENT))
|
||||
.append(text("Usage:").color(factory.INFO_MSG)).append(space())
|
||||
.append(text("/statistic").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow).append(space())
|
||||
.append(text("name").color(factory.MSG_HOVER_ACCENT))
|
||||
.append(factory.arrow()).append(space())
|
||||
.append(text("name").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow).append(space())
|
||||
.append(text("{sub-statistic}").color(factory.MSG_HOVER_ACCENT)).append(space())
|
||||
.append(factory.arrow()).append(space())
|
||||
.append(text("{sub-statistic}").color(factory.INFO_MSG_ACCENT_MEDIUM)).append(space())
|
||||
.append(text("(a block, item or entity)").color(factory.BRACKETS))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow).append(space())
|
||||
.append(text("me | player | server | top").color(factory.MSG_HOVER_ACCENT))
|
||||
.append(factory.arrow()).append(space())
|
||||
.append(text("me | player | server | top").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(newline())
|
||||
.append(spaces).append(spaces).append(bullet).append(space())
|
||||
.append(text("me:").color(factory.MSG_ACCENT_2A)).append(space())
|
||||
.append(factory.bulletPointIndented()).append(space())
|
||||
.append(text("me:").color(factory.INFO_MSG_ACCENT_DARKEST)).append(space())
|
||||
.append(text("your own statistic").color(factory.BRACKETS))
|
||||
.append(newline())
|
||||
.append(spaces).append(spaces).append(bullet).append(space())
|
||||
.append(text("player:").color(factory.MSG_ACCENT_2A)).append(space())
|
||||
.append(factory.bulletPointIndented()).append(space())
|
||||
.append(text("player:").color(factory.INFO_MSG_ACCENT_DARKEST)).append(space())
|
||||
.append(text("choose a player").color(factory.BRACKETS))
|
||||
.append(newline())
|
||||
.append(spaces).append(spaces).append(bullet).append(space())
|
||||
.append(text("server:").color(factory.MSG_ACCENT_2A)).append(space())
|
||||
.append(factory.bulletPointIndented()).append(space())
|
||||
.append(text("server:").color(factory.INFO_MSG_ACCENT_DARKEST)).append(space())
|
||||
.append(text("everyone on the server combined").color(factory.BRACKETS))
|
||||
.append(newline())
|
||||
.append(spaces).append(spaces).append(bullet).append(space())
|
||||
.append(text("top:").color(factory.MSG_ACCENT_2A)).append(space())
|
||||
.append(factory.bulletPointIndented()).append(space())
|
||||
.append(text("top:").color(factory.INFO_MSG_ACCENT_DARKEST)).append(space())
|
||||
.append(text("the top").color(factory.BRACKETS).append(space()).append(text(listSize)))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow).append(space())
|
||||
.append(text("{player-name}").color(factory.MSG_HOVER_ACCENT));
|
||||
.append(factory.arrow()).append(space())
|
||||
.append(text("{player-name}").color(factory.INFO_MSG_ACCENT_MEDIUM));
|
||||
}
|
||||
|
||||
private TextComponent buildHoverMsg(ComponentFactory factory, int listSize) {
|
||||
TextComponent spaces = text(" ");
|
||||
TextComponent arrow = text("→").color(factory.MSG_MAIN_2);
|
||||
|
||||
private @NotNull TextComponent buildHoverMsg(@NotNull ComponentFactory factory, int listSize) {
|
||||
return Component.newline()
|
||||
.append(factory.pluginPrefixAsTitle())
|
||||
.append(newline())
|
||||
.append(factory.subTitle("Hover over the arguments for more information!"))
|
||||
.append(newline())
|
||||
.append(text("Usage:").color(factory.MSG_MAIN_2)).append(space())
|
||||
.append(text("/statistic").color(factory.MSG_HOVER_ACCENT))
|
||||
.append(text("Usage:").color(factory.INFO_MSG)).append(space())
|
||||
.append(text("/statistic").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow).append(space())
|
||||
.append(text("name").color(factory.MSG_HOVER_ACCENT)
|
||||
.append(factory.arrow()).append(space())
|
||||
.append(text("name").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.hoverEvent(HoverEvent.showText(text("The name that describes the statistic").color(factory.MSG_HOVER)
|
||||
.append(newline())
|
||||
.append(text("Example: ").color(factory.MSG_MAIN_2))
|
||||
.append(text("\"animals_bred\"").color(factory.MSG_HOVER_ACCENT)))))
|
||||
.append(text("Example: ").color(factory.INFO_MSG))
|
||||
.append(text("\"animals_bred\"").color(factory.INFO_MSG_ACCENT_MEDIUM)))))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow).append(space())
|
||||
.append(text("sub-statistic").color(factory.MSG_HOVER_ACCENT)
|
||||
.append(factory.arrow()).append(space())
|
||||
.append(text("sub-statistic").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("Some statistics need an item, block or entity as extra input").color(factory.MSG_HOVER)
|
||||
.append(newline())
|
||||
.append(text("Example: ").color(factory.MSG_MAIN_2)
|
||||
.append(text("\"mine_block diorite\"").color(factory.MSG_HOVER_ACCENT))))))
|
||||
.append(text("Example: ").color(factory.INFO_MSG)
|
||||
.append(text("\"mine_block diorite\"").color(factory.INFO_MSG_ACCENT_MEDIUM))))))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow
|
||||
.append(factory.arrow()
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("Choose one").color(factory.UNDERSCORE)))).append(space())
|
||||
.append(text("me").color(factory.MSG_HOVER_ACCENT)
|
||||
text("Choose one").color(factory.MSG_CLICKED))))
|
||||
.append(space())
|
||||
.append(text("me").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("See your own statistic").color(factory.MSG_HOVER))))
|
||||
.append(text(" | ").color(factory.MSG_HOVER_ACCENT))
|
||||
.append(text("player").color(factory.MSG_HOVER_ACCENT)
|
||||
.append(text(" | ").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(text("player").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("Choose any player that has played on your server").color(factory.MSG_HOVER))))
|
||||
.append(text(" | ").color(factory.MSG_HOVER_ACCENT))
|
||||
.append(text("server").color(factory.MSG_HOVER_ACCENT)
|
||||
.append(text(" | ").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(text("server").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("See the combined total for everyone on your server").color(factory.MSG_HOVER))))
|
||||
.append(text(" | ").color(factory.MSG_HOVER_ACCENT))
|
||||
.append(text("top").color(factory.MSG_HOVER_ACCENT)
|
||||
.append(text(" | ").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(text("top").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("See the top").color(factory.MSG_HOVER).append(space())
|
||||
.append(text(listSize)))))
|
||||
.append(newline())
|
||||
.append(spaces).append(arrow).append(space())
|
||||
.append(text("player-name").color(factory.MSG_HOVER_ACCENT)
|
||||
.append(factory.arrow()).append(space())
|
||||
.append(text("player-name").color(factory.INFO_MSG_ACCENT_MEDIUM)
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text("In case you typed").color(factory.MSG_HOVER).append(space())
|
||||
.append(text("\"player\"").color(factory.MSG_HOVER_ACCENT))
|
||||
.append(text("\"player\"").color(factory.INFO_MSG_ACCENT_MEDIUM))
|
||||
.append(text(", add the player's name")))));
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* A festive version of the {@link ComponentFactory}
|
||||
*/
|
||||
public final class PrideComponentFactory extends ComponentFactory {
|
||||
|
||||
public PrideComponentFactory() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getExampleName() {
|
||||
return miniMessageToComponent("<gradient:#f74040:gold:#FF6600:#f74040>Artemis_the_gr8</gradient>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent sharerName(String sharerName) {
|
||||
return miniMessageToComponent(decorateWithRandomGradient(sharerName));
|
||||
}
|
||||
|
||||
@Override
|
||||
//12 underscores
|
||||
public TextComponent pluginPrefixAsTitle() {
|
||||
return miniMessageToComponent("<rainbow:16>____________ [PlayerStats] ____________</rainbow>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent pluginPrefix() {
|
||||
return miniMessageToComponent("<#f74040>[</#f74040>" +
|
||||
"<#F54D39>P</#F54D39>" +
|
||||
"<#F16E28>l</#F16E28>" +
|
||||
"<#ee8a19>a</#ee8a19>" +
|
||||
"<#EEA019>y</#EEA019>" +
|
||||
"<#F7C522>e</#F7C522>" +
|
||||
"<#C1DA15>r</#C1DA15>" +
|
||||
"<#84D937>S</#84D937>" +
|
||||
"<#46D858>t</#46D858>" +
|
||||
"<#01c1a7>a</#01c1a7>" +
|
||||
"<#1F8BEB>t</#1F8BEB>" +
|
||||
"<#3341E6>s</#3341E6>" +
|
||||
"<#631ae6>]</#631ae6>");
|
||||
}
|
||||
|
||||
private @NotNull String decorateWithRandomGradient(@NotNull String input) {
|
||||
Random random = new Random();
|
||||
String colorString = switch (random.nextInt(8)) {
|
||||
case 0 -> "<gradient:#03b6fc:#f854df>";
|
||||
case 1 -> "<gradient:#14f7a0:#4287f5>";
|
||||
case 2 -> "<gradient:#f971ae:#fcad23>";
|
||||
case 3 -> "<gradient:#309de6:#af45ed>";
|
||||
case 4 -> "<gradient:#f971ae:#af45ed:#4287f5>";
|
||||
case 5 -> "<gradient:#FFEA40:#fcad23:#F79438>";
|
||||
case 6 -> "<gradient:#309de6:#01c1a7:#F7F438>";
|
||||
case 7 -> "<gradient:#F79438:#f967b2>";
|
||||
default -> "<gradient:#F7F438:#01c1a7>";
|
||||
};
|
||||
return colorString + input + "</gradient>";
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.artemis.the.gr8.playerstats.core.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public final class WinterComponentFactory extends ComponentFactory {
|
||||
|
||||
public WinterComponentFactory() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent pluginPrefixAsTitle() {
|
||||
return miniMessageToComponent(
|
||||
"<gradient:#4f20f7:#4bc3fa:#05ebb1:#4f20f7>" +
|
||||
"<#D6F1FE>\u2744</#D6F1FE> __________ [PlayerStats] __________ " +
|
||||
"<#D6F1FE>\u2744</#D6F1FE></gradient>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent pluginPrefix() {
|
||||
return miniMessageToComponent(
|
||||
"<gradient:#4CA5F9:#15D6C4:#409ef7:#4F2FF7>[PlayerStats]</gradient>");
|
||||
}
|
||||
}
|
@ -1,15 +1,21 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.msgutils;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.StringUtils;
|
||||
import net.kyori.adventure.text.*;
|
||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A small utility class for turning PlayerStats' custom Components into String.
|
||||
*/
|
||||
public final class ComponentUtils {
|
||||
public final class ComponentSerializer {
|
||||
|
||||
private final LanguageKeyHandler languageKeyHandler;
|
||||
|
||||
public ComponentSerializer() {
|
||||
languageKeyHandler = LanguageKeyHandler.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a LegacyComponentSerializer that is capable of serializing
|
||||
@ -21,19 +27,19 @@ public final class ComponentUtils {
|
||||
* @return the Serializer
|
||||
* @see LanguageKeyHandler
|
||||
*/
|
||||
public static LegacyComponentSerializer getTranslatableComponentSerializer() {
|
||||
public @NotNull LegacyComponentSerializer getTranslatableComponentSerializer() {
|
||||
LegacyComponentSerializer serializer = getTextComponentSerializer();
|
||||
|
||||
ComponentFlattener flattener = ComponentFlattener.basic().toBuilder()
|
||||
.mapper(TranslatableComponent.class, trans -> {
|
||||
StringBuilder totalPrettyName = new StringBuilder();
|
||||
if (LanguageKeyHandler.isKeyForEntityKilledByArg(trans.key())) {
|
||||
if (LanguageKeyHandler.isCustomKeyForEntityKilledByArg(trans.key())) {
|
||||
return "";
|
||||
}
|
||||
else if (LanguageKeyHandler.isKeyForEntityKilledBy(trans.key()) ||
|
||||
LanguageKeyHandler.isAlternativeKeyForEntityKilledBy(trans.key()) ||
|
||||
LanguageKeyHandler.isKeyForKillEntity(trans.key()) ||
|
||||
LanguageKeyHandler.isAlternativeKeyForKillEntity(trans.key())) {
|
||||
else if (LanguageKeyHandler.isNormalKeyForEntityKilledBy(trans.key()) ||
|
||||
LanguageKeyHandler.isCustomKeyForEntityKilledBy(trans.key()) ||
|
||||
LanguageKeyHandler.isNormalKeyForKillEntity(trans.key()) ||
|
||||
LanguageKeyHandler.isCustomKeyForKillEntity(trans.key())) {
|
||||
|
||||
TextComponent.Builder temp = Component.text();
|
||||
trans.iterator(ComponentIteratorType.DEPTH_FIRST, ComponentIteratorFlag.INCLUDE_TRANSLATABLE_COMPONENT_ARGUMENTS)
|
||||
@ -50,28 +56,25 @@ public final class ComponentUtils {
|
||||
}
|
||||
//isolate the translatable component with the entity inside
|
||||
else if (component instanceof TranslatableComponent translatable) {
|
||||
if (translatable.key().contains("entity.")) {
|
||||
if (LanguageKeyHandler.isEntityKey(translatable.key())) {
|
||||
temp.append(Component.space())
|
||||
.append(Component.text("(")
|
||||
.append(Component.text(
|
||||
StringUtils.prettify(LanguageKeyHandler.convertToName(translatable.key()))))
|
||||
languageKeyHandler.convertLanguageKeyToDisplayName(translatable.key())))
|
||||
.append(Component.text(")")));
|
||||
totalPrettyName.append(
|
||||
serializer.serialize(temp.build()));
|
||||
}
|
||||
else if (!LanguageKeyHandler.isKeyForEntityKilledByArg(translatable.key())) {
|
||||
else if (!LanguageKeyHandler.isCustomKeyForEntityKilledByArg(translatable.key())) {
|
||||
totalPrettyName.append(
|
||||
LanguageKeyHandler.getStatKeyTranslation(
|
||||
languageKeyHandler.convertLanguageKeyToDisplayName(
|
||||
translatable.key()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (trans.key().startsWith("stat")) {
|
||||
return LanguageKeyHandler.getStatKeyTranslation(trans.key());
|
||||
}
|
||||
else {
|
||||
return StringUtils.prettify(LanguageKeyHandler.convertToName(trans.key()));
|
||||
return languageKeyHandler.convertLanguageKeyToDisplayName(trans.key());
|
||||
}
|
||||
return totalPrettyName.toString();
|
||||
})
|
||||
@ -80,7 +83,8 @@ public final class ComponentUtils {
|
||||
return serializer.toBuilder().flattener(flattener).build();
|
||||
}
|
||||
|
||||
private static LegacyComponentSerializer getTextComponentSerializer() {
|
||||
@Contract(" -> new")
|
||||
private static @NotNull LegacyComponentSerializer getTextComponentSerializer() {
|
||||
return LegacyComponentSerializer
|
||||
.builder()
|
||||
.hexColors()
|
@ -1,4 +1,4 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.msgutils;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import net.kyori.adventure.text.Component;
|
||||
@ -8,7 +8,9 @@ import net.kyori.adventure.text.minimessage.tag.Tag;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
@ -19,26 +21,13 @@ import java.util.Random;
|
||||
*/
|
||||
public final class EasterEggProvider {
|
||||
|
||||
private static boolean isEnabled;
|
||||
private static final Random random;
|
||||
|
||||
static {
|
||||
enable();
|
||||
random = new Random();
|
||||
}
|
||||
|
||||
public static void enable() {
|
||||
isEnabled = true;
|
||||
}
|
||||
public static void disable() {
|
||||
isEnabled = false;
|
||||
}
|
||||
|
||||
public static Component getPlayerName(Player player) {
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @Nullable Component getPlayerName(@NotNull Player player) {
|
||||
int sillyNumber = getSillyNumber();
|
||||
String playerName = null;
|
||||
switch (player.getUniqueId().toString()) {
|
||||
@ -117,7 +106,8 @@ public final class EasterEggProvider {
|
||||
return sillyNumber >= lowerBound && sillyNumber <= upperBound;
|
||||
}
|
||||
|
||||
private static TagResolver papiTag(final @NotNull Player player) {
|
||||
@Contract("_ -> new")
|
||||
private static @NotNull TagResolver papiTag(final @NotNull Player player) {
|
||||
return TagResolver.resolver("papi", (argumentQueue, context) -> {
|
||||
final String papiPlaceholder = argumentQueue.popOr("papi tag requires an argument").value();
|
||||
final String parsedPlaceholder = PlaceholderAPI.setPlaceholders(player, '%' + papiPlaceholder + '%');
|
@ -1,4 +1,4 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.msgutils;
|
||||
|
||||
import org.bukkit.map.MinecraftFont;
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.artemis.the.gr8.playerstats.core.msg.msgutils;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public final class FormattingFunction {
|
||||
|
||||
private final BiFunction<Integer, CommandSender, TextComponent> formattingFunction;
|
||||
|
||||
public FormattingFunction(BiFunction<Integer, CommandSender, TextComponent> formattingFunction) {
|
||||
this.formattingFunction = formattingFunction;
|
||||
}
|
||||
|
||||
public TextComponent getResultWithShareButton(Integer shareCode) {
|
||||
return this.apply(shareCode, null);
|
||||
}
|
||||
|
||||
public TextComponent getResultWithSharerName(CommandSender sender) {
|
||||
return this.apply(null, sender);
|
||||
}
|
||||
|
||||
public TextComponent getDefaultResult() {
|
||||
return this.apply(null, null);
|
||||
}
|
||||
|
||||
private TextComponent apply(Integer shareCode, CommandSender sender) {
|
||||
return formattingFunction.apply(shareCode, sender);
|
||||
}
|
||||
}
|
@ -1,64 +1,60 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.msgutils;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.FileHandler;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Unit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
* A utility class that provides language keys to be
|
||||
* put in a TranslatableComponent.
|
||||
*/
|
||||
public final class LanguageKeyHandler {
|
||||
public final class LanguageKeyHandler extends FileHandler {
|
||||
|
||||
private static Main plugin;
|
||||
private static HashMap<Statistic, String> statNameKeys;
|
||||
private static File languageKeyFile;
|
||||
private static FileConfiguration languageKeys;
|
||||
private static volatile LanguageKeyHandler instance;
|
||||
private static HashMap<Statistic, String> statisticKeys;
|
||||
private final Pattern subStatKey;
|
||||
|
||||
/**
|
||||
* Since this class uses a file to get the English translations
|
||||
* of languageKeys, it needs an instance of the PlayerStats
|
||||
* plugin to get access to this file.
|
||||
*
|
||||
* @param plugin an instance of PlayerStats' Main class
|
||||
*/
|
||||
public LanguageKeyHandler(Main plugin) {
|
||||
LanguageKeyHandler.plugin = plugin;
|
||||
statNameKeys = generateStatNameKeys();
|
||||
loadFile();
|
||||
private LanguageKeyHandler() {
|
||||
super("language.yml");
|
||||
statisticKeys = generateStatisticKeys();
|
||||
subStatKey = Pattern.compile("(item|entity|block)\\.minecraft\\.");
|
||||
}
|
||||
|
||||
private static void loadFile() {
|
||||
languageKeyFile = new File(plugin.getDataFolder(), "language.yml");
|
||||
if (!languageKeyFile.exists()) {
|
||||
plugin.saveResource("language.yml", false);
|
||||
}
|
||||
languageKeys = YamlConfiguration.loadConfiguration(languageKeyFile);
|
||||
public static LanguageKeyHandler getInstance() {
|
||||
LanguageKeyHandler localVar = instance;
|
||||
if (localVar != null) {
|
||||
return localVar;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public static void reloadFile() {
|
||||
if (!languageKeyFile.exists()) {
|
||||
loadFile();
|
||||
} else {
|
||||
languageKeys = YamlConfiguration.loadConfiguration(languageKeyFile);
|
||||
MyLogger.logLowLevelMsg("Language file reloaded!");
|
||||
synchronized (LanguageKeyHandler.class) {
|
||||
if (instance == null) {
|
||||
instance = new LanguageKeyHandler();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public @NotNull String getKeyForBlockUnit() {
|
||||
return "soundCategory.block";
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public static boolean isEntityKey(@NotNull String key) {
|
||||
return key.contains("entity.minecraft");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,7 +63,8 @@ public final class LanguageKeyHandler {
|
||||
* @param statKey the Key to check
|
||||
* @return true if this Key is key for kill-entity
|
||||
*/
|
||||
public static boolean isKeyForKillEntity(String statKey) {
|
||||
@Contract(pure = true)
|
||||
public static boolean isNormalKeyForKillEntity(@NotNull String statKey) {
|
||||
return statKey.equalsIgnoreCase("stat_type.minecraft.killed");
|
||||
}
|
||||
|
||||
@ -77,7 +74,8 @@ public final class LanguageKeyHandler {
|
||||
* @param statKey the Key to check
|
||||
* @return true if this Key is key for commands.kill.success.single
|
||||
*/
|
||||
public static boolean isAlternativeKeyForKillEntity(String statKey) {
|
||||
@Contract(pure = true)
|
||||
public static boolean isCustomKeyForKillEntity(@NotNull String statKey) {
|
||||
return statKey.equalsIgnoreCase("commands.kill.success.single");
|
||||
}
|
||||
|
||||
@ -86,7 +84,8 @@ public final class LanguageKeyHandler {
|
||||
*
|
||||
* @return the key "commands.kill.success.single", which results in "Killed %s"
|
||||
*/
|
||||
public static String getAlternativeKeyForKillEntity() {
|
||||
@Contract(pure = true)
|
||||
public static @NotNull String getCustomKeyForKillEntity() {
|
||||
return "commands.kill.success.single";
|
||||
}
|
||||
|
||||
@ -96,27 +95,19 @@ public final class LanguageKeyHandler {
|
||||
* @param statKey the Key to check
|
||||
* @return true if this Key is a key for entity-killed-by
|
||||
*/
|
||||
public static boolean isKeyForEntityKilledBy(String statKey) {
|
||||
@Contract(pure = true)
|
||||
public static boolean isNormalKeyForEntityKilledBy(@NotNull String statKey) {
|
||||
return statKey.equalsIgnoreCase("stat_type.minecraft.killed_by");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given Key is the language key "stat.minecraft.deaths".
|
||||
* Checks if a given Key is the language key "subtitles.entity.generic.death".
|
||||
* @param statKey the Key to check
|
||||
* @return true if this Key is key for stat.minecraft.deaths
|
||||
* @return true if this Key is key for subtitles.entity.generic.death
|
||||
*/
|
||||
public static boolean isAlternativeKeyForEntityKilledBy(String statKey) {
|
||||
return statKey.equalsIgnoreCase("stat.minecraft.deaths");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a language key to replace the default stat_type.minecraft.killed_by key.
|
||||
*
|
||||
* @return the key "stat.minecraft.deaths", which results in "Number of Deaths"
|
||||
* (meant to be followed by {@link #getAlternativeKeyForEntityKilledByArg()})
|
||||
*/
|
||||
public static String getAlternativeKeyForEntityKilledBy() {
|
||||
return "stat.minecraft.deaths";
|
||||
@Contract(pure = true)
|
||||
public static boolean isCustomKeyForEntityKilledBy(@NotNull String statKey) {
|
||||
return statKey.equalsIgnoreCase("subtitles.entity.generic.death");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,70 +117,75 @@ public final class LanguageKeyHandler {
|
||||
* @param statKey the Key to Check
|
||||
* @return true if this Key is the key for book.byAuthor
|
||||
*/
|
||||
public static boolean isKeyForEntityKilledByArg(String statKey) {
|
||||
@Contract(pure = true)
|
||||
public static boolean isCustomKeyForEntityKilledByArg(@NotNull String statKey) {
|
||||
return statKey.equalsIgnoreCase("book.byAuthor");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a language key to complete the alternative key for Statistic.Entity_Killed_By.
|
||||
* Returns a language key to replace the default stat_type.minecraft.killed_by key.
|
||||
*
|
||||
* @return the key "book.byAuthor", which results in "by %". If used after
|
||||
* {@link #getAlternativeKeyForEntityKilledBy()}, you will get "Number of Deaths" "by %s"
|
||||
* @return the key "subtitles.entity.generic.death", which results in "Dying"
|
||||
* (meant to be followed by {@link #getCustomKeyForEntityKilledByArg()})
|
||||
*/
|
||||
public static String getAlternativeKeyForEntityKilledByArg() {
|
||||
return "book.byAuthor";
|
||||
@Contract(pure = true)
|
||||
public static @NotNull String getCustomKeyForEntityKilledBy() {
|
||||
return "subtitles.entity.generic.death";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key the String to turn into a normal name
|
||||
* @return a pretty name
|
||||
* Returns a language key to complete the alternative key for statistic.entity_killed_by.
|
||||
*
|
||||
* @return the key "book.byAuthor", which results in "by %". If used after
|
||||
* {@link #getCustomKeyForEntityKilledBy()}, you will get "Dying" "by %s"
|
||||
*/
|
||||
public static String convertToName(String key) {
|
||||
if (key.equalsIgnoreCase("soundCategory.block")) {
|
||||
@Contract(pure = true)
|
||||
public static @NotNull String getCustomKeyForEntityKilledByArg() {
|
||||
return "book.byAuthor";
|
||||
}
|
||||
|
||||
public String convertLanguageKeyToDisplayName(String key) {
|
||||
if (key == null) return null;
|
||||
if (isStatKey(key)) {
|
||||
return getStatKeyTranslation(key);
|
||||
}
|
||||
else if (key.equalsIgnoreCase(getKeyForBlockUnit())) {
|
||||
return Unit.BLOCK.getLabel();
|
||||
} else if (isKeyForKillEntity(key)) {
|
||||
return "times_killed";
|
||||
} else if (isKeyForEntityKilledBy(key)) {
|
||||
return "number_of_times_killed_by";
|
||||
} else if (isKeyForEntityKilledByArg(key)) { //this one returns nothing, because it's an extra key I added
|
||||
return ""; //to make the TranslatableComponent work
|
||||
}
|
||||
String toReplace = "";
|
||||
if (key.contains("stat")) {
|
||||
if (key.contains("type")) {
|
||||
toReplace = "stat_type";
|
||||
} else {
|
||||
toReplace = "stat";
|
||||
}
|
||||
} else if (key.contains("entity")) { //for the two entity-related ones, put brackets around it to
|
||||
toReplace = "entity"; //make up for the multiple-keys/args-serializer issues
|
||||
} else if (key.contains("block")) {
|
||||
toReplace = "block";
|
||||
} else if (key.contains("item")) {
|
||||
toReplace = "item";
|
||||
}
|
||||
toReplace = toReplace + ".minecraft.";
|
||||
return key.replace(toReplace, "");
|
||||
}
|
||||
|
||||
private static @Nullable String convertToNormalStatKey(String statKey) {
|
||||
if (isKeyForKillEntity(statKey)) {
|
||||
return "stat_type.minecraft.killed";
|
||||
} else if (isKeyForEntityKilledBy(statKey)) {
|
||||
return "stat_type.minecraft.killed_by";
|
||||
} else if (isKeyForEntityKilledByArg(statKey)) {
|
||||
return null;
|
||||
} else {
|
||||
return statKey;
|
||||
Matcher matcher = subStatKey.matcher(key);
|
||||
if (matcher.find()) {
|
||||
String rawName = matcher.replaceFirst("");
|
||||
return StringUtils.prettify(rawName);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
public static String getStatKeyTranslation(String statKey) {
|
||||
private boolean isStatKey(@NotNull String key) {
|
||||
return (key.contains("stat") ||
|
||||
isCustomKeyForKillEntity(key) ||
|
||||
isCustomKeyForEntityKilledBy(key) ||
|
||||
isCustomKeyForEntityKilledByArg(key));
|
||||
}
|
||||
|
||||
private String getStatKeyTranslation(String statKey) {
|
||||
String realKey = convertToNormalStatKey(statKey);
|
||||
if (realKey == null) {
|
||||
return "";
|
||||
}
|
||||
return languageKeys.getString(realKey);
|
||||
return super.getFileConfiguration().getString(realKey);
|
||||
}
|
||||
|
||||
private static @Nullable String convertToNormalStatKey(String statKey) {
|
||||
if (isCustomKeyForKillEntity(statKey)) {
|
||||
return "stat_type.minecraft.killed";
|
||||
} else if (isCustomKeyForEntityKilledBy(statKey)) {
|
||||
return "stat_type.minecraft.killed_by";
|
||||
} else if (isCustomKeyForEntityKilledByArg(statKey)) {
|
||||
return null;
|
||||
} else {
|
||||
return statKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,12 +193,12 @@ public final class LanguageKeyHandler {
|
||||
* @return the official Key from the NameSpacedKey for this Statistic,
|
||||
* or return null if no enum constant can be retrieved.
|
||||
*/
|
||||
public String getStatKey(@NotNull Statistic statistic) {
|
||||
public @NotNull String getStatKey(@NotNull Statistic statistic) {
|
||||
if (statistic.getType() == Statistic.Type.UNTYPED) {
|
||||
return "stat.minecraft." + statNameKeys.get(statistic);
|
||||
return "stat.minecraft." + statisticKeys.get(statistic);
|
||||
}
|
||||
else {
|
||||
return "stat_type.minecraft." + statNameKeys.get(statistic);
|
||||
return "stat_type.minecraft." + statisticKeys.get(statistic);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,7 +238,7 @@ public final class LanguageKeyHandler {
|
||||
if (block == null) return null;
|
||||
else if (block.toString().toLowerCase(Locale.ENGLISH).contains("wall_banner")) { //replace wall_banner with regular banner, since there is no key for wall banners
|
||||
String blockName = block.toString().toLowerCase(Locale.ENGLISH).replace("wall_", "");
|
||||
Material newBlock = EnumHandler.getBlockEnum(blockName);
|
||||
Material newBlock = EnumHandler.getInstance().getBlockEnum(blockName);
|
||||
return (newBlock != null) ? "block.minecraft." + newBlock.getKey().getKey() : null;
|
||||
}
|
||||
else {
|
||||
@ -262,7 +258,7 @@ public final class LanguageKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull HashMap<Statistic, String> generateStatNameKeys() {
|
||||
private @NotNull HashMap<Statistic, String> generateStatisticKeys() {
|
||||
//get the enum names for all statistics first
|
||||
HashMap<Statistic, String> statNames = new HashMap<>(Statistic.values().length);
|
||||
Arrays.stream(Statistic.values()).forEach(statistic -> statNames.put(statistic, statistic.toString().toLowerCase(Locale.ENGLISH)));
|
@ -1,6 +1,8 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.msgutils;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.api.StatNumberFormatter;
|
||||
import com.artemis.the.gr8.playerstats.api.enums.Unit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
@ -10,7 +12,7 @@ import java.text.DecimalFormat;
|
||||
* that are easier to understand (for example: from ticks to hours) and adds commas
|
||||
* to break up large numbers.
|
||||
*/
|
||||
public final class NumberFormatter {
|
||||
public final class NumberFormatter implements StatNumberFormatter {
|
||||
|
||||
private final DecimalFormat format;
|
||||
|
||||
@ -21,11 +23,10 @@ public final class NumberFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the input number into a more readable format depending on its type
|
||||
* (number-of-times, time-, damage- or distance-based) according to the
|
||||
* corresponding config settings, and adds commas in groups of 3.
|
||||
* Adds commas in groups of 3.
|
||||
*/
|
||||
public String formatNumber(long number) {
|
||||
@Override
|
||||
public @NotNull String formatDefaultNumber(long number) {
|
||||
return format.format(number);
|
||||
}
|
||||
|
||||
@ -33,7 +34,8 @@ public final class NumberFormatter {
|
||||
* The unit of damage-based statistics is half a heart by default.
|
||||
* This method turns the number into hearts.
|
||||
*/
|
||||
public String formatDamageNumber(long number, Unit statUnit) { //7 statistics
|
||||
@Override
|
||||
public @NotNull String formatDamageNumber(long number, @NotNull Unit statUnit) { //7 statistics
|
||||
if (statUnit == Unit.HEART) {
|
||||
return format.format(Math.round(number / 2.0));
|
||||
} else {
|
||||
@ -47,7 +49,8 @@ public final class NumberFormatter {
|
||||
* and turns it into km or leaves it as cm otherwise,
|
||||
* depending on the config settings.
|
||||
*/
|
||||
public String formatDistanceNumber(long number, Unit statUnit) { //15 statistics
|
||||
@Override
|
||||
public @NotNull String formatDistanceNumber(long number, @NotNull Unit statUnit) { //15 statistics
|
||||
switch (statUnit) {
|
||||
case CM -> {
|
||||
return format.format(number);
|
||||
@ -69,6 +72,7 @@ public final class NumberFormatter {
|
||||
* @return a String with the form "1D 2H 3M 4S"
|
||||
* (depending on the Unit range selected)
|
||||
*/
|
||||
@Override
|
||||
public String formatTimeNumber(long number, Unit biggestUnit, Unit smallestUnit) { //5 statistics
|
||||
if (number <= 0) {
|
||||
return "-";
|
||||
@ -83,9 +87,9 @@ public final class NumberFormatter {
|
||||
|
||||
while(currUnit != null){
|
||||
//Define amount of units
|
||||
int amount = 0;
|
||||
int amount;
|
||||
|
||||
//Current unit is equal to smallest unit, in this case round the remainder
|
||||
//Current unit is equal to the smallest unit, in this case round the remainder
|
||||
if(currUnit == smallestUnit){
|
||||
amount = (int) Math.round(leftoverSeconds / currUnit.getSeconds());
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
package com.artemis.the.gr8.playerstats.core.msg.msgutils;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A small utility class that helps make enum constant
|
||||
@ -10,6 +12,12 @@ import java.util.Locale;
|
||||
*/
|
||||
public final class StringUtils {
|
||||
|
||||
private static final Pattern lowercaseLetterAfterSpace;
|
||||
|
||||
static {
|
||||
lowercaseLetterAfterSpace = Pattern.compile("(?<= )[a-z]");
|
||||
}
|
||||
|
||||
private StringUtils() {
|
||||
}
|
||||
|
||||
@ -20,14 +28,22 @@ public final class StringUtils {
|
||||
*/
|
||||
public static String prettify(String input) {
|
||||
if (input == null) return null;
|
||||
MyLogger.logHighLevelMsg("Prettifying [" + input + "]");
|
||||
|
||||
StringBuilder capitals = new StringBuilder(input.toLowerCase(Locale.ENGLISH));
|
||||
capitals.setCharAt(0, Character.toUpperCase(capitals.charAt(0)));
|
||||
while (capitals.indexOf("_") != -1) {
|
||||
MyLogger.logHighLevelMsg("Replacing underscores and capitalizing names...");
|
||||
|
||||
while (capitals.indexOf("_") != -1) {
|
||||
int index = capitals.indexOf("_");
|
||||
capitals.setCharAt(index + 1, Character.toUpperCase(capitals.charAt(index + 1)));
|
||||
capitals.setCharAt(index, ' ');
|
||||
MyLogger.logHighLevelMsg("Replacing underscores: " + capitals);
|
||||
}
|
||||
|
||||
Matcher matcher = lowercaseLetterAfterSpace.matcher(capitals);
|
||||
while (matcher.find()) {
|
||||
int index = matcher.start();
|
||||
capitals.setCharAt(index, Character.toUpperCase(capitals.charAt(index)));
|
||||
MyLogger.logHighLevelMsg("Capitalizing names: " + capitals);
|
||||
}
|
||||
return capitals.toString();
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package com.artemis.the.gr8.playerstats.reload;
|
||||
package com.artemis.the.gr8.playerstats.core.multithreading;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.UnixTimeHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.UnixTimeHandler;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.util.UUID;
|
||||
@ -13,7 +13,7 @@ import java.util.concurrent.RecursiveAction;
|
||||
/**
|
||||
* The action that is executed when a reload-command is triggered.
|
||||
*/
|
||||
final class ReloadAction extends RecursiveAction {
|
||||
final class PlayerLoadAction extends RecursiveAction {
|
||||
|
||||
private static int threshold;
|
||||
|
||||
@ -21,33 +21,26 @@ final class ReloadAction extends RecursiveAction {
|
||||
private final int start;
|
||||
private final int end;
|
||||
|
||||
private final int lastPlayedLimit;
|
||||
private final ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||
|
||||
/**
|
||||
* Fills a ConcurrentHashMap with PlayerNames and UUIDs for all OfflinePlayers
|
||||
* that should be included in statistic calculations.
|
||||
*
|
||||
* @param players array of all OfflinePlayers (straight from Bukkit)
|
||||
* @param lastPlayedLimit whether to set a limit based on last-played-date
|
||||
* @param players array of all OfflinePlayers to filter and load
|
||||
* @param offlinePlayerUUIDs the ConcurrentHashMap to put playerNames and UUIDs in
|
||||
* @see OfflinePlayerHandler
|
||||
*/
|
||||
public ReloadAction(OfflinePlayer[] players,
|
||||
int lastPlayedLimit, ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
|
||||
|
||||
this(players, 0, players.length, lastPlayedLimit, offlinePlayerUUIDs);
|
||||
public PlayerLoadAction(OfflinePlayer[] players, ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
|
||||
this(players, 0, players.length, offlinePlayerUUIDs);
|
||||
}
|
||||
|
||||
private ReloadAction(OfflinePlayer[] players, int start, int end,
|
||||
int lastPlayedLimit, ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
|
||||
private PlayerLoadAction(OfflinePlayer[] players, int start, int end, ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
|
||||
threshold = ThreadManager.getTaskThreshold();
|
||||
|
||||
this.players = players;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
|
||||
this.lastPlayedLimit = lastPlayedLimit;
|
||||
this.offlinePlayerUUIDs = offlinePlayerUUIDs;
|
||||
|
||||
MyLogger.subActionCreated(Thread.currentThread().getName());
|
||||
@ -61,10 +54,10 @@ final class ReloadAction extends RecursiveAction {
|
||||
}
|
||||
else {
|
||||
final int split = length / 2;
|
||||
final ReloadAction subTask1 = new ReloadAction(players, start, (start + split),
|
||||
lastPlayedLimit, offlinePlayerUUIDs);
|
||||
final ReloadAction subTask2 = new ReloadAction(players, (start + split), end,
|
||||
lastPlayedLimit, offlinePlayerUUIDs);
|
||||
final PlayerLoadAction subTask1 = new PlayerLoadAction(players, start, (start + split),
|
||||
offlinePlayerUUIDs);
|
||||
final PlayerLoadAction subTask2 = new PlayerLoadAction(players, (start + split), end,
|
||||
offlinePlayerUUIDs);
|
||||
|
||||
//queue and compute all subtasks in the right order
|
||||
invokeAll(subTask1, subTask2);
|
||||
@ -72,12 +65,16 @@ final class ReloadAction extends RecursiveAction {
|
||||
}
|
||||
|
||||
private void process() {
|
||||
OfflinePlayerHandler offlinePlayerHandler = OfflinePlayerHandler.getInstance();
|
||||
int lastPlayedLimit = ConfigHandler.getInstance().getLastPlayedLimit();
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
OfflinePlayer player = players[i];
|
||||
String playerName = player.getName();
|
||||
MyLogger.actionRunning(Thread.currentThread().getName());
|
||||
if (playerName != null &&
|
||||
(lastPlayedLimit == 0 || UnixTimeHandler.hasPlayedSince(lastPlayedLimit, player.getLastPlayed()))) {
|
||||
!offlinePlayerHandler.isExcludedPlayer(player.getUniqueId()) &&
|
||||
UnixTimeHandler.hasPlayedSince(lastPlayedLimit, player.getLastPlayed())) {
|
||||
offlinePlayerUUIDs.put(playerName, player.getUniqueId());
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.artemis.the.gr8.playerstats.core.multithreading;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.core.Main;
|
||||
import com.artemis.the.gr8.playerstats.core.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/** The Thread that is in charge of reloading PlayerStats. */
|
||||
final class ReloadThread extends Thread {
|
||||
|
||||
private final Main main;
|
||||
private static OutputManager outputManager;
|
||||
|
||||
private final StatThread statThread;
|
||||
private final CommandSender sender;
|
||||
|
||||
public ReloadThread(Main main, OutputManager m, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
|
||||
this.main = main;
|
||||
outputManager = m;
|
||||
|
||||
statThread = s;
|
||||
sender = se;
|
||||
|
||||
this.setName("ReloadThread-" + ID);
|
||||
MyLogger.logHighLevelMsg(this.getName() + " created!");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call reload() from Main. If a {@link StatThread}
|
||||
* is still running, it will join the statThread and wait for it to finish.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
MyLogger.logHighLevelMsg(this.getName() + " started!");
|
||||
|
||||
if (statThread != null && statThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.logLowLevelMsg(this.getName() + ": Waiting for " + statThread.getName() + " to finish up...");
|
||||
statThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
MyLogger.logException(e, "ReloadThread", "run(), trying to join " + statThread.getName());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
MyLogger.logLowLevelMsg("Reloading!");
|
||||
main.reloadPlugin();
|
||||
|
||||
if (sender != null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.RELOADED_CONFIG);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic;
|
||||
package com.artemis.the.gr8.playerstats.core.multithreading;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
@ -17,10 +16,8 @@ import java.util.concurrent.RecursiveTask;
|
||||
final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>> {
|
||||
|
||||
private static int threshold;
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private final ImmutableList<String> playerNames;
|
||||
private final RequestSettings requestSettings;
|
||||
private final StatRequest.Settings requestSettings;
|
||||
private final ConcurrentHashMap<String, Integer> allStats;
|
||||
|
||||
/**
|
||||
@ -29,15 +26,13 @@ final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>>
|
||||
* ForkJoinPool, and returns the ConcurrentHashMap when
|
||||
* everything is done.
|
||||
*
|
||||
* @param offlinePlayerHandler the OfflinePlayerHandler to convert playerNames into Players
|
||||
* @param playerNames ImmutableList of playerNames for players that should be included in stat calculations
|
||||
* @param requestSettings a validated requestSettings object
|
||||
* @param allStats the ConcurrentHashMap to put the results on
|
||||
*/
|
||||
public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList<String> playerNames, RequestSettings requestSettings, ConcurrentHashMap<String, Integer> allStats) {
|
||||
public StatAction(ImmutableList<String> playerNames, StatRequest.Settings requestSettings, ConcurrentHashMap<String, Integer> allStats) {
|
||||
threshold = ThreadManager.getTaskThreshold();
|
||||
|
||||
this.offlinePlayerHandler = offlinePlayerHandler;
|
||||
this.playerNames = playerNames;
|
||||
this.requestSettings = requestSettings;
|
||||
this.allStats = allStats;
|
||||
@ -51,8 +46,8 @@ final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>>
|
||||
return getStatsDirectly();
|
||||
}
|
||||
else {
|
||||
final StatAction subTask1 = new StatAction(offlinePlayerHandler, playerNames.subList(0, playerNames.size()/2), requestSettings, allStats);
|
||||
final StatAction subTask2 = new StatAction(offlinePlayerHandler, playerNames.subList(playerNames.size()/2, playerNames.size()), requestSettings, allStats);
|
||||
final StatAction subTask1 = new StatAction(playerNames.subList(0, playerNames.size()/2), requestSettings, allStats);
|
||||
final StatAction subTask2 = new StatAction(playerNames.subList(playerNames.size()/2, playerNames.size()), requestSettings, allStats);
|
||||
|
||||
//queue and compute all subtasks in the right order
|
||||
subTask1.fork();
|
||||
@ -62,12 +57,14 @@ final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>>
|
||||
}
|
||||
|
||||
private ConcurrentHashMap<String, Integer> getStatsDirectly() {
|
||||
OfflinePlayerHandler offlinePlayerHandler = OfflinePlayerHandler.getInstance();
|
||||
|
||||
Iterator<String> iterator = playerNames.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
do {
|
||||
String playerName = iterator.next();
|
||||
MyLogger.actionRunning(Thread.currentThread().getName());
|
||||
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(playerName);
|
||||
OfflinePlayer player = offlinePlayerHandler.getIncludedOfflinePlayer(playerName);
|
||||
int statistic = 0;
|
||||
switch (requestSettings.getStatistic().getType()) {
|
||||
case UNTYPED -> statistic = player.getStatistic(requestSettings.getStatistic());
|
@ -0,0 +1,67 @@
|
||||
package com.artemis.the.gr8.playerstats.core.multithreading;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.core.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.core.statrequest.RequestManager;
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import com.artemis.the.gr8.playerstats.api.StatResult;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.core.enums.StandardMessage;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Thread that is in charge of getting and calculating statistics.
|
||||
*/
|
||||
final class StatThread extends Thread {
|
||||
|
||||
private static OutputManager outputManager;
|
||||
|
||||
private final ReloadThread reloadThread;
|
||||
private final StatRequest<?> statRequest;
|
||||
|
||||
public StatThread(OutputManager m, int ID, StatRequest<?> s, @Nullable ReloadThread r) {
|
||||
outputManager = m;
|
||||
reloadThread = r;
|
||||
statRequest = s;
|
||||
|
||||
this.setName("StatThread-" + statRequest.getSettings().getCommandSender().getName() + "-" + ID);
|
||||
MyLogger.logHighLevelMsg(this.getName() + " created!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws IllegalStateException {
|
||||
MyLogger.logHighLevelMsg(this.getName() + " started!");
|
||||
CommandSender statRequester = statRequest.getSettings().getCommandSender();
|
||||
|
||||
if (reloadThread != null && reloadThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.logLowLevelMsg(this.getName() + ": Waiting for " + reloadThread.getName() + " to finish up...");
|
||||
outputManager.sendFeedbackMsg(statRequester, StandardMessage.STILL_RELOADING);
|
||||
reloadThread.join();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
MyLogger.logException(e, "StatThread", "Trying to join " + reloadThread.getName());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
long lastCalc = ThreadManager.getLastRecordedCalcTime();
|
||||
if (lastCalc > 6000) {
|
||||
outputManager.sendFeedbackMsg(statRequester, StandardMessage.WAIT_A_MINUTE);
|
||||
} else if (lastCalc > 2000) {
|
||||
outputManager.sendFeedbackMsg(statRequester, StandardMessage.WAIT_A_MOMENT);
|
||||
}
|
||||
|
||||
try {
|
||||
StatResult<?> result = RequestManager.execute(statRequest);
|
||||
outputManager.sendToCommandSender(statRequester, result.formattedComponent());
|
||||
}
|
||||
catch (ConcurrentModificationException e) {
|
||||
if (!statRequest.getSettings().isConsoleSender()) {
|
||||
outputManager.sendFeedbackMsg(statRequester, StandardMessage.UNKNOWN_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package com.artemis.the.gr8.playerstats.core.multithreading;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.core.Main;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.OfflinePlayerHandler;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* The ThreadManager is in charge of the Threads that PlayerStats
|
||||
* can utilize. It keeps track of past and currently active Threads,
|
||||
* to ensure a Player cannot start multiple Threads at the same time
|
||||
* (thereby limiting them to one stat-lookup at a time). It also
|
||||
* passes appropriate references along to the {@link StatThread}
|
||||
* or {@link ReloadThread}, to ensure those will never run at the
|
||||
* same time.
|
||||
*/
|
||||
public final class ThreadManager {
|
||||
|
||||
private final static int threshold = 10;
|
||||
private int statThreadID;
|
||||
private int reloadThreadID;
|
||||
|
||||
private final Main main;
|
||||
private final ConfigHandler config;
|
||||
private static OutputManager outputManager;
|
||||
|
||||
private ReloadThread activatedReloadThread;
|
||||
private StatThread activatedStatThread;
|
||||
private final HashMap<String, Thread> statThreads;
|
||||
private static long lastRecordedCalcTime;
|
||||
|
||||
public ThreadManager(Main main, OutputManager outputManager) {
|
||||
this.main = main;
|
||||
this.config = ConfigHandler.getInstance();
|
||||
ThreadManager.outputManager = outputManager;
|
||||
|
||||
statThreads = new HashMap<>();
|
||||
statThreadID = 0;
|
||||
reloadThreadID = 0;
|
||||
lastRecordedCalcTime = 0;
|
||||
}
|
||||
|
||||
static int getTaskThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public static @NotNull StatAction getStatAction(StatRequest.Settings requestSettings) {
|
||||
OfflinePlayerHandler offlinePlayerHandler = OfflinePlayerHandler.getInstance();
|
||||
|
||||
ImmutableList<String> relevantPlayerNames = ImmutableList.copyOf(offlinePlayerHandler.getIncludedOfflinePlayerNames());
|
||||
ConcurrentHashMap<String, Integer> resultingStatNumbers = new ConcurrentHashMap<>(relevantPlayerNames.size());
|
||||
StatAction task = new StatAction(relevantPlayerNames, requestSettings, resultingStatNumbers);
|
||||
|
||||
MyLogger.actionCreated(relevantPlayerNames.size());
|
||||
return task;
|
||||
}
|
||||
|
||||
public static @NotNull PlayerLoadAction getPlayerLoadAction(OfflinePlayer[] playersToLoad, ConcurrentHashMap<String, UUID> mapToFill) {
|
||||
PlayerLoadAction task = new PlayerLoadAction(playersToLoad, mapToFill);
|
||||
MyLogger.actionCreated(playersToLoad != null ? playersToLoad.length : 0);
|
||||
return task;
|
||||
}
|
||||
|
||||
public void startReloadThread(CommandSender sender) {
|
||||
if (activatedReloadThread == null || !activatedReloadThread.isAlive()) {
|
||||
reloadThreadID += 1;
|
||||
|
||||
activatedReloadThread = new ReloadThread(main, outputManager, reloadThreadID, activatedStatThread, sender);
|
||||
activatedReloadThread.start();
|
||||
}
|
||||
else {
|
||||
MyLogger.logLowLevelMsg("Another reloadThread is already running! (" + activatedReloadThread.getName() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
public void startStatThread(@NotNull StatRequest<?> request) {
|
||||
statThreadID += 1;
|
||||
CommandSender sender = request.getSettings().getCommandSender();
|
||||
|
||||
if (config.limitStatRequests() && statThreads.containsKey(sender.getName())) {
|
||||
Thread runningThread = statThreads.get(sender.getName());
|
||||
|
||||
if (runningThread.isAlive()) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.REQUEST_ALREADY_RUNNING);
|
||||
} else {
|
||||
startNewStatThread(request);
|
||||
}
|
||||
} else {
|
||||
startNewStatThread(request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the duration in milliseconds of the last top-stat-lookup
|
||||
* (or of loading the offline-player-list if no look-ups have been done yet).
|
||||
*/
|
||||
public static void recordCalcTime(long time) {
|
||||
lastRecordedCalcTime = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration in milliseconds of the last top-stat-lookup
|
||||
* (or of loading the offline-player-list if no look-ups have been done yet).
|
||||
*/
|
||||
public static long getLastRecordedCalcTime() {
|
||||
return lastRecordedCalcTime;
|
||||
}
|
||||
|
||||
private void startNewStatThread(StatRequest<?> request) {
|
||||
activatedStatThread = new StatThread(outputManager, statThreadID, request, activatedReloadThread);
|
||||
statThreads.put(request.getSettings().getCommandSender().getName(), activatedStatThread);
|
||||
activatedStatThread.start();
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
package com.artemis.the.gr8.playerstats;
|
||||
package com.artemis.the.gr8.playerstats.core.sharing;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.InternalStatResult;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
@ -26,30 +25,46 @@ import static java.time.temporal.ChronoUnit.SECONDS;
|
||||
*/
|
||||
public final class ShareManager {
|
||||
|
||||
private static volatile ShareManager instance;
|
||||
private static boolean isEnabled;
|
||||
private static int waitingTime;
|
||||
private int waitingTime;
|
||||
|
||||
private static volatile AtomicInteger resultID;
|
||||
private static ConcurrentHashMap<Integer, InternalStatResult> statResultQueue;
|
||||
private static ConcurrentHashMap<String, Instant> shareTimeStamp;
|
||||
private static ArrayBlockingQueue<Integer> sharedResults;
|
||||
private volatile AtomicInteger NumberOfStoredResults;
|
||||
private ConcurrentHashMap<Integer, StoredResult> statResultQueue;
|
||||
private ConcurrentHashMap<String, Instant> shareTimeStamp;
|
||||
private ArrayBlockingQueue<Integer> sharedResults;
|
||||
|
||||
public ShareManager(ConfigHandler config) {
|
||||
updateSettings(config);
|
||||
private ShareManager() {
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
public static ShareManager getInstance() {
|
||||
ShareManager localVar = instance;
|
||||
if (localVar != null) {
|
||||
return localVar;
|
||||
}
|
||||
|
||||
synchronized (ShareManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new ShareManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
public static synchronized void updateSettings(ConfigHandler config) {
|
||||
public void updateSettings() {
|
||||
ConfigHandler config = ConfigHandler.getInstance();
|
||||
isEnabled = config.allowStatSharing() && config.useHoverText();
|
||||
waitingTime = config.getStatShareWaitingTime();
|
||||
|
||||
if (isEnabled) {
|
||||
sharedResults = new ArrayBlockingQueue<>(500); //reset the sharedResultsQueue
|
||||
if (resultID == null) { //if we went from disabled to enabled, initialize
|
||||
resultID = new AtomicInteger(); //always starts with value 0
|
||||
if (NumberOfStoredResults == null) { //if we went from disabled to enabled, initialize
|
||||
NumberOfStoredResults = new AtomicInteger(); //always starts with value 0
|
||||
statResultQueue = new ConcurrentHashMap<>();
|
||||
shareTimeStamp = new ConcurrentHashMap<>();
|
||||
}
|
||||
@ -75,8 +90,7 @@ public final class ShareManager {
|
||||
removeExcessResults(playerName);
|
||||
|
||||
int ID = getNextIDNumber();
|
||||
//UUID shareCode = UUID.randomUUID();
|
||||
InternalStatResult result = new InternalStatResult(playerName, statResult, ID);
|
||||
StoredResult result = new StoredResult(playerName, statResult, ID);
|
||||
int shareCode = result.hashCode();
|
||||
statResultQueue.put(shareCode, result);
|
||||
MyLogger.logMediumLevelMsg("Saving statResults with no. " + ID);
|
||||
@ -103,7 +117,7 @@ public final class ShareManager {
|
||||
* and returns the formattedComponent. If no formattedComponent was found,
|
||||
* returns null.
|
||||
*/
|
||||
public @Nullable InternalStatResult getStatResult(String playerName, int shareCode) {
|
||||
public @Nullable StoredResult getStatResult(String playerName, int shareCode) {
|
||||
if (statResultQueue.containsKey(shareCode)) {
|
||||
shareTimeStamp.put(playerName, Instant.now());
|
||||
|
||||
@ -134,7 +148,7 @@ public final class ShareManager {
|
||||
* StatResults saved, remove the oldest one.
|
||||
*/
|
||||
private void removeExcessResults(String playerName) {
|
||||
List<InternalStatResult> alreadySavedResults = statResultQueue.values()
|
||||
List<StoredResult> alreadySavedResults = statResultQueue.values()
|
||||
.parallelStream()
|
||||
.filter(result -> result.executorName().equalsIgnoreCase(playerName))
|
||||
.toList();
|
||||
@ -142,7 +156,7 @@ public final class ShareManager {
|
||||
if (alreadySavedResults.size() > 25) {
|
||||
int hashCode = alreadySavedResults
|
||||
.parallelStream()
|
||||
.min(Comparator.comparing(InternalStatResult::ID))
|
||||
.min(Comparator.comparing(StoredResult::ID))
|
||||
.orElseThrow().hashCode();
|
||||
MyLogger.logMediumLevelMsg("Removing old stat no. " + statResultQueue.get(hashCode).ID() + " for player " + playerName);
|
||||
statResultQueue.remove(hashCode);
|
||||
@ -150,6 +164,6 @@ public final class ShareManager {
|
||||
}
|
||||
|
||||
private int getNextIDNumber() {
|
||||
return resultID.incrementAndGet();
|
||||
return NumberOfStoredResults.incrementAndGet();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.artemis.the.gr8.playerstats.core.sharing;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
/**
|
||||
* This Record is used to store stat-results internally,
|
||||
* so Players can share them by clicking a share-button.
|
||||
*/
|
||||
public record StoredResult(String executorName, TextComponent formattedValue, int ID) {
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.artemis.the.gr8.playerstats.core.statrequest;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.OfflinePlayerHandler;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class PlayerStatRequest extends StatRequest<Integer> implements RequestGenerator<Integer> {
|
||||
|
||||
public PlayerStatRequest(String playerName) {
|
||||
this(Bukkit.getConsoleSender(), playerName);
|
||||
}
|
||||
|
||||
public PlayerStatRequest(CommandSender sender, String playerName) {
|
||||
super(sender);
|
||||
super.configureForPlayer(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
if (!hasValidTarget()) {
|
||||
return false;
|
||||
}
|
||||
return super.hasMatchingSubStat();
|
||||
}
|
||||
|
||||
private boolean hasValidTarget() {
|
||||
StatRequest.Settings settings = super.getSettings();
|
||||
if (settings.getPlayerName() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OfflinePlayerHandler offlinePlayerHandler = OfflinePlayerHandler.getInstance();
|
||||
if (offlinePlayerHandler.isExcludedPlayer(settings.getPlayerName())) {
|
||||
return ConfigHandler.getInstance().allowPlayerLookupsForExcludedPlayers();
|
||||
} else {
|
||||
return offlinePlayerHandler.isIncludedPlayer(settings.getPlayerName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest<Integer> untyped(@NotNull Statistic statistic) {
|
||||
super.configureUntyped(statistic);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest<Integer> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
super.configureBlockOrItemType(statistic, material);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest<Integer> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
super.configureEntityType(statistic, entityType);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
package com.artemis.the.gr8.playerstats.core.statrequest;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.api.StatManager;
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import com.artemis.the.gr8.playerstats.api.StatResult;
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.msgutils.FormattingFunction;
|
||||
import com.artemis.the.gr8.playerstats.core.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.core.multithreading.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.core.sharing.ShareManager;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.core.utils.OfflinePlayerHandler;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Turns user input into a {@link StatRequest} that can be
|
||||
* executed to get statistic data.
|
||||
*/
|
||||
public final class RequestManager implements StatManager {
|
||||
|
||||
private static RequestProcessor processor;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
public RequestManager(OutputManager outputManager) {
|
||||
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
|
||||
processor = new RequestProcessor(outputManager);
|
||||
}
|
||||
|
||||
public static StatResult<?> execute(@NotNull StatRequest<?> request) {
|
||||
return switch (request.getSettings().getTarget()) {
|
||||
case PLAYER -> processor.processPlayerRequest(request.getSettings());
|
||||
case SERVER -> processor.processServerRequest(request.getSettings());
|
||||
case TOP -> processor.processTopRequest(request.getSettings());
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExcludedPlayer(String playerName) {
|
||||
return offlinePlayerHandler.isExcludedPlayer(playerName);
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
@Override
|
||||
public @NotNull RequestGenerator<Integer> createPlayerStatRequest(String playerName) {
|
||||
return new PlayerStatRequest(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull StatResult<Integer> executePlayerStatRequest(@NotNull StatRequest<Integer> request) {
|
||||
return processor.processPlayerRequest(request.getSettings());
|
||||
}
|
||||
|
||||
@Contract(" -> new")
|
||||
@Override
|
||||
public @NotNull RequestGenerator<Long> createServerStatRequest() {
|
||||
return new ServerStatRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull StatResult<Long> executeServerStatRequest(@NotNull StatRequest<Long> request) {
|
||||
return processor.processServerRequest(request.getSettings());
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
@Override
|
||||
public @NotNull RequestGenerator<LinkedHashMap<String, Integer>> createTopStatRequest(int topListSize) {
|
||||
return new TopStatRequest(topListSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RequestGenerator<LinkedHashMap<String, Integer>> createTotalTopStatRequest() {
|
||||
int playerCount = offlinePlayerHandler.getIncludedPlayerCount();
|
||||
return createTopStatRequest(playerCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull StatResult<LinkedHashMap<String, Integer>> executeTopRequest(@NotNull StatRequest<LinkedHashMap<String, Integer>> request) {
|
||||
return processor.processTopRequest(request.getSettings());
|
||||
}
|
||||
|
||||
private final class RequestProcessor {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static OutputManager outputManager;
|
||||
private static ShareManager shareManager;
|
||||
|
||||
public RequestProcessor(OutputManager outputManager) {
|
||||
RequestProcessor.config = ConfigHandler.getInstance();
|
||||
RequestProcessor.outputManager = outputManager;
|
||||
RequestProcessor.shareManager = ShareManager.getInstance();
|
||||
}
|
||||
|
||||
public @NotNull StatResult<Integer> processPlayerRequest(StatRequest.Settings requestSettings) {
|
||||
int stat = getPlayerStat(requestSettings);
|
||||
FormattingFunction formattingFunction = outputManager.formatPlayerStat(requestSettings, stat);
|
||||
TextComponent formattedResult = processFunction(requestSettings.getCommandSender(), formattingFunction);
|
||||
String resultAsString = outputManager.textComponentToString(formattedResult);
|
||||
|
||||
return new StatResult<>(stat, formattedResult, resultAsString);
|
||||
}
|
||||
|
||||
public @NotNull StatResult<Long> processServerRequest(StatRequest.Settings requestSettings) {
|
||||
long stat = getServerStat(requestSettings);
|
||||
FormattingFunction formattingFunction = outputManager.formatServerStat(requestSettings, stat);
|
||||
TextComponent formattedResult = processFunction(requestSettings.getCommandSender(), formattingFunction);
|
||||
String resultAsString = outputManager.textComponentToString(formattedResult);
|
||||
|
||||
return new StatResult<>(stat, formattedResult, resultAsString);
|
||||
}
|
||||
|
||||
public @NotNull StatResult<LinkedHashMap<String, Integer>> processTopRequest(StatRequest.Settings requestSettings) {
|
||||
LinkedHashMap<String, Integer> stats = getTopStats(requestSettings);
|
||||
FormattingFunction formattingFunction = outputManager.formatTopStats(requestSettings, stats);
|
||||
TextComponent formattedResult = processFunction(requestSettings.getCommandSender(), formattingFunction);
|
||||
String resultAsString = outputManager.textComponentToString(formattedResult);
|
||||
|
||||
return new StatResult<>(stats, formattedResult, resultAsString);
|
||||
}
|
||||
|
||||
private int getPlayerStat(@NotNull StatRequest.Settings requestSettings) {
|
||||
OfflinePlayer player;
|
||||
if (offlinePlayerHandler.isExcludedPlayer(requestSettings.getPlayerName()) &&
|
||||
config.allowPlayerLookupsForExcludedPlayers()) {
|
||||
player = offlinePlayerHandler.getExcludedOfflinePlayer(requestSettings.getPlayerName());
|
||||
} else {
|
||||
player = offlinePlayerHandler.getIncludedOfflinePlayer(requestSettings.getPlayerName());
|
||||
}
|
||||
return switch (requestSettings.getStatistic().getType()) {
|
||||
case UNTYPED -> player.getStatistic(requestSettings.getStatistic());
|
||||
case ENTITY -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getEntity());
|
||||
case BLOCK -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getBlock());
|
||||
case ITEM -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getItem());
|
||||
};
|
||||
}
|
||||
|
||||
private long getServerStat(StatRequest.Settings requestSettings) {
|
||||
List<Integer> numbers = getAllStatsAsync(requestSettings)
|
||||
.values()
|
||||
.parallelStream()
|
||||
.toList();
|
||||
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, Integer> getTopStats(StatRequest.Settings requestSettings) {
|
||||
return getAllStatsAsync(requestSettings).entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||
.limit(requestSettings.getTopListSize())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private TextComponent processFunction(CommandSender sender, FormattingFunction function) {
|
||||
if (outputShouldBeStored(sender)) {
|
||||
int shareCode = shareManager.saveStatResult(sender.getName(), function.getResultWithSharerName(sender));
|
||||
return function.getResultWithShareButton(shareCode);
|
||||
}
|
||||
return function.getDefaultResult();
|
||||
}
|
||||
|
||||
private boolean outputShouldBeStored(CommandSender sender) {
|
||||
return !(sender instanceof ConsoleCommandSender) &&
|
||||
shareManager.isEnabled() &&
|
||||
shareManager.senderHasPermission(sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a bunch of worker pool threads to get the statistics for
|
||||
* all players that are stored in the {@link OfflinePlayerHandler}).
|
||||
*/
|
||||
private @NotNull ConcurrentHashMap<String, Integer> getAllStatsAsync(StatRequest.Settings requestSettings) {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
ForkJoinPool commonPool = ForkJoinPool.commonPool();
|
||||
ConcurrentHashMap<String, Integer> allStats;
|
||||
|
||||
try {
|
||||
allStats = commonPool.invoke(ThreadManager.getStatAction(requestSettings));
|
||||
} catch (ConcurrentModificationException e) {
|
||||
MyLogger.logWarning("The requestSettings 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!");
|
||||
throw new ConcurrentModificationException(e.toString());
|
||||
}
|
||||
|
||||
MyLogger.actionFinished();
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
MyLogger.logMediumLevelTask("Calculated all stats", time);
|
||||
|
||||
return allStats;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.artemis.the.gr8.playerstats.core.statrequest;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ServerStatRequest extends StatRequest<Long> implements RequestGenerator<Long> {
|
||||
|
||||
|
||||
public ServerStatRequest() {
|
||||
this(Bukkit.getConsoleSender());
|
||||
}
|
||||
|
||||
public ServerStatRequest(CommandSender sender) {
|
||||
super(sender);
|
||||
super.configureForServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return super.hasMatchingSubStat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest<Long> untyped(@NotNull Statistic statistic) {
|
||||
super.configureUntyped(statistic);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest<Long> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
super.configureBlockOrItemType(statistic, material);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest<Long> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
super.configureEntityType(statistic, entityType);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.artemis.the.gr8.playerstats.core.statrequest;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.api.StatRequest;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public final class TopStatRequest extends StatRequest<LinkedHashMap<String, Integer>> implements RequestGenerator<LinkedHashMap<String, Integer>> {
|
||||
|
||||
public TopStatRequest(int topListSize) {
|
||||
this(Bukkit.getConsoleSender(), topListSize);
|
||||
}
|
||||
|
||||
public TopStatRequest(CommandSender sender, int topListSize) {
|
||||
super(sender);
|
||||
super.configureForTop(topListSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return super.hasMatchingSubStat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest<LinkedHashMap<String, Integer>> untyped(@NotNull Statistic statistic) {
|
||||
super.configureUntyped(statistic);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest<LinkedHashMap<String, Integer>> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
super.configureBlockOrItemType(statistic, material);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest<LinkedHashMap<String, Integer>> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
super.configureEntityType(statistic, entityType);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.artemis.the.gr8.playerstats.utils;
|
||||
package com.artemis.the.gr8.playerstats.core.utils;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
@ -22,21 +22,36 @@ import java.util.stream.Stream;
|
||||
*/
|
||||
public final class EnumHandler {
|
||||
|
||||
private static volatile EnumHandler instance;
|
||||
private static List<String> blockNames;
|
||||
private static List<String> itemNames;
|
||||
private static List<String> statNames;
|
||||
private static List<String> subStatNames;
|
||||
|
||||
public EnumHandler() {
|
||||
private EnumHandler() {
|
||||
prepareLists();
|
||||
}
|
||||
|
||||
public static EnumHandler getInstance() {
|
||||
EnumHandler localVar = instance;
|
||||
if (localVar != null) {
|
||||
return localVar;
|
||||
}
|
||||
|
||||
synchronized (EnumHandler.class) {
|
||||
if (instance == null) {
|
||||
instance = new EnumHandler();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all block-names in lowercase.
|
||||
*
|
||||
* @return the List
|
||||
*/
|
||||
public List<String> getBlockNames() {
|
||||
public List<String> getAllBlockNames() {
|
||||
return blockNames;
|
||||
}
|
||||
|
||||
@ -45,7 +60,7 @@ public final class EnumHandler {
|
||||
*
|
||||
* @return the List
|
||||
*/
|
||||
public List<String> getItemNames() {
|
||||
public List<String> getAllItemNames() {
|
||||
return itemNames;
|
||||
}
|
||||
|
||||
@ -54,7 +69,7 @@ public final class EnumHandler {
|
||||
*
|
||||
* @return the List
|
||||
*/
|
||||
public List<String> getStatNames() {
|
||||
public List<String> getAllStatNames() {
|
||||
return statNames;
|
||||
}
|
||||
|
||||
@ -65,7 +80,7 @@ public final class EnumHandler {
|
||||
* @return Material enum constant (uppercase), or null if none
|
||||
* can be found
|
||||
*/
|
||||
public static @Nullable Material getItemEnum(String itemName) {
|
||||
public @Nullable Material getItemEnum(String itemName) {
|
||||
if (itemName == null) return null;
|
||||
|
||||
Material item = Material.matchMaterial(itemName);
|
||||
@ -79,7 +94,7 @@ public final class EnumHandler {
|
||||
* @return EntityType enum constant (uppercase), or null if none
|
||||
* can be found
|
||||
*/
|
||||
public static @Nullable EntityType getEntityEnum(String entityName) {
|
||||
public @Nullable EntityType getEntityEnum(String entityName) {
|
||||
try {
|
||||
return EntityType.valueOf(entityName.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
@ -95,7 +110,7 @@ public final class EnumHandler {
|
||||
* @return Material enum constant (uppercase), or null if none
|
||||
* can be found
|
||||
*/
|
||||
public static @Nullable Material getBlockEnum(String materialName) {
|
||||
public @Nullable Material getBlockEnum(String materialName) {
|
||||
if (materialName == null) return null;
|
||||
|
||||
Material block = Material.matchMaterial(materialName);
|
||||
@ -108,7 +123,7 @@ public final class EnumHandler {
|
||||
* @param statName String (case-insensitive)
|
||||
* @return the Statistic enum constant, or null
|
||||
*/
|
||||
public static @Nullable Statistic getStatEnum(@NotNull String statName) {
|
||||
public @Nullable Statistic getStatEnum(@NotNull String statName) {
|
||||
try {
|
||||
return Statistic.valueOf(statName.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
@ -134,7 +149,7 @@ public final class EnumHandler {
|
||||
* @param statName the String to check (case-insensitive)
|
||||
* @return true if this String is a Statistic of Type.Entity
|
||||
*/
|
||||
public boolean isEntityStatistic(String statName) {
|
||||
public boolean isEntityStatistic(@NotNull String statName) {
|
||||
return statName.equalsIgnoreCase(Statistic.ENTITY_KILLED_BY.toString()) ||
|
||||
statName.equalsIgnoreCase(Statistic.KILL_ENTITY.toString());
|
||||
}
|
||||
@ -158,7 +173,7 @@ public final class EnumHandler {
|
||||
* @return "block", "entity", "item", or "sub-statistic" if the
|
||||
* provided Type is null.
|
||||
*/
|
||||
public static String getSubStatTypeName(Statistic.Type statType) {
|
||||
public String getSubStatTypeName(Statistic.Type statType) {
|
||||
String subStat = "sub-statistic";
|
||||
if (statType == null) return subStat;
|
||||
switch (statType) {
|
@ -0,0 +1,109 @@
|
||||
package com.artemis.the.gr8.playerstats.core.utils;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.core.Main;
|
||||
import com.tchristofferson.configupdater.ConfigUpdater;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class FileHandler {
|
||||
|
||||
private final String fileName;
|
||||
private File file;
|
||||
private FileConfiguration fileConfiguration;
|
||||
|
||||
public FileHandler(String fileName) {
|
||||
this.fileName = fileName;
|
||||
loadFile();
|
||||
}
|
||||
|
||||
private void loadFile() {
|
||||
JavaPlugin plugin = Main.getPluginInstance();
|
||||
|
||||
file = new File(plugin.getDataFolder(), fileName);
|
||||
if (!file.exists()) {
|
||||
plugin.saveResource(fileName, false);
|
||||
}
|
||||
fileConfiguration = YamlConfiguration.loadConfiguration(file);
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
if (!file.exists()) {
|
||||
loadFile();
|
||||
} else {
|
||||
fileConfiguration = YamlConfiguration.loadConfiguration(file);
|
||||
MyLogger.logLowLevelMsg(fileName + " reloaded!");
|
||||
}
|
||||
}
|
||||
|
||||
public FileConfiguration getFileConfiguration() {
|
||||
return fileConfiguration;
|
||||
}
|
||||
|
||||
public void addValues(@NotNull Map<String, Object> keyValuePairs) {
|
||||
keyValuePairs.forEach(this::setValue);
|
||||
save();
|
||||
updateFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key the Key under which the List will be stored
|
||||
* (or expanded if it already exists)
|
||||
* @param value the value(s) to expand the List with
|
||||
*/
|
||||
public void writeEntryToList(@NotNull String key, @NotNull String value) {
|
||||
List<String> existingList = fileConfiguration.getStringList(key);
|
||||
|
||||
List<String> updatedList = existingList.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
updatedList.add(value);
|
||||
|
||||
setValue(key, updatedList);
|
||||
save();
|
||||
updateFile();
|
||||
}
|
||||
|
||||
public void removeEntryFromList(@NotNull String key, @NotNull String value) {
|
||||
List<String> currentValues = fileConfiguration.getStringList(key);
|
||||
|
||||
if (currentValues.remove(value)) {
|
||||
setValue(key, currentValues);
|
||||
save();
|
||||
updateFile();
|
||||
}
|
||||
}
|
||||
|
||||
private void setValue(String key, Object value) {
|
||||
fileConfiguration.set(key, value);
|
||||
}
|
||||
|
||||
private void save() {
|
||||
try {
|
||||
fileConfiguration.save(file);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new key-value pairs to the config without losing comments,
|
||||
* using <a href="https://github.com/tchristofferson/Config-Updater">tchristofferson's Config-Updater</a>
|
||||
*/
|
||||
private void updateFile() {
|
||||
JavaPlugin plugin = Main.getPluginInstance();
|
||||
try {
|
||||
ConfigUpdater.update(plugin, fileName, file);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.artemis.the.gr8.playerstats.utils;
|
||||
package com.artemis.the.gr8.playerstats.core.utils;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import com.artemis.the.gr8.playerstats.core.enums.DebugLevel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -53,12 +53,22 @@ public final class MyLogger {
|
||||
logger.info(content);
|
||||
}
|
||||
|
||||
public static void logLowLevelTask(String taskName, long startTime) {
|
||||
printTime(taskName, startTime);
|
||||
}
|
||||
|
||||
public static void logMediumLevelMsg(String content) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
logger.info(content);
|
||||
}
|
||||
}
|
||||
|
||||
public static void logMediumLevelTask(String taskName, long startTime) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
printTime(taskName, startTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static void logHighLevelMsg(String content) {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
logger.info(content);
|
||||
@ -146,24 +156,13 @@ public final class MyLogger {
|
||||
}
|
||||
}
|
||||
|
||||
public static void logMediumLevelTask(String className, String methodName, long startTime) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
printTime(className, methodName, startTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static void logLowLevelTask(String className, String methodName, long startTime) {
|
||||
printTime(className, methodName, startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output to console how long a certain task has taken.
|
||||
*
|
||||
* @param className Name of the class executing the task
|
||||
* @param methodName Name or description of the task
|
||||
* @param taskName name of the task that has been executed
|
||||
* @param startTime Timestamp marking the beginning of the task
|
||||
*/
|
||||
private static void printTime(String className, String methodName, long startTime) {
|
||||
logger.info(className + " " + methodName + ": " + (System.currentTimeMillis() - startTime) + "ms");
|
||||
private static void printTime(String taskName, long startTime) {
|
||||
logger.info(taskName + " (" + (System.currentTimeMillis() - startTime) + "ms)");
|
||||
}
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
package com.artemis.the.gr8.playerstats.core.utils;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.core.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.core.multithreading.ThreadManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* A utility class that deals with OfflinePlayers. It stores a list
|
||||
* of all OfflinePlayer-names that need to be included in statistic
|
||||
* calculations, and can retrieve the corresponding OfflinePlayer
|
||||
* object for a given player-name.
|
||||
*/
|
||||
public final class OfflinePlayerHandler extends FileHandler {
|
||||
|
||||
private static volatile OfflinePlayerHandler instance;
|
||||
private final ConfigHandler config;
|
||||
private static ConcurrentHashMap<String, UUID> includedPlayerUUIDs;
|
||||
private static ConcurrentHashMap<String, UUID> excludedPlayerUUIDs;
|
||||
|
||||
private OfflinePlayerHandler() {
|
||||
super("excluded_players.yml");
|
||||
config = ConfigHandler.getInstance();
|
||||
|
||||
loadOfflinePlayers();
|
||||
}
|
||||
|
||||
public static OfflinePlayerHandler getInstance() {
|
||||
OfflinePlayerHandler localVar = instance;
|
||||
if (localVar != null) {
|
||||
return localVar;
|
||||
}
|
||||
|
||||
synchronized (OfflinePlayerHandler.class) {
|
||||
if (instance == null) {
|
||||
instance = new OfflinePlayerHandler();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
super.reload();
|
||||
loadOfflinePlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given player is currently
|
||||
* included for /statistic lookups.
|
||||
*
|
||||
* @param playerName String (case-sensitive)
|
||||
* @return true if this player is included
|
||||
*/
|
||||
public boolean isIncludedPlayer(String playerName) {
|
||||
return includedPlayerUUIDs.containsKey(playerName);
|
||||
}
|
||||
|
||||
public boolean isExcludedPlayer(String playerName) {
|
||||
return excludedPlayerUUIDs.containsKey(playerName);
|
||||
}
|
||||
|
||||
public boolean isExcludedPlayer(UUID uniqueID) {
|
||||
return excludedPlayerUUIDs.containsValue(uniqueID);
|
||||
}
|
||||
|
||||
public boolean addPlayerToExcludeList(String playerName) {
|
||||
if (isIncludedPlayer(playerName)) {
|
||||
UUID uuid = includedPlayerUUIDs.get(playerName);
|
||||
|
||||
super.writeEntryToList("excluded", uuid.toString());
|
||||
includedPlayerUUIDs.remove(playerName);
|
||||
excludedPlayerUUIDs.put(playerName, uuid);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean removePlayerFromExcludeList(String playerName) {
|
||||
if (isExcludedPlayer(playerName)) {
|
||||
UUID uuid = excludedPlayerUUIDs.get(playerName);
|
||||
|
||||
super.removeEntryFromList("excluded", uuid.toString());
|
||||
excludedPlayerUUIDs.remove(playerName);
|
||||
includedPlayerUUIDs.put(playerName, uuid);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Contract(" -> new")
|
||||
public @NotNull ArrayList<String> getExcludedPlayerNames() {
|
||||
return Collections.list(excludedPlayerUUIDs.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an ArrayList of names from all OfflinePlayers that should
|
||||
* be included in statistic calculations.
|
||||
*
|
||||
* @return the ArrayList
|
||||
*/
|
||||
@Contract(" -> new")
|
||||
public @NotNull ArrayList<String> getIncludedOfflinePlayerNames() {
|
||||
return Collections.list(includedPlayerUUIDs.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of OfflinePlayers that are
|
||||
* currently included in statistic calculations.
|
||||
*
|
||||
* @return the number of included OfflinePlayers
|
||||
*/
|
||||
public int getIncludedPlayerCount() {
|
||||
return includedPlayerUUIDs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the playerName to get the player's UUID from a private HashMap,
|
||||
* and uses the UUID to get the corresponding OfflinePlayer Object.
|
||||
*
|
||||
* @param playerName name of the target player (case-sensitive)
|
||||
* @return OfflinePlayer
|
||||
* @throws IllegalArgumentException if this player is not on the list
|
||||
* of players that should be included in statistic calculations
|
||||
*/
|
||||
public @NotNull OfflinePlayer getIncludedOfflinePlayer(String playerName) throws IllegalArgumentException {
|
||||
if (includedPlayerUUIDs.get(playerName) != null) {
|
||||
return Bukkit.getOfflinePlayer(includedPlayerUUIDs.get(playerName));
|
||||
}
|
||||
else {
|
||||
MyLogger.logWarning("Cannot calculate statistics for player-name: " + playerName +
|
||||
"! Double-check if the name is spelled correctly (including capital letters), " +
|
||||
"or if any of your config settings exclude them");
|
||||
throw new IllegalArgumentException("PlayerStats does not know a player by this name");
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull OfflinePlayer getExcludedOfflinePlayer(String playerName) throws IllegalArgumentException {
|
||||
if (excludedPlayerUUIDs.get(playerName) != null) {
|
||||
return Bukkit.getOfflinePlayer(excludedPlayerUUIDs.get(playerName));
|
||||
}
|
||||
throw new IllegalArgumentException("There is no player on the exclude-list with this name");
|
||||
}
|
||||
|
||||
private void loadOfflinePlayers() {
|
||||
Executors.newSingleThreadExecutor().execute(() -> {
|
||||
loadExcludedPlayerNames();
|
||||
loadIncludedOfflinePlayers();
|
||||
});
|
||||
}
|
||||
|
||||
private void loadIncludedOfflinePlayers() {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
OfflinePlayer[] offlinePlayers;
|
||||
if (config.whitelistOnly()) {
|
||||
offlinePlayers = getWhitelistedPlayers();
|
||||
} else if (config.excludeBanned()) {
|
||||
offlinePlayers = getNonBannedPlayers();
|
||||
} else {
|
||||
offlinePlayers = Bukkit.getOfflinePlayers();
|
||||
}
|
||||
|
||||
int size = includedPlayerUUIDs != null ? includedPlayerUUIDs.size() : 16;
|
||||
includedPlayerUUIDs = new ConcurrentHashMap<>(size);
|
||||
|
||||
ForkJoinPool.commonPool().invoke(ThreadManager.getPlayerLoadAction(offlinePlayers, includedPlayerUUIDs));
|
||||
|
||||
MyLogger.actionFinished();
|
||||
MyLogger.logLowLevelTask(("Loaded " + includedPlayerUUIDs.size() + " offline players"), time);
|
||||
}
|
||||
|
||||
private void loadExcludedPlayerNames() {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
excludedPlayerUUIDs = new ConcurrentHashMap<>();
|
||||
List<String> excluded = super.getFileConfiguration().getStringList("excluded");
|
||||
excluded.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(UUID::fromString)
|
||||
.forEach(uuid -> {
|
||||
OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
|
||||
String playerName = player.getName();
|
||||
if (playerName != null) {
|
||||
excludedPlayerUUIDs.put(playerName, uuid);
|
||||
}
|
||||
});
|
||||
|
||||
MyLogger.logLowLevelTask("Loaded " + excludedPlayerUUIDs.size() + " excluded players from file", time);
|
||||
}
|
||||
|
||||
private OfflinePlayer[] getWhitelistedPlayers() {
|
||||
return Bukkit.getWhitelistedPlayers().toArray(OfflinePlayer[]::new);
|
||||
}
|
||||
|
||||
private @NotNull OfflinePlayer[] getNonBannedPlayers() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("LiteBans")) {
|
||||
return Arrays.stream(Bukkit.getOfflinePlayers())
|
||||
.parallel()
|
||||
.filter(Predicate.not(OfflinePlayer::isBanned))
|
||||
.toArray(OfflinePlayer[]::new);
|
||||
}
|
||||
|
||||
Set<OfflinePlayer> banList = Bukkit.getBannedPlayers();
|
||||
return Arrays.stream(Bukkit.getOfflinePlayers())
|
||||
.parallel()
|
||||
.filter(Predicate.not(banList::contains))
|
||||
.toArray(OfflinePlayer[]::new);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.artemis.the.gr8.playerstats.utils;
|
||||
package com.artemis.the.gr8.playerstats.core.utils;
|
||||
|
||||
/**
|
||||
* A small utility class that calculates with unix time.
|
@ -1,155 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.enums;
|
||||
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This enum represents the colorscheme PlayerStats uses in its output messages.
|
||||
* The first set of colors is used throughout the plugin, while the set of NAME-colors
|
||||
* represents the colors that player-names can be in the "shared by player-name"
|
||||
* section of shared statistics
|
||||
*/
|
||||
public enum PluginColor {
|
||||
/**
|
||||
* ChatColor Gray (#AAAAAA)
|
||||
*/
|
||||
GRAY (NamedTextColor.GRAY),
|
||||
|
||||
/**
|
||||
* A Dark Purple that is mainly used for title-underscores (#6E3485).
|
||||
*/
|
||||
DARK_PURPLE (TextColor.fromHexString("#6E3485")),
|
||||
|
||||
/**
|
||||
* A Light Purple that is meant to simulate the color of a clicked link.
|
||||
* Used for the "Hover Here" part of shared statistics (#845EC2)
|
||||
* */
|
||||
LIGHT_PURPLE (TextColor.fromHexString("#845EC2")),
|
||||
|
||||
/**
|
||||
* ChatColor Blue (#5555FF)
|
||||
*/
|
||||
BLUE (NamedTextColor.BLUE),
|
||||
|
||||
/**
|
||||
* A Medium Blue that is used for default feedback and error messages (#55AAFF).
|
||||
*/
|
||||
MEDIUM_BLUE (TextColor.fromHexString("#55AAFF")),
|
||||
|
||||
/**
|
||||
* A Light Blue that is used for hover-messages and the share-button (#55C6FF).
|
||||
*/
|
||||
LIGHT_BLUE (TextColor.fromHexString("#55C6FF")),
|
||||
|
||||
/**
|
||||
* ChatColor Gold (#FFAA00)
|
||||
*/
|
||||
GOLD (NamedTextColor.GOLD),
|
||||
|
||||
/**
|
||||
* A Medium Gold that is used for the example message and for hover-text accents (#FFD52B).
|
||||
*/
|
||||
MEDIUM_GOLD (TextColor.fromHexString("#FFD52B")),
|
||||
|
||||
/**
|
||||
* A Light Gold that is used for the example message and for hover-text accents (#FFEA40).
|
||||
*/
|
||||
LIGHT_GOLD (TextColor.fromHexString("#FFEA40")),
|
||||
|
||||
/**
|
||||
* A Light Yellow that is used for final accents in the example message (#FFFF8E).
|
||||
*/
|
||||
LIGHT_YELLOW (TextColor.fromHexString("#FFFF8E")),
|
||||
|
||||
/**
|
||||
* The color of vanilla Minecraft hearts (#FF1313).
|
||||
*/
|
||||
RED (TextColor.fromHexString("#FF1313")),
|
||||
|
||||
/**
|
||||
* ChatColor Blue (#5555FF)
|
||||
*/
|
||||
NAME_1 (NamedTextColor.BLUE), //#5555FF - blue
|
||||
|
||||
/**
|
||||
* A shade of blue between Blue and Medium Blue (#4287F5)
|
||||
*/
|
||||
NAME_2 (TextColor.fromHexString("#4287F5")),
|
||||
|
||||
/**
|
||||
* Medium Blue (#55AAFF)
|
||||
*/
|
||||
NAME_3 (TextColor.fromHexString("#55AAFF")),
|
||||
|
||||
/**
|
||||
* A shade of magenta/purple (#D65DB1)
|
||||
*/
|
||||
NAME_4 (TextColor.fromHexString("#D65DB1")),
|
||||
|
||||
/**
|
||||
* A dark shade of orange (#EE8A19)
|
||||
*/
|
||||
NAME_5 (TextColor.fromHexString("#EE8A19")),
|
||||
|
||||
/**
|
||||
* A shade of green/aqua/cyan-ish (#01C1A7)
|
||||
*/
|
||||
NAME_6 (TextColor.fromHexString("#01C1A7")),
|
||||
|
||||
/**
|
||||
* A light shade of green (#46D858)
|
||||
*/
|
||||
NAME_7 (TextColor.fromHexString("#46D858"));
|
||||
|
||||
|
||||
private final TextColor color;
|
||||
|
||||
PluginColor(TextColor color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TextColor value belonging to the corresponding enum constant.
|
||||
*/
|
||||
public TextColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest NamedTextColor for the corresponding enum constant.
|
||||
*/
|
||||
public TextColor getConsoleColor() {
|
||||
return NamedTextColor.nearestTo(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly selects one of the 7 different NAME-colors.
|
||||
*/
|
||||
public static TextColor getRandomNameColor() {
|
||||
return getRandomNameColor(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly selects one of the 7 different NAME-colors, and if isConsole is true,
|
||||
* returns the closest NamedTextColor
|
||||
*/
|
||||
public static TextColor getRandomNameColor(boolean isConsole) {
|
||||
Random randomizer = new Random();
|
||||
PluginColor color = switch (randomizer.nextInt(7)) {
|
||||
case 0 -> NAME_1;
|
||||
case 2 -> NAME_3;
|
||||
case 3 -> NAME_4;
|
||||
case 4 -> NAME_5;
|
||||
case 5 -> NAME_6;
|
||||
case 6 -> NAME_7;
|
||||
default -> NAME_2;
|
||||
};
|
||||
return getCorrespondingColor(color, isConsole);
|
||||
}
|
||||
|
||||
private static TextColor getCorrespondingColor(PluginColor nameColor, boolean isConsole) {
|
||||
return isConsole ? nameColor.getConsoleColor() : nameColor.getColor();
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import net.kyori.adventure.text.*;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/** The {@link InternalFormatter} formats raw numbers into pretty messages.
|
||||
* This Formatter takes a {@link RequestSettings} object and combines it
|
||||
* with the raw data returned by the {@link StatCalculator}, and transforms
|
||||
* those into a pretty message with all the relevant information in it.
|
||||
* @see MessageBuilder
|
||||
*/
|
||||
@Internal
|
||||
public interface InternalFormatter {
|
||||
|
||||
/** @return a TextComponent with the following parts:
|
||||
* <br>[player-name]: [number] [stat-name] {sub-stat-name}
|
||||
*/
|
||||
TextComponent formatAndSavePlayerStat(RequestSettings requestSettings, int playerStat);
|
||||
|
||||
/** @return a TextComponent with the following parts:
|
||||
* <br>[Total on] [server-name]: [number] [stat-name] [sub-stat-name]
|
||||
*/
|
||||
TextComponent formatAndSaveServerStat(RequestSettings requestSettings, long serverStat);
|
||||
|
||||
/** @return a TextComponent with the following parts:
|
||||
* <br>[PlayerStats] [Top 10] [stat-name] [sub-stat-name]
|
||||
* <br> [1.] [player-name] [number]
|
||||
* <br> [2.] [player-name] [number]
|
||||
* <br> [3.] etc...
|
||||
*/
|
||||
TextComponent formatAndSaveTopStat(RequestSettings requestSettings, LinkedHashMap<String, Integer> topStats);
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ShareManager;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.BukkitConsoleComponentFactory;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.PrideComponentFactory;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.artemis.the.gr8.playerstats.enums.StandardMessage.*;
|
||||
|
||||
/**
|
||||
* This class manages all PlayerStats output. It is the only
|
||||
* place where messages are sent. It gets its messages from a
|
||||
* {@link MessageBuilder} configured for either a Console or
|
||||
* for Players (mainly to deal with the lack of hover-text,
|
||||
* and for Bukkit consoles to make up for the lack of hex-colors).
|
||||
*/
|
||||
public final class OutputManager implements InternalFormatter {
|
||||
|
||||
private static BukkitAudiences adventure;
|
||||
private static ConfigHandler config;
|
||||
private static ShareManager shareManager;
|
||||
private static MessageBuilder messageBuilder;
|
||||
private static MessageBuilder consoleMessageBuilder;
|
||||
|
||||
private static EnumMap<StandardMessage, Function<MessageBuilder, TextComponent>> standardMessages;
|
||||
|
||||
public OutputManager(BukkitAudiences adventure, ConfigHandler config, ShareManager shareManager) {
|
||||
OutputManager.adventure = adventure;
|
||||
OutputManager.config = config;
|
||||
OutputManager.shareManager = shareManager;
|
||||
|
||||
getMessageBuilders();
|
||||
prepareFunctions();
|
||||
}
|
||||
|
||||
public static void updateMessageBuilders() {
|
||||
getMessageBuilders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatAndSavePlayerStat(@NotNull RequestSettings requestSettings, int playerStat) {
|
||||
BiFunction<Integer, CommandSender, TextComponent> playerStatFunction =
|
||||
getMessageBuilder(requestSettings).formattedPlayerStatFunction(playerStat, requestSettings);
|
||||
|
||||
return processFunction(requestSettings.getCommandSender(), playerStatFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatAndSaveServerStat(@NotNull RequestSettings requestSettings, long serverStat) {
|
||||
BiFunction<Integer, CommandSender, TextComponent> serverStatFunction =
|
||||
getMessageBuilder(requestSettings).formattedServerStatFunction(serverStat, requestSettings);
|
||||
|
||||
return processFunction(requestSettings.getCommandSender(), serverStatFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatAndSaveTopStat(@NotNull RequestSettings requestSettings, @NotNull LinkedHashMap<String, Integer> topStats) {
|
||||
BiFunction<Integer, CommandSender, TextComponent> topStatFunction =
|
||||
getMessageBuilder(requestSettings).formattedTopStatFunction(topStats, requestSettings);
|
||||
|
||||
return processFunction(requestSettings.getCommandSender(), topStatFunction);
|
||||
}
|
||||
|
||||
public void sendFeedbackMsg(@NotNull CommandSender sender, StandardMessage message) {
|
||||
if (message != null) {
|
||||
adventure.sender(sender).sendMessage(standardMessages.get(message)
|
||||
.apply(getMessageBuilder(sender)));
|
||||
}
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgWaitAMoment(@NotNull CommandSender sender, boolean longWait) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.waitAMoment(longWait));
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgMissingSubStat(@NotNull CommandSender sender, Statistic.Type statType) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.missingSubStatName(statType));
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgWrongSubStat(@NotNull CommandSender sender, Statistic.Type statType, @Nullable String subStatName) {
|
||||
if (subStatName == null) {
|
||||
sendFeedbackMsgMissingSubStat(sender, statType);
|
||||
} else {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.wrongSubStatType(statType, subStatName));
|
||||
}
|
||||
}
|
||||
|
||||
public void sendExamples(@NotNull CommandSender sender) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.usageExamples());
|
||||
}
|
||||
|
||||
public void sendHelp(@NotNull CommandSender sender) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.helpMsg());
|
||||
}
|
||||
|
||||
public void sendToAllPlayers(@NotNull TextComponent component) {
|
||||
adventure.players().sendMessage(component);
|
||||
}
|
||||
|
||||
public void sendToCommandSender(@NotNull CommandSender sender, @NotNull TextComponent component) {
|
||||
adventure.sender(sender).sendMessage(component);
|
||||
}
|
||||
|
||||
private TextComponent processFunction(CommandSender sender, @NotNull BiFunction<Integer, CommandSender, TextComponent> statResultFunction) {
|
||||
boolean saveOutput = !(sender instanceof ConsoleCommandSender) &&
|
||||
ShareManager.isEnabled() &&
|
||||
shareManager.senderHasPermission(sender);
|
||||
|
||||
if (saveOutput) {
|
||||
int shareCode =
|
||||
shareManager.saveStatResult(sender.getName(), statResultFunction.apply(null, sender));
|
||||
return statResultFunction.apply(shareCode, null);
|
||||
}
|
||||
else {
|
||||
return statResultFunction.apply(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageBuilder getMessageBuilder(CommandSender sender) {
|
||||
return sender instanceof ConsoleCommandSender ? consoleMessageBuilder : messageBuilder;
|
||||
}
|
||||
|
||||
private MessageBuilder getMessageBuilder(RequestSettings requestSettings) {
|
||||
if (!requestSettings.isConsoleSender()) {
|
||||
return messageBuilder;
|
||||
} else {
|
||||
return consoleMessageBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
private static void getMessageBuilders() {
|
||||
messageBuilder = getClientMessageBuilder();
|
||||
consoleMessageBuilder = getConsoleMessageBuilder();
|
||||
}
|
||||
|
||||
private static MessageBuilder getClientMessageBuilder() {
|
||||
if (useRainbowStyle()) {
|
||||
return MessageBuilder.fromComponentFactory(config, new PrideComponentFactory(config));
|
||||
}
|
||||
return MessageBuilder.defaultBuilder(config);
|
||||
}
|
||||
|
||||
private static MessageBuilder getConsoleMessageBuilder() {
|
||||
MessageBuilder consoleBuilder;
|
||||
if (isBukkit()) {
|
||||
consoleBuilder = MessageBuilder.fromComponentFactory(config, new BukkitConsoleComponentFactory(config));
|
||||
} else {
|
||||
consoleBuilder = getClientMessageBuilder();
|
||||
}
|
||||
consoleBuilder.setConsoleBuilder(true);
|
||||
consoleBuilder.toggleHoverUse(false);
|
||||
return consoleBuilder;
|
||||
}
|
||||
|
||||
private static boolean useRainbowStyle() {
|
||||
return config.useRainbowMode() || (config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE));
|
||||
}
|
||||
|
||||
private static boolean isBukkit() {
|
||||
return Bukkit.getName().equalsIgnoreCase("CraftBukkit");
|
||||
}
|
||||
|
||||
private void prepareFunctions() {
|
||||
standardMessages = new EnumMap<>(StandardMessage.class);
|
||||
|
||||
standardMessages.put(RELOADED_CONFIG, (MessageBuilder::reloadedConfig));
|
||||
standardMessages.put(STILL_RELOADING, (MessageBuilder::stillReloading));
|
||||
standardMessages.put(MISSING_STAT_NAME, (MessageBuilder::missingStatName));
|
||||
standardMessages.put(MISSING_PLAYER_NAME, (MessageBuilder::missingPlayerName));
|
||||
standardMessages.put(REQUEST_ALREADY_RUNNING, (MessageBuilder::requestAlreadyRunning));
|
||||
standardMessages.put(STILL_ON_SHARE_COOLDOWN, (MessageBuilder::stillOnShareCoolDown));
|
||||
standardMessages.put(RESULTS_ALREADY_SHARED, (MessageBuilder::resultsAlreadyShared));
|
||||
standardMessages.put(STAT_RESULTS_TOO_OLD, (MessageBuilder::statResultsTooOld));
|
||||
standardMessages.put(UNKNOWN_ERROR, (MessageBuilder::unknownError));
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
|
||||
/**
|
||||
* A festive version of the {@link ComponentFactory}
|
||||
*/
|
||||
public class PrideComponentFactory extends ComponentFactory {
|
||||
|
||||
public PrideComponentFactory(ConfigHandler c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareColors() {
|
||||
PREFIX = PluginColor.GOLD.getColor();
|
||||
BRACKETS = PluginColor.GRAY.getColor();
|
||||
UNDERSCORE = PluginColor.DARK_PURPLE.getColor();
|
||||
HEARTS = PluginColor.RED.getColor();
|
||||
|
||||
MSG_MAIN = PluginColor.GRAY.getColor(); //difference 1
|
||||
MSG_ACCENT = PluginColor.LIGHT_GOLD.getColor(); //difference 2
|
||||
|
||||
MSG_MAIN_2 = PluginColor.GOLD.getColor();
|
||||
MSG_ACCENT_2A = PluginColor.MEDIUM_GOLD.getColor();
|
||||
MSG_ACCENT_2B = PluginColor.LIGHT_YELLOW.getColor();
|
||||
|
||||
MSG_HOVER = PluginColor.LIGHT_BLUE.getColor();
|
||||
MSG_CLICKED = PluginColor.LIGHT_PURPLE.getColor();
|
||||
MSG_HOVER_ACCENT = PluginColor.LIGHT_GOLD.getColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextColor getExampleNameColor() {
|
||||
return getSharerNameColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextColor getSharerNameColor() {
|
||||
return PluginColor.getRandomNameColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent pluginPrefixAsTitle() {
|
||||
String title = "<rainbow:16>____________ [PlayerStats] ____________</rainbow>"; //12 underscores
|
||||
return text()
|
||||
.append(MiniMessage.miniMessage().deserialize(title))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent pluginPrefix() {
|
||||
Random randomizer = new Random();
|
||||
if (randomizer.nextBoolean()) {
|
||||
return backwardsPluginPrefixComponent();
|
||||
}
|
||||
return rainbowPrefix();
|
||||
}
|
||||
|
||||
public TextComponent rainbowPrefix() {
|
||||
return text()
|
||||
.append(MiniMessage.miniMessage()
|
||||
.deserialize("<#f74040>[</#f74040>" +
|
||||
"<#F54D39>P</#F54D39>" +
|
||||
"<#F16E28>l</#F16E28>" +
|
||||
"<#ee8a19>a</#ee8a19>" +
|
||||
"<#EEA019>y</#EEA019>" +
|
||||
"<#F7C522>e</#F7C522>" +
|
||||
"<#C1DA15>r</#C1DA15>" +
|
||||
"<#84D937>S</#84D937>" +
|
||||
"<#46D858>t</#46D858>" +
|
||||
"<#01c1a7>a</#01c1a7>" +
|
||||
"<#1F8BEB>t</#1F8BEB>" +
|
||||
"<#3341E6>s</#3341E6>" +
|
||||
"<#631ae6>]</#631ae6>"))
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent backwardsPluginPrefixComponent() {
|
||||
return text()
|
||||
.append(MiniMessage.miniMessage()
|
||||
.deserialize("<#631ae6>[</#631ae6>" +
|
||||
"<#3341E6>P</#3341E6>" +
|
||||
"<#1F8BEB>l</#1F8BEB>" +
|
||||
"<#01c1a7>a</#01c1a7>" +
|
||||
"<#46D858>y</#46D858>" +
|
||||
"<#84D937>e</#84D937>" +
|
||||
"<#C1DA15>r</#C1DA15>" +
|
||||
"<#F7C522>S</#F7C522>" +
|
||||
"<#EEA019>t</#EEA019>" +
|
||||
"<#ee8a19>a</#ee8a19>" +
|
||||
"<#f67824>t</#f67824>" +
|
||||
"<#f76540>s</#f76540>" +
|
||||
"<#f74040>]</#f74040>"))
|
||||
.build();
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.reload;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ShareManager;
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/** The Thread that is in charge of reloading PlayerStats. */
|
||||
public final class ReloadThread extends Thread {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static OutputManager outputManager;
|
||||
|
||||
private final int reloadThreadID;
|
||||
private final StatThread statThread;
|
||||
|
||||
private final CommandSender sender;
|
||||
|
||||
public ReloadThread(ConfigHandler c, OutputManager m, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
|
||||
config = c;
|
||||
outputManager = m;
|
||||
|
||||
reloadThreadID = ID;
|
||||
statThread = s;
|
||||
sender = se;
|
||||
|
||||
this.setName("ReloadThread-" + reloadThreadID);
|
||||
MyLogger.logHighLevelMsg(this.getName() + " created!");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will perform a series of tasks. If a {@link StatThread}
|
||||
* is still running, it will join the statThread and wait for it to finish.
|
||||
* Then, it will reload the config, update the offlinePlayerList in the
|
||||
* {@link OfflinePlayerHandler}, update the {@link DebugLevel}, update
|
||||
* the share-settings in {@link ShareManager} and topListSize-settings
|
||||
* in {@link StatCalculator}, and update the MessageBuilders in the
|
||||
* {@link OutputManager}.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
long time = System.currentTimeMillis();
|
||||
MyLogger.logHighLevelMsg(this.getName() + " started!");
|
||||
|
||||
if (statThread != null && statThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.logLowLevelMsg(this.getName() + ": Waiting for " + statThread.getName() + " to finish up...");
|
||||
statThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
MyLogger.logException(e, "ReloadThread", "run(), trying to join " + statThread.getName());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (reloadThreadID != 1 && config.reloadConfig()) { //during a reload
|
||||
MyLogger.logLowLevelMsg("Reloading!");
|
||||
reloadEverything();
|
||||
|
||||
if (sender != null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.RELOADED_CONFIG);
|
||||
}
|
||||
}
|
||||
else { //during first start-up
|
||||
MyLogger.setDebugLevel(config.getDebugLevel());
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadEverything() {
|
||||
MyLogger.setDebugLevel(config.getDebugLevel());
|
||||
LanguageKeyHandler.reloadFile();
|
||||
OutputManager.updateMessageBuilders();
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
|
||||
ShareManager.updateSettings(config);
|
||||
}
|
||||
|
||||
private ConcurrentHashMap<String, UUID> loadOfflinePlayers() {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
OfflinePlayer[] offlinePlayers;
|
||||
if (config.whitelistOnly()) {
|
||||
offlinePlayers = Bukkit.getWhitelistedPlayers().toArray(OfflinePlayer[]::new);
|
||||
MyLogger.logMediumLevelTask("ReloadThread",
|
||||
"retrieved whitelist", time);
|
||||
}
|
||||
else if (config.excludeBanned()) {
|
||||
if (Bukkit.getPluginManager().getPlugin("LiteBans") != null) {
|
||||
offlinePlayers = Arrays.stream(Bukkit.getOfflinePlayers())
|
||||
.parallel()
|
||||
.filter(Predicate.not(OfflinePlayer::isBanned))
|
||||
.toArray(OfflinePlayer[]::new);
|
||||
} else {
|
||||
Set<OfflinePlayer> bannedPlayers = Bukkit.getBannedPlayers();
|
||||
offlinePlayers = Arrays.stream(Bukkit.getOfflinePlayers())
|
||||
.parallel()
|
||||
.filter(offlinePlayer -> !bannedPlayers.contains(offlinePlayer)).toArray(OfflinePlayer[]::new);
|
||||
}
|
||||
MyLogger.logMediumLevelTask("ReloadThread",
|
||||
"retrieved banlist", time);
|
||||
}
|
||||
else {
|
||||
offlinePlayers = Bukkit.getOfflinePlayers();
|
||||
MyLogger.logMediumLevelTask("ReloadThread",
|
||||
"retrieved list of Offline Players", time);
|
||||
}
|
||||
|
||||
int size = offlinePlayers != null ? offlinePlayers.length : 16;
|
||||
ConcurrentHashMap<String, UUID> playerMap = new ConcurrentHashMap<>(size);
|
||||
|
||||
ReloadAction task = new ReloadAction(offlinePlayers, config.getLastPlayedLimit(), playerMap);
|
||||
MyLogger.actionCreated((offlinePlayers != null) ? offlinePlayers.length : 0);
|
||||
ForkJoinPool.commonPool().invoke(task);
|
||||
MyLogger.actionFinished();
|
||||
|
||||
MyLogger.logLowLevelTask("ReloadThread",
|
||||
("loaded " + playerMap.size() + " offline players"), time);
|
||||
return playerMap;
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class StatCalculator {
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
public StatCalculator(OfflinePlayerHandler offlinePlayerHandler) {
|
||||
this.offlinePlayerHandler = offlinePlayerHandler;
|
||||
}
|
||||
|
||||
public int getPlayerStat(RequestSettings requestSettings) {
|
||||
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(requestSettings.getPlayerName());
|
||||
return switch (requestSettings.getStatistic().getType()) {
|
||||
case UNTYPED -> player.getStatistic(requestSettings.getStatistic());
|
||||
case ENTITY -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getEntity());
|
||||
case BLOCK -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getBlock());
|
||||
case ITEM -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getItem());
|
||||
};
|
||||
}
|
||||
|
||||
public LinkedHashMap<String, Integer> getTopStats(RequestSettings requestSettings) {
|
||||
return getAllStatsAsync(requestSettings).entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||
.limit(requestSettings.getTopListSize())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
public long getServerStat(RequestSettings requestSettings) {
|
||||
List<Integer> numbers = getAllStatsAsync(requestSettings)
|
||||
.values()
|
||||
.parallelStream()
|
||||
.toList();
|
||||
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a bunch of worker pool threads to get the statistics for
|
||||
* all players that are stored in the {@link OfflinePlayerHandler}).
|
||||
*/
|
||||
private @NotNull ConcurrentHashMap<String, Integer> getAllStatsAsync(RequestSettings requestSettings) {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
ForkJoinPool commonPool = ForkJoinPool.commonPool();
|
||||
ConcurrentHashMap<String, Integer> allStats;
|
||||
|
||||
try {
|
||||
allStats = commonPool.invoke(getStatTask(requestSettings));
|
||||
} catch (ConcurrentModificationException e) {
|
||||
MyLogger.logWarning("The requestSettings 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!");
|
||||
throw new ConcurrentModificationException(e.toString());
|
||||
}
|
||||
|
||||
MyLogger.actionFinished();
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
MyLogger.logMediumLevelTask("StatThread", "calculated all stats", time);
|
||||
|
||||
return allStats;
|
||||
}
|
||||
|
||||
private StatAction getStatTask(RequestSettings requestSettings) {
|
||||
int size = offlinePlayerHandler.getOfflinePlayerCount() != 0 ? offlinePlayerHandler.getOfflinePlayerCount() : 16;
|
||||
ConcurrentHashMap<String, Integer> allStats = new ConcurrentHashMap<>(size);
|
||||
ImmutableList<String> playerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
|
||||
|
||||
StatAction task = new StatAction(offlinePlayerHandler, playerNames, requestSettings, allStats);
|
||||
MyLogger.actionCreated(playerNames.size());
|
||||
|
||||
return task;
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Thread that is in charge of getting and calculating statistics.
|
||||
*/
|
||||
public final class StatThread extends Thread {
|
||||
|
||||
private static OutputManager outputManager;
|
||||
private static StatCalculator statCalculator;
|
||||
|
||||
private final ReloadThread reloadThread;
|
||||
private final RequestSettings requestSettings;
|
||||
|
||||
public StatThread(OutputManager m, StatCalculator t, int ID, RequestSettings s, @Nullable ReloadThread r) {
|
||||
outputManager = m;
|
||||
statCalculator = t;
|
||||
|
||||
reloadThread = r;
|
||||
requestSettings = s;
|
||||
|
||||
this.setName("StatThread-" + requestSettings.getCommandSender().getName() + "-" + ID);
|
||||
MyLogger.logHighLevelMsg(this.getName() + " created!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws IllegalStateException, NullPointerException {
|
||||
MyLogger.logHighLevelMsg(this.getName() + " started!");
|
||||
|
||||
if (requestSettings == null) {
|
||||
throw new NullPointerException("No statistic requestSettings was found!");
|
||||
}
|
||||
if (reloadThread != null && reloadThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.logLowLevelMsg(this.getName() + ": Waiting for " + reloadThread.getName() + " to finish up...");
|
||||
outputManager.sendFeedbackMsg(requestSettings.getCommandSender(), StandardMessage.STILL_RELOADING);
|
||||
reloadThread.join();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
MyLogger.logException(e, "StatThread", "Trying to join " + reloadThread.getName());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
long lastCalc = ThreadManager.getLastRecordedCalcTime();
|
||||
if (lastCalc > 2000) {
|
||||
outputManager.sendFeedbackMsgWaitAMoment(requestSettings.getCommandSender(), lastCalc > 20000);
|
||||
}
|
||||
|
||||
Target selection = requestSettings.getTarget();
|
||||
try {
|
||||
TextComponent statResult = switch (selection) {
|
||||
case PLAYER -> outputManager.formatAndSavePlayerStat(requestSettings, statCalculator.getPlayerStat(requestSettings));
|
||||
case TOP -> outputManager.formatAndSaveTopStat(requestSettings, statCalculator.getTopStats(requestSettings));
|
||||
case SERVER -> outputManager.formatAndSaveServerStat(requestSettings, statCalculator.getServerStat(requestSettings));
|
||||
};
|
||||
outputManager.sendToCommandSender(requestSettings.getCommandSender(), statResult);
|
||||
}
|
||||
catch (ConcurrentModificationException e) {
|
||||
if (!requestSettings.isConsoleSender()) {
|
||||
outputManager.sendFeedbackMsg(requestSettings.getCommandSender(), StandardMessage.UNKNOWN_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.PlayerStatResult;
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class PlayerStatRequest extends StatRequest<Integer> implements RequestGenerator<Integer> {
|
||||
|
||||
private final RequestHandler requestHandler;
|
||||
|
||||
public PlayerStatRequest(RequestSettings request) {
|
||||
super(request);
|
||||
requestHandler = new RequestHandler(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatRequest untyped(@NotNull Statistic statistic) {
|
||||
RequestSettings completedRequest = requestHandler.untyped(statistic);
|
||||
return new PlayerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
RequestSettings completedRequest = requestHandler.blockOrItemType(statistic, material);
|
||||
return new PlayerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
RequestSettings completedRequest = requestHandler.entityType(statistic, entityType);
|
||||
return new PlayerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatResult execute() {
|
||||
return getStatResult(super.requestSettings);
|
||||
}
|
||||
|
||||
private PlayerStatResult getStatResult(RequestSettings completedRequest) {
|
||||
int stat = Main
|
||||
.getStatCalculator()
|
||||
.getPlayerStat(completedRequest);
|
||||
|
||||
TextComponent prettyComponent = Main
|
||||
.getStatFormatter()
|
||||
.formatAndSavePlayerStat(completedRequest, stat);
|
||||
|
||||
String prettyString = ComponentUtils
|
||||
.getTranslatableComponentSerializer()
|
||||
.serialize(prettyComponent);
|
||||
|
||||
return new PlayerStatResult(stat, prettyComponent, prettyString);
|
||||
}
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class RequestHandler {
|
||||
|
||||
private final RequestSettings requestSettings;
|
||||
|
||||
public RequestHandler(RequestSettings request) {
|
||||
requestSettings = request;
|
||||
}
|
||||
|
||||
public static RequestSettings getBasicPlayerStatRequest(String playerName) {
|
||||
RequestSettings request = RequestSettings.getBasicAPIRequest();
|
||||
request.setTarget(Target.PLAYER);
|
||||
request.setPlayerName(playerName);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static RequestSettings getBasicServerStatRequest() {
|
||||
RequestSettings request = RequestSettings.getBasicAPIRequest();
|
||||
request.setTarget(Target.SERVER);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static RequestSettings getBasicTopStatRequest(int topListSize) {
|
||||
RequestSettings request = RequestSettings.getBasicAPIRequest();
|
||||
request.setTarget(Target.TOP);
|
||||
request.setTopListSize(topListSize != 0 ? topListSize : Main.getConfigHandler().getTopListMaxSize());
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sender the CommandSender that requested this specific statistic
|
||||
*/
|
||||
public static RequestSettings getBasicInternalStatRequest(CommandSender sender) {
|
||||
RequestSettings request = RequestSettings.getBasicRequest(sender);
|
||||
request.setTopListSize(Main.getConfigHandler().getTopListMaxSize());
|
||||
return request;
|
||||
}
|
||||
|
||||
public RequestSettings untyped(@NotNull Statistic statistic) throws IllegalArgumentException {
|
||||
if (statistic.getType() == Statistic.Type.UNTYPED) {
|
||||
requestSettings.setStatistic(statistic);
|
||||
return requestSettings;
|
||||
}
|
||||
throw new IllegalArgumentException("This statistic is not of Type.Untyped");
|
||||
}
|
||||
|
||||
public RequestSettings blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
|
||||
Statistic.Type type = statistic.getType();
|
||||
if (type == Statistic.Type.BLOCK && material.isBlock()) {
|
||||
requestSettings.setBlock(material);
|
||||
}
|
||||
else if (type == Statistic.Type.ITEM && material.isItem()){
|
||||
requestSettings.setItem(material);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Either this statistic is not of Type.Block or Type.Item, or no valid block or item has been provided");
|
||||
}
|
||||
requestSettings.setStatistic(statistic);
|
||||
requestSettings.setSubStatEntryName(material.toString());
|
||||
return requestSettings;
|
||||
}
|
||||
|
||||
public RequestSettings entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
|
||||
if (statistic.getType() == Statistic.Type.ENTITY) {
|
||||
requestSettings.setStatistic(statistic);
|
||||
requestSettings.setSubStatEntryName(entityType.toString());
|
||||
requestSettings.setEntity(entityType);
|
||||
return requestSettings;
|
||||
}
|
||||
throw new IllegalArgumentException("This statistic is not of Type.Entity");
|
||||
}
|
||||
|
||||
/**
|
||||
* This will create a {@link RequestSettings} object from the provided args,
|
||||
* with the requesting Player (or Console) as CommandSender. This CommandSender
|
||||
* will receive feedback messages if the RequestSettings could not be created.
|
||||
*
|
||||
* @param args an Array of args such as a CommandSender would put in Minecraft chat:
|
||||
* <ul>
|
||||
* <li> a <code>statName</code> (example: "mine_block")
|
||||
* <li> if applicable, a <code>subStatEntryName</code> (example: diorite)
|
||||
* <li> a <code>target</code> for this lookup: can be "top", "server", "player"
|
||||
* (or "me" to indicate the current CommandSender)
|
||||
* <li> if "player" was chosen, include a <code>playerName</code>
|
||||
* </ul>
|
||||
* @return the generated RequestSettings
|
||||
*/
|
||||
public RequestSettings getRequestFromArgs(String[] args) {
|
||||
EnumHandler enumHandler = Main.getEnumHandler();
|
||||
OfflinePlayerHandler offlinePlayerHandler = Main.getOfflinePlayerHandler();
|
||||
CommandSender sender = requestSettings.getCommandSender();
|
||||
|
||||
for (String arg : args) {
|
||||
//check for statName
|
||||
if (enumHandler.isStatistic(arg) && requestSettings.getStatistic() == null) {
|
||||
requestSettings.setStatistic(EnumHandler.getStatEnum(arg));
|
||||
}
|
||||
//check for subStatEntry and playerFlag
|
||||
else if (enumHandler.isSubStatEntry(arg)) {
|
||||
if (arg.equalsIgnoreCase("player") && !requestSettings.getPlayerFlag()) {
|
||||
requestSettings.setPlayerFlag(true);
|
||||
} else {
|
||||
if (requestSettings.getSubStatEntryName() == null) requestSettings.setSubStatEntryName(arg);
|
||||
}
|
||||
}
|
||||
//check for selection
|
||||
else if (arg.equalsIgnoreCase("top")) {
|
||||
requestSettings.setTarget(Target.TOP);
|
||||
} else if (arg.equalsIgnoreCase("server")) {
|
||||
requestSettings.setTarget(Target.SERVER);
|
||||
} else if (arg.equalsIgnoreCase("me")) {
|
||||
if (sender instanceof Player) {
|
||||
requestSettings.setPlayerName(sender.getName());
|
||||
requestSettings.setTarget(Target.PLAYER);
|
||||
} else if (sender instanceof ConsoleCommandSender) {
|
||||
requestSettings.setTarget(Target.SERVER);
|
||||
}
|
||||
} else if (offlinePlayerHandler.isRelevantPlayer(arg) && requestSettings.getPlayerName() == null) {
|
||||
requestSettings.setPlayerName(arg);
|
||||
requestSettings.setTarget(Target.PLAYER);
|
||||
}
|
||||
}
|
||||
patchRequest(requestSettings);
|
||||
return requestSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the RequestSettings object if needed: unpack the playerFlag
|
||||
* into a subStatEntry, try to retrieve the corresponding Enum Constant
|
||||
* for any relevant block/entity/item, and remove any unnecessary
|
||||
* subStatEntries.
|
||||
*/
|
||||
private void patchRequest(RequestSettings requestSettings) {
|
||||
if (requestSettings.getStatistic() != null) {
|
||||
Statistic.Type type = requestSettings.getStatistic().getType();
|
||||
|
||||
if (requestSettings.getPlayerFlag()) { //unpack the playerFlag
|
||||
if (type == Statistic.Type.ENTITY && requestSettings.getSubStatEntryName() == null) {
|
||||
requestSettings.setSubStatEntryName("player");
|
||||
} else {
|
||||
requestSettings.setTarget(Target.PLAYER);
|
||||
}
|
||||
}
|
||||
|
||||
String subStatEntry = requestSettings.getSubStatEntryName();
|
||||
switch (type) { //attempt to convert relevant subStatEntries into their corresponding Enum Constant
|
||||
case BLOCK -> {
|
||||
Material block = EnumHandler.getBlockEnum(subStatEntry);
|
||||
if (block != null) requestSettings.setBlock(block);
|
||||
}
|
||||
case ENTITY -> {
|
||||
EntityType entity = EnumHandler.getEntityEnum(subStatEntry);
|
||||
if (entity != null) requestSettings.setEntity(entity);
|
||||
}
|
||||
case ITEM -> {
|
||||
Material item = EnumHandler.getItemEnum(subStatEntry);
|
||||
if (item != null) requestSettings.setItem(item);
|
||||
}
|
||||
case UNTYPED -> { //remove unnecessary subStatEntries
|
||||
if (subStatEntry != null) requestSettings.setSubStatEntryName(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The object PlayerStats uses to calculate and format the requested
|
||||
* statistic. The settings in this RequestSettings object can be
|
||||
* configured from two different sources:
|
||||
* <br>- Internally: by PlayerStats itself when /stat is called,
|
||||
* using the args provided by the CommandSender.
|
||||
* <br>- Externally: through the API methods provided by the
|
||||
* {@link RequestGenerator} interface.
|
||||
* <br>
|
||||
* <br>For this RequestSettings object to be valid, the following
|
||||
* values need to be set:
|
||||
* <ul>
|
||||
* <li> a {@link Statistic} <code>statistic</code> </li>
|
||||
* <li> if this Statistic is not of {@link Statistic.Type} Untyped,
|
||||
* a <code>subStatEntryName</code> needs to be set, together with one
|
||||
* of the following values:
|
||||
* <br>- for Type.Block: a {@link Material} <code>blockMaterial</code>
|
||||
* <br>- for Type.Item: a {@link Material} <code>itemMaterial</code>
|
||||
* <br>- for Type.Entity: an {@link EntityType} <code>entityType</code>
|
||||
* <li> a {@link Target} <code>target</code> (defaults to Top)
|
||||
* <li> if the <code>target</code> is Target.Player, a
|
||||
* <code>playerName</code> needs to be added
|
||||
* </ul>
|
||||
*/
|
||||
public final class RequestSettings {
|
||||
|
||||
private final CommandSender sender;
|
||||
private Statistic statistic;
|
||||
private String playerName;
|
||||
private Target target;
|
||||
private int topListSize;
|
||||
|
||||
private String subStatEntryName;
|
||||
private EntityType entity;
|
||||
private Material block;
|
||||
private Material item;
|
||||
private boolean playerFlag;
|
||||
|
||||
/**
|
||||
* Create a new {@link RequestSettings} with default values:
|
||||
* <br>- CommandSender sender (provided)
|
||||
* <br>- Target target = {@link Target#TOP}
|
||||
* <br>- int topListSize = 10
|
||||
* <br>- boolean playerFlag = false
|
||||
*
|
||||
* @param sender the CommandSender who prompted this RequestGenerator
|
||||
*/
|
||||
private RequestSettings(@NotNull CommandSender sender) {
|
||||
this.sender = sender;
|
||||
target = Target.TOP;
|
||||
playerFlag = false;
|
||||
}
|
||||
|
||||
public static RequestSettings getBasicRequest(CommandSender sender) {
|
||||
return new RequestSettings(sender);
|
||||
}
|
||||
|
||||
public static RequestSettings getBasicAPIRequest() {
|
||||
return new RequestSettings(Bukkit.getConsoleSender());
|
||||
}
|
||||
|
||||
public @NotNull CommandSender getCommandSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public boolean isConsoleSender() {
|
||||
return sender instanceof ConsoleCommandSender;
|
||||
}
|
||||
|
||||
public void setStatistic(Statistic statistic) {
|
||||
this.statistic = statistic;
|
||||
}
|
||||
|
||||
public Statistic getStatistic() {
|
||||
return statistic;
|
||||
}
|
||||
|
||||
public void setSubStatEntryName(String subStatEntry) {
|
||||
this.subStatEntryName = subStatEntry;
|
||||
}
|
||||
|
||||
public String getSubStatEntryName() {
|
||||
return subStatEntryName;
|
||||
}
|
||||
|
||||
public void setPlayerName(String playerName) {
|
||||
this.playerName = playerName;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public void setPlayerFlag(boolean playerFlag) {
|
||||
this.playerFlag = playerFlag;
|
||||
}
|
||||
|
||||
public boolean getPlayerFlag() {
|
||||
return playerFlag;
|
||||
}
|
||||
|
||||
public void setTarget(@NotNull Target target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public @NotNull Target getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTopListSize(int topListSize) {
|
||||
this.topListSize = topListSize;
|
||||
}
|
||||
|
||||
public int getTopListSize() {
|
||||
return this.topListSize;
|
||||
}
|
||||
|
||||
public void setEntity(EntityType entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public EntityType getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
public void setBlock(Material block) {
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
public Material getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
public void setItem(Material item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public Material getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
if (statistic == null) {
|
||||
return false;
|
||||
} else if (target == Target.PLAYER && playerName == null) {
|
||||
return false;
|
||||
} else if (statistic.getType() != Statistic.Type.UNTYPED &&
|
||||
subStatEntryName == null) {
|
||||
return false;
|
||||
} else {
|
||||
return hasMatchingSubStat();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasMatchingSubStat() {
|
||||
switch (statistic.getType()) {
|
||||
case BLOCK -> {
|
||||
return block != null;
|
||||
}
|
||||
case ENTITY -> {
|
||||
return entity != null;
|
||||
}
|
||||
case ITEM -> {
|
||||
return item != null;
|
||||
}
|
||||
default -> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.ServerStatResult;
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ServerStatRequest extends StatRequest<Long> implements RequestGenerator<Long> {
|
||||
|
||||
private final RequestHandler requestHandler;
|
||||
|
||||
public ServerStatRequest(RequestSettings request) {
|
||||
super(request);
|
||||
requestHandler = new RequestHandler(requestSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest untyped(@NotNull Statistic statistic) {
|
||||
RequestSettings completedRequest = requestHandler.untyped(statistic);
|
||||
return new ServerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
RequestSettings completedRequest = requestHandler.blockOrItemType(statistic, material);
|
||||
return new ServerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
RequestSettings completedRequest = requestHandler.entityType(statistic, entityType);
|
||||
return new ServerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatResult execute() {
|
||||
return getStatResult(requestSettings);
|
||||
}
|
||||
|
||||
private ServerStatResult getStatResult(RequestSettings completedRequest) {
|
||||
long stat = Main
|
||||
.getStatCalculator()
|
||||
.getServerStat(completedRequest);
|
||||
|
||||
TextComponent prettyComponent = Main
|
||||
.getStatFormatter()
|
||||
.formatAndSaveServerStat(completedRequest, stat);
|
||||
|
||||
String prettyString = ComponentUtils
|
||||
.getTranslatableComponentSerializer()
|
||||
.serialize(prettyComponent);
|
||||
|
||||
return new ServerStatResult(stat, prettyComponent, prettyString);
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.PlayerStats;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.StatResult;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Holds all the information PlayerStats needs to perform
|
||||
* a lookup, and can be executed to get the results. Calling
|
||||
* {@link #execute()} on a Top- or ServerRequest can take some
|
||||
* time (especially if there is a substantial amount of
|
||||
* OfflinePlayers on this particular server), so I strongly
|
||||
* advice you to call this asynchronously!
|
||||
*/
|
||||
public abstract class StatRequest<T> {
|
||||
|
||||
protected final RequestSettings requestSettings;
|
||||
|
||||
protected StatRequest(RequestSettings request) {
|
||||
requestSettings = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes this StatRequest. For a Top- or ServerRequest, this can
|
||||
* take some time!
|
||||
*
|
||||
* @return a StatResult containing the value of this lookup, both as
|
||||
* numerical value and as formatted message
|
||||
* @see PlayerStats
|
||||
* @see StatResult
|
||||
*/
|
||||
public abstract StatResult<T> execute();
|
||||
|
||||
/**
|
||||
* Gets the Statistic that calling {@link #execute()} will calculate
|
||||
* the data for.
|
||||
* @return the Statistic
|
||||
*/
|
||||
public Statistic getStatisticSetting() {
|
||||
return requestSettings.getStatistic();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Statistic setting for this StatRequest is of Type.Block,
|
||||
* this will get the Material that was set.
|
||||
*
|
||||
* @return a Material for which #isBlock is true, or null if no
|
||||
* Material was set
|
||||
*/
|
||||
public @Nullable Material getBlockSetting() {
|
||||
return requestSettings.getBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Statistic setting for this StatRequest is of Type.Item,
|
||||
* this will get the Material that was set.
|
||||
*
|
||||
* @return a Material for which #isItem is true, or null if no
|
||||
* Material was set
|
||||
*/
|
||||
public @Nullable Material getItemSetting() {
|
||||
return requestSettings.getItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Statistic setting for this StatRequest is of Type.Entity,
|
||||
* this will get the EntityType that was set.
|
||||
*
|
||||
* @return an EntityType, or null if no EntityType was set
|
||||
*/
|
||||
public @Nullable EntityType getEntitySetting() {
|
||||
return requestSettings.getEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Target that will be used when calling {@link #execute()}.
|
||||
*
|
||||
* @return the Target for this lookup (either Player, Server or Top)
|
||||
*/
|
||||
public Target getTargetSetting() {
|
||||
return requestSettings.getTarget();
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.TopStatResult;
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public final class TopStatRequest extends StatRequest<LinkedHashMap<String, Integer>> implements RequestGenerator<LinkedHashMap<String, Integer>> {
|
||||
|
||||
private final RequestHandler requestHandler;
|
||||
|
||||
public TopStatRequest(RequestSettings request) {
|
||||
super(request);
|
||||
requestHandler = new RequestHandler(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest untyped(@NotNull Statistic statistic) {
|
||||
RequestSettings completedRequest = requestHandler.untyped(statistic);
|
||||
return new TopStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
RequestSettings completedRequest = requestHandler.blockOrItemType(statistic, material);
|
||||
return new TopStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
RequestSettings completedRequest = requestHandler.entityType(statistic, entityType);
|
||||
return new TopStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatResult execute() {
|
||||
return getStatResult(super.requestSettings);
|
||||
}
|
||||
|
||||
private TopStatResult getStatResult(RequestSettings completedRequest) {
|
||||
LinkedHashMap<String, Integer> stat = Main
|
||||
.getStatCalculator()
|
||||
.getTopStats(completedRequest);
|
||||
|
||||
TextComponent prettyComponent = Main
|
||||
.getStatFormatter()
|
||||
.formatAndSaveTopStat(completedRequest, stat);
|
||||
|
||||
String prettyString = ComponentUtils
|
||||
.getTranslatableComponentSerializer()
|
||||
.serialize(prettyComponent);
|
||||
|
||||
return new TopStatResult(stat, prettyComponent, prettyString);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
/**
|
||||
* This Record is used to store stat-results internally,
|
||||
* so Players can share them by clicking a share-button.
|
||||
*/
|
||||
public record InternalStatResult(String executorName, TextComponent formattedValue, int ID) implements StatResult<Integer> {
|
||||
|
||||
/**
|
||||
* Gets the ID number for this StatResult. Unlike for the
|
||||
* other {@link StatResult} implementations, this one does
|
||||
* not return the actual statistic data, because this
|
||||
* implementation is meant for internal saving-and-sharing only.
|
||||
* This method is only for Interface-consistency,
|
||||
* InternalStatResult#ID is better.
|
||||
*
|
||||
@return Integer that represents this StatResult's ID number
|
||||
*/
|
||||
@Override
|
||||
public Integer getNumericalValue() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedString() {
|
||||
return ComponentUtils.getTranslatableComponentSerializer()
|
||||
.serialize(formattedValue);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public record PlayerStatResult(int value, TextComponent formattedComponent, String formattedString) implements StatResult<Integer> {
|
||||
|
||||
@Override
|
||||
public Integer getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedString() {
|
||||
return formattedString;
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public record ServerStatResult(long value, TextComponent formattedComponent, String formattedString) implements StatResult<Long> {
|
||||
|
||||
@Override
|
||||
public Long getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedString() {
|
||||
return formattedString;
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public record TopStatResult(LinkedHashMap<String, Integer> value, TextComponent formattedComponent, String formattedString) implements StatResult<LinkedHashMap<String,Integer>> {
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<String, Integer> getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedString() {
|
||||
return formattedString;
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package com.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A utility class that deals with OfflinePlayers. It stores a list
|
||||
* of all OfflinePlayer-names that need to be included in statistic
|
||||
* calculations, and can retrieve the corresponding OfflinePlayer
|
||||
* object for a given player-name.
|
||||
*/
|
||||
public final class OfflinePlayerHandler {
|
||||
|
||||
private static ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||
private static ArrayList<String> playerNames;
|
||||
|
||||
public OfflinePlayerHandler() {
|
||||
offlinePlayerUUIDs = new ConcurrentHashMap<>();
|
||||
playerNames = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new HashMap that stores the players to include in stat calculations.
|
||||
* This HashMap is stored as a private variable in OfflinePlayerHandler.
|
||||
*
|
||||
* @param playerList ConcurrentHashMap with keys: playerNames and values: UUIDs
|
||||
*/
|
||||
public static void updateOfflinePlayerList(ConcurrentHashMap<String, UUID> playerList) {
|
||||
offlinePlayerUUIDs = playerList;
|
||||
playerNames = Collections.list(offlinePlayerUUIDs.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given playerName is on the private HashMap of players
|
||||
* that should be included in statistic calculations.
|
||||
*
|
||||
* @param playerName String (case-sensitive)
|
||||
* @return true if this Player should be included in calculations
|
||||
*/
|
||||
public boolean isRelevantPlayer(String playerName) {
|
||||
return offlinePlayerUUIDs.containsKey(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of OfflinePlayers that are included in
|
||||
* statistic calculations.
|
||||
*
|
||||
* @return the number of included OfflinePlayers
|
||||
*/
|
||||
public int getOfflinePlayerCount() {
|
||||
return offlinePlayerUUIDs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an ArrayList of names from all OfflinePlayers that should
|
||||
* be included in statistic calculations.
|
||||
*
|
||||
* @return the ArrayList
|
||||
*/
|
||||
public ArrayList<String> getOfflinePlayerNames() {
|
||||
return playerNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the playerName to get the player's UUID from a private HashMap,
|
||||
* and uses the UUID to get the corresponding OfflinePlayer Object.
|
||||
*
|
||||
* @param playerName name of the target player (case-sensitive)
|
||||
* @return OfflinePlayer
|
||||
* @throws IllegalArgumentException if this player is not on the list
|
||||
* of players that should be included in statistic calculations
|
||||
*/
|
||||
public OfflinePlayer getOfflinePlayer(String playerName) throws IllegalArgumentException {
|
||||
if (offlinePlayerUUIDs.get(playerName) != null) {
|
||||
return Bukkit.getOfflinePlayer(offlinePlayerUUIDs.get(playerName));
|
||||
}
|
||||
else {
|
||||
MyLogger.logWarning("Cannot calculate statistics for player-name: " + playerName +
|
||||
"! Double-check if the name is spelled correctly (including capital letters), " +
|
||||
"or if any of your config settings exclude them");
|
||||
throw new IllegalArgumentException("Cannot convert this player-name into a valid Player to calculate statistics for");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
# ------------------------------------------------------------------------------------------------------ #
|
||||
# PlayerStats Configuration #
|
||||
# ------------------------------------------------------------------------------------------------------ #
|
||||
config-version: 6
|
||||
config-version: 7
|
||||
|
||||
|
||||
# # ------------------------------- # #
|
||||
@ -33,6 +33,11 @@ exclude-banned-players: false
|
||||
# Leave this on 0 to include all players
|
||||
number-of-days-since-last-joined: 0
|
||||
|
||||
# Players that are excluded through the previous settings or the excluded-players-file will not
|
||||
# show up in top or server statistics. This setting controls whether you can still see their stats with
|
||||
# the /stat player command
|
||||
allow-player-lookups-for-excluded-players: true
|
||||
|
||||
|
||||
# # ------------------------------- # #
|
||||
# # Format & Display # #
|
||||
|
14
src/main/resources/excluded_players.yml
Normal file
14
src/main/resources/excluded_players.yml
Normal file
@ -0,0 +1,14 @@
|
||||
# ------------------------------------------------------------------------------------------------------ #
|
||||
# PlayerStats Excluded Players #
|
||||
# ------------------------------------------------------------------------------------------------------ #
|
||||
|
||||
# Players whose UUIDs are stored in this file, will be hidden from /statistic results.
|
||||
# This can be used to exclude alt accounts, for example.
|
||||
# To exclude groups of players (such as banned players), see the config.yml (section 'General')
|
||||
|
||||
# UUIDs can be added directly to this file, or through the /statexclude command in game.
|
||||
# Format:
|
||||
# - player1UUID
|
||||
# - player2UUID
|
||||
excluded:
|
||||
-
|
@ -2,6 +2,9 @@
|
||||
# PlayerStats Language File #
|
||||
# ------------------------------------------------------------------------------------------------------ #
|
||||
|
||||
# If "translate-to-client-language" in the config.yml is set to false (section 'Format & Display'),
|
||||
# values from this file will be used instead
|
||||
|
||||
stat_type.minecraft.mined: "Times Mined"
|
||||
stat_type.minecraft.crafted: "Times Crafted"
|
||||
stat_type.minecraft.used: "Times Used"
|
||||
|
@ -1,6 +1,6 @@
|
||||
main: com.artemis.the.gr8.playerstats.Main
|
||||
main: com.artemis.the.gr8.playerstats.core.Main
|
||||
name: PlayerStats
|
||||
version: 1.8
|
||||
version: 2.0
|
||||
api-version: 1.13
|
||||
description: adds commands to view player statistics in chat
|
||||
author: Artemis_the_gr8
|
||||
@ -11,22 +11,31 @@ commands:
|
||||
aliases:
|
||||
- stat
|
||||
- stats
|
||||
description: general statistic command
|
||||
description: show player statistics in private chat
|
||||
usage: "§6/stat info"
|
||||
permission: playerstats.stat
|
||||
statisticshare:
|
||||
aliases:
|
||||
- statshare
|
||||
- statsshare
|
||||
description: shares last stat lookup in chat
|
||||
usage: "§b/statshare"
|
||||
description: share last stat lookup in chat
|
||||
usage: "§6/This command can only be executed by clicking the \"share\" button in /stat results.
|
||||
If you don't see this button, you don't have share-permission, or sharing is turned off."
|
||||
permission: playerstats.share
|
||||
statisticreload:
|
||||
aliases:
|
||||
- statreload
|
||||
- statsreload
|
||||
description: reloads the config
|
||||
usage: "§a/statreload"
|
||||
usage: "§6/statreload"
|
||||
permission: playerstats.reload
|
||||
statisticexclude:
|
||||
aliases:
|
||||
- statexclude
|
||||
- statsexclude
|
||||
description: hide this player's statistics from /stat results
|
||||
usage: "§6/statexclude info"
|
||||
permission: playerstats.exclude
|
||||
permissions:
|
||||
playerstats.stat:
|
||||
description: allows usage of /statistic
|
||||
@ -34,6 +43,9 @@ permissions:
|
||||
playerstats.share:
|
||||
description: allows sharing stats in chat
|
||||
default: true
|
||||
playerstats.exclude:
|
||||
description: allows usage of /statexclude
|
||||
default: op
|
||||
playerstats.reload:
|
||||
description: allows usage of /statreload
|
||||
default: op
|
Loading…
Reference in New Issue
Block a user