mirror of
https://github.com/itHotL/PlayerStats.git
synced 2024-11-25 12:25:36 +01:00
commit
84ee9a86db
@ -5,6 +5,7 @@
|
||||
<configuration>
|
||||
<autoDetectTypes>
|
||||
<platformType>SPIGOT</platformType>
|
||||
<platformType>ADVENTURE</platformType>
|
||||
</autoDetectTypes>
|
||||
</configuration>
|
||||
</facet>
|
||||
|
@ -26,6 +26,11 @@
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="placeholderapi" />
|
||||
<option name="name" value="placeholderapi" />
|
||||
<option name="url" value="https://repo.extendedclip.com/content/repositories/placeholderapi/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
|
@ -6,6 +6,9 @@
|
||||
</list>
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="JavadocGenerationManager">
|
||||
<option name="OUTPUT_DIRECTORY" value="$PROJECT_DIR$/javadoc" />
|
||||
</component>
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
|
@ -39,11 +39,15 @@
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.kyori</shadedPattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.lib.kyori</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.tchristofferson</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.tchristofferson</shadedPattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.util.tchristofferson</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.util.bstats</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<filters>
|
||||
@ -67,9 +71,27 @@
|
||||
<exclude>META-INF/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.bstats:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<repositories>
|
||||
@ -85,6 +107,10 @@
|
||||
<id>maven-central</id>
|
||||
<url>https://oss.sonatype.org/content/groups/public</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>placeholderapi</id>
|
||||
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@ -93,6 +119,12 @@
|
||||
<version>1.19-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.11.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
|
48
pom.xml
48
pom.xml
@ -30,6 +30,11 @@
|
||||
<id>maven-central</id> <!-- Config-Updater -->
|
||||
<url>https://oss.sonatype.org/content/groups/public</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>placeholderapi</id> <!-- Placeholder API -->
|
||||
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
@ -49,7 +54,7 @@
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-platform-bukkit</artifactId>
|
||||
<version>4.1.1</version>
|
||||
<version>4.1.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -58,12 +63,26 @@
|
||||
<version>4.11.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.11.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tchristofferson</groupId>
|
||||
<artifactId>ConfigUpdater</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bstats</groupId>
|
||||
<artifactId>bstats-bukkit</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
@ -109,11 +128,15 @@
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.kyori</shadedPattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.lib.kyori</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.tchristofferson</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.tchristofferson</shadedPattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.util.tchristofferson</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.util.bstats</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<filters>
|
||||
@ -137,9 +160,28 @@
|
||||
<exclude>META-INF/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.bstats:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -1,5 +1,7 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.PlayerStats;
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.PlayerStatsAPI;
|
||||
import com.gmail.artemis.the.gr8.playerstats.commands.ReloadCommand;
|
||||
import com.gmail.artemis.the.gr8.playerstats.commands.ShareCommand;
|
||||
import com.gmail.artemis.the.gr8.playerstats.commands.StatCommand;
|
||||
@ -7,42 +9,44 @@ import com.gmail.artemis.the.gr8.playerstats.commands.TabCompleter;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.listeners.JoinListener;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class Main extends JavaPlugin {
|
||||
public final class Main extends JavaPlugin {
|
||||
|
||||
private static BukkitAudiences adventure;
|
||||
|
||||
public static @NotNull BukkitAudiences adventure() {
|
||||
if (adventure == null) {
|
||||
throw new IllegalStateException("Tried to access Adventure when the plugin was disabled!");
|
||||
}
|
||||
return adventure;
|
||||
}
|
||||
private static ConfigHandler config;
|
||||
private static OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static EnumHandler enumHandler;
|
||||
|
||||
private static OutputManager outputManager;
|
||||
private static ShareManager shareManager;
|
||||
private static ThreadManager threadManager;
|
||||
|
||||
private static PlayerStats playerStatsAPI;
|
||||
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
//initialize the Adventure library
|
||||
adventure = BukkitAudiences.create(this);
|
||||
//TODO fix (move these two into initializeMainClasses also, and remove all the Main.get... methods)
|
||||
new Metrics(this, 15923);
|
||||
|
||||
//first get an instance of all the classes that need to be passed along to different classes
|
||||
ConfigHandler config = new ConfigHandler(this);
|
||||
OfflinePlayerHandler offlinePlayerHandler = new OfflinePlayerHandler();
|
||||
|
||||
OutputManager outputManager = OutputManager.getInstance(config);
|
||||
ThreadManager threadManager = ThreadManager.getInstance(config, outputManager, offlinePlayerHandler);
|
||||
ShareManager shareManager = ShareManager.getInstance(config);
|
||||
//initialize all the Managers, singletons, ConfigHandler and the API
|
||||
initializeMainClasses();
|
||||
|
||||
//register all commands and the tabCompleter
|
||||
PluginCommand statcmd = this.getCommand("statistic");
|
||||
if (statcmd != null) {
|
||||
statcmd.setExecutor(new StatCommand(outputManager, threadManager, offlinePlayerHandler));
|
||||
statcmd.setTabCompleter(new TabCompleter(offlinePlayerHandler));
|
||||
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));
|
||||
@ -51,7 +55,7 @@ public class Main extends JavaPlugin {
|
||||
|
||||
//register the listener
|
||||
Bukkit.getPluginManager().registerEvents(new JoinListener(threadManager), this);
|
||||
|
||||
|
||||
//finish up
|
||||
this.getLogger().info("Enabled PlayerStats!");
|
||||
}
|
||||
@ -64,4 +68,54 @@ public class Main extends JavaPlugin {
|
||||
}
|
||||
this.getLogger().info("Disabled PlayerStats!");
|
||||
}
|
||||
|
||||
public static @NotNull BukkitAudiences getAdventure() throws IllegalStateException {
|
||||
if (adventure == null) {
|
||||
throw new IllegalStateException("Tried to access Adventure without PlayerStats being enabled!");
|
||||
}
|
||||
return adventure;
|
||||
}
|
||||
|
||||
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 EnumHandler getEnumHandler() {
|
||||
if (enumHandler == null) {
|
||||
enumHandler = new EnumHandler();
|
||||
}
|
||||
return enumHandler;
|
||||
}
|
||||
|
||||
public static @NotNull OfflinePlayerHandler getOfflinePlayerHandler() throws IllegalStateException {
|
||||
if (offlinePlayerHandler == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be fully loaded!");
|
||||
}
|
||||
return offlinePlayerHandler;
|
||||
}
|
||||
|
||||
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() {
|
||||
adventure = BukkitAudiences.create(this);
|
||||
|
||||
config = new ConfigHandler(this);
|
||||
enumHandler = new EnumHandler();
|
||||
offlinePlayerHandler = new OfflinePlayerHandler();
|
||||
|
||||
shareManager = new ShareManager(config);
|
||||
outputManager = new OutputManager(getAdventure(), config, shareManager);
|
||||
StatManager statManager = new StatManager(offlinePlayerHandler);
|
||||
threadManager = new ThreadManager(config, statManager, outputManager);
|
||||
|
||||
playerStatsAPI = new PlayerStatsAPI(statManager, outputManager);
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ package com.gmail.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import com.gmail.artemis.the.gr8.playerstats.models.StatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.InternalStatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@ -13,43 +13,33 @@ import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.SECONDS;
|
||||
|
||||
/** The manager of all Player-prompted statistic-sharing. If sharing is enabled, this class will save the
|
||||
results of past stat-lookups, so the results can be retrieved and shared when a Player clicks the share-button.*/
|
||||
public final class ShareManager {
|
||||
|
||||
private static volatile ShareManager instance;
|
||||
|
||||
private static boolean isEnabled;
|
||||
private static int waitingTime;
|
||||
|
||||
private volatile AtomicInteger resultID;
|
||||
private ConcurrentHashMap<UUID, StatResult> statResultQueue;
|
||||
private ConcurrentHashMap<String, Instant> shareTimeStamp;
|
||||
private ArrayBlockingQueue<UUID> sharedResults;
|
||||
private static volatile AtomicInteger resultID;
|
||||
private static ConcurrentHashMap<Integer, InternalStatResult> statResultQueue;
|
||||
private static ConcurrentHashMap<String, Instant> shareTimeStamp;
|
||||
private static ArrayBlockingQueue<Integer> sharedResults;
|
||||
|
||||
private ShareManager(ConfigHandler config) {
|
||||
public ShareManager(ConfigHandler config) {
|
||||
updateSettings(config);
|
||||
}
|
||||
|
||||
public static ShareManager getInstance(ConfigHandler config) {
|
||||
ShareManager shareManager = instance;
|
||||
if (shareManager != null) {
|
||||
return shareManager;
|
||||
}
|
||||
synchronized (ShareManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new ShareManager(config);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
public static boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
public synchronized void updateSettings(ConfigHandler config) {
|
||||
public static synchronized void updateSettings(ConfigHandler config) {
|
||||
isEnabled = config.allowStatSharing() && config.useHoverText();
|
||||
waitingTime = config.getStatShareWaitingTime();
|
||||
|
||||
@ -74,21 +64,18 @@ public final class ShareManager {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
public boolean senderHasPermission(CommandSender sender) {
|
||||
return !(sender instanceof ConsoleCommandSender) && sender.hasPermission("playerstats.share");
|
||||
}
|
||||
|
||||
public UUID saveStatResult(String playerName, TextComponent statResult) {
|
||||
public int saveStatResult(String playerName, TextComponent statResult) {
|
||||
removeExcessResults(playerName);
|
||||
|
||||
int ID = getNextIDNumber();
|
||||
UUID shareCode = UUID.randomUUID();
|
||||
|
||||
statResultQueue.put(shareCode, new StatResult(playerName, statResult, ID, shareCode));
|
||||
//UUID shareCode = UUID.randomUUID();
|
||||
InternalStatResult result = new InternalStatResult(playerName, statResult, ID);
|
||||
int shareCode = result.hashCode();
|
||||
statResultQueue.put(shareCode, result);
|
||||
MyLogger.logMsg("Saving statResults with no. " + ID, DebugLevel.MEDIUM);
|
||||
return shareCode;
|
||||
}
|
||||
@ -102,25 +89,25 @@ public final class ShareManager {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean requestAlreadyShared(UUID shareCode) {
|
||||
public boolean requestAlreadyShared(int shareCode) {
|
||||
return sharedResults.contains(shareCode);
|
||||
}
|
||||
|
||||
/** Takes a statResult from the internal ConcurrentHashmap,
|
||||
/** Takes a formattedValue from the internal ConcurrentHashmap,
|
||||
puts the current time in the shareTimeStamp (ConcurrentHashMap),
|
||||
puts the shareCode (UUID) in the sharedResults (ArrayBlockingQueue),
|
||||
and returns the statResult. If no statResult was found, returns null.*/
|
||||
public @Nullable StatResult getStatResult(String playerName, UUID shareCode) {
|
||||
puts the shareCode (int hashCode) in the sharedResults (ArrayBlockingQueue),
|
||||
and returns the formattedValue. If no formattedValue was found, returns null.*/
|
||||
public @Nullable InternalStatResult getStatResult(String playerName, int shareCode) {
|
||||
if (statResultQueue.containsKey(shareCode)) {
|
||||
shareTimeStamp.put(playerName, Instant.now());
|
||||
|
||||
if (!sharedResults.offer(shareCode)) { //create a new ArrayBlockingQueue if our queue is full
|
||||
MyLogger.logMsg("500 stat-results have been shared, " +
|
||||
"creating a new internal queue with the most recent 50 share-code-values and discarding the rest...", DebugLevel.MEDIUM);
|
||||
ArrayBlockingQueue<UUID> newQueue = new ArrayBlockingQueue<>(500);
|
||||
ArrayBlockingQueue<Integer> newQueue = new ArrayBlockingQueue<>(500);
|
||||
|
||||
synchronized (this) { //put the last 50 values in the new Queue
|
||||
UUID[] lastValues = sharedResults.toArray(new UUID[0]);
|
||||
Integer[] lastValues = sharedResults.toArray(new Integer[500]);
|
||||
Arrays.stream(Arrays.copyOfRange(lastValues, 450, 500))
|
||||
.parallel().iterator()
|
||||
.forEachRemaining(newQueue::offer);
|
||||
@ -139,18 +126,18 @@ public final class ShareManager {
|
||||
/** If the given player already has more than x (in this case 25) StatResults saved,
|
||||
remove the oldest one.*/
|
||||
private void removeExcessResults(String playerName) {
|
||||
List<StatResult> alreadySavedResults = statResultQueue.values()
|
||||
List<InternalStatResult> alreadySavedResults = statResultQueue.values()
|
||||
.parallelStream()
|
||||
.filter(result -> result.playerName().equalsIgnoreCase(playerName))
|
||||
.filter(result -> result.executorName().equalsIgnoreCase(playerName))
|
||||
.toList();
|
||||
|
||||
if (alreadySavedResults.size() > 25) {
|
||||
UUID uuid = alreadySavedResults
|
||||
int hashCode = alreadySavedResults
|
||||
.parallelStream()
|
||||
.min(Comparator.comparing(StatResult::ID))
|
||||
.orElseThrow().uuid();
|
||||
MyLogger.logMsg("Removing old stat no. " + statResultQueue.get(uuid).ID() + " for player " + playerName, DebugLevel.MEDIUM);
|
||||
statResultQueue.remove(uuid);
|
||||
.min(Comparator.comparing(InternalStatResult::ID))
|
||||
.orElseThrow().hashCode();
|
||||
MyLogger.logMsg("Removing old stat no. " + statResultQueue.get(hashCode).ID() + " for player " + playerName, DebugLevel.MEDIUM);
|
||||
statResultQueue.remove(hashCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,37 +2,40 @@ package com.gmail.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
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 static volatile ThreadManager instance;
|
||||
|
||||
private final static int threshold = 10;
|
||||
private int statThreadID;
|
||||
private int reloadThreadID;
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static OutputManager messageSender;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static OutputManager outputManager;
|
||||
private static StatManager statManager;
|
||||
|
||||
private ReloadThread lastActiveReloadThread;
|
||||
private StatThread lastActiveStatThread;
|
||||
private final HashMap<String, Thread> statThreads;
|
||||
private static long lastRecordedCalcTime;
|
||||
|
||||
private ThreadManager(ConfigHandler c, OutputManager m, OfflinePlayerHandler o) {
|
||||
config = c;
|
||||
messageSender = m;
|
||||
offlinePlayerHandler = o;
|
||||
public ThreadManager(ConfigHandler config, StatManager statManager, OutputManager outputManager) {
|
||||
ThreadManager.config = config;
|
||||
ThreadManager.outputManager = outputManager;
|
||||
ThreadManager.statManager = statManager;
|
||||
|
||||
statThreads = new HashMap<>();
|
||||
statThreadID = 0;
|
||||
@ -42,19 +45,6 @@ public final class ThreadManager {
|
||||
startReloadThread(null);
|
||||
}
|
||||
|
||||
public static ThreadManager getInstance(ConfigHandler config, OutputManager messageSender, OfflinePlayerHandler offlinePlayerHandler) {
|
||||
ThreadManager threadManager = instance;
|
||||
if (threadManager != null) {
|
||||
return threadManager;
|
||||
}
|
||||
synchronized (ThreadManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new ThreadManager(config, messageSender, offlinePlayerHandler);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getTaskThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
@ -63,7 +53,7 @@ public final class ThreadManager {
|
||||
if (lastActiveReloadThread == null || !lastActiveReloadThread.isAlive()) {
|
||||
reloadThreadID += 1;
|
||||
|
||||
lastActiveReloadThread = new ReloadThread(config, messageSender, offlinePlayerHandler, reloadThreadID, lastActiveStatThread, sender);
|
||||
lastActiveReloadThread = new ReloadThread(config, outputManager, reloadThreadID, lastActiveStatThread, sender);
|
||||
lastActiveReloadThread.start();
|
||||
}
|
||||
else {
|
||||
@ -71,19 +61,19 @@ public final class ThreadManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void startStatThread(StatRequest request) {
|
||||
public void startStatThread(StatRequest statRequest) {
|
||||
statThreadID += 1;
|
||||
String cmdSender = request.getCommandSender().getName();
|
||||
String cmdSender = statRequest.getCommandSender().getName();
|
||||
|
||||
if (config.limitStatRequests() && statThreads.containsKey(cmdSender)) {
|
||||
Thread runningThread = statThreads.get(cmdSender);
|
||||
if (runningThread.isAlive()) {
|
||||
messageSender.sendFeedbackMsg(request.getCommandSender(), StandardMessage.REQUEST_ALREADY_RUNNING);
|
||||
outputManager.sendFeedbackMsg(statRequest.getCommandSender(), StandardMessage.REQUEST_ALREADY_RUNNING);
|
||||
} else {
|
||||
startNewStatThread(request);
|
||||
startNewStatThread(statRequest);
|
||||
}
|
||||
} else {
|
||||
startNewStatThread(request);
|
||||
startNewStatThread(statRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,9 +89,9 @@ public final class ThreadManager {
|
||||
return lastRecordedCalcTime;
|
||||
}
|
||||
|
||||
private void startNewStatThread(StatRequest request) {
|
||||
lastActiveStatThread = new StatThread(config, messageSender, offlinePlayerHandler, statThreadID, request, lastActiveReloadThread);
|
||||
statThreads.put(request.getCommandSender().getName(), lastActiveStatThread);
|
||||
private void startNewStatThread(StatRequest statRequest) {
|
||||
lastActiveStatThread = new StatThread(outputManager, statManager, statThreadID, statRequest, lastActiveReloadThread);
|
||||
statThreads.put(statRequest.getCommandSender().getName(), lastActiveStatThread);
|
||||
lastActiveStatThread.start();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Statistic;
|
||||
|
||||
public interface Formatter {
|
||||
|
||||
/** Turns a TextComponent into its String representation. This method is equipped
|
||||
to turn all PlayerStats' formatted statResults into String.
|
||||
|
||||
@return a String representation of this TextComponent, without hover/click events,
|
||||
but with color, style and formatting. TranslatableComponents will be turned into
|
||||
plain English.*/
|
||||
default String TextComponentToString(TextComponent component) {
|
||||
return ComponentUtils.getTranslatableComponentSerializer()
|
||||
.serialize(component);
|
||||
}
|
||||
|
||||
/** @return [PlayerStats]*/
|
||||
TextComponent getPluginPrefix();
|
||||
|
||||
TextComponent getRainbowPluginPrefix();
|
||||
|
||||
/** @return ________ [PlayerStats] ________*/
|
||||
TextComponent getPluginPrefixAsTitle();
|
||||
|
||||
TextComponent getRainbowPluginPrefixAsTitle();
|
||||
|
||||
/** @return a single line from a top-x statistic:
|
||||
* <br> x. Player-name ......... number */
|
||||
TextComponent formatSingleTopStatLine(int positionInTopList, String playerName, long statNumber, Statistic statistic);
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.PlayerStatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.ServerStatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.TopStatRequest;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** The outgoing API that you can use to access the core functionality of PlayerStats!
|
||||
To work with the API, you need to call PlayerStats.{@link #getAPI()} to get an instance of
|
||||
{@link PlayerStatsAPI}. You can then use this object to access any of the further methods.
|
||||
<br>
|
||||
<br>Since calculating a top or server statistics can take some time, I strongly
|
||||
encourage you to call all the serverStat() and topStat() methods asynchronously.
|
||||
Otherwise, the main Thread will have to wait until all calculations are done,
|
||||
and this can severely impact server performance.
|
||||
*/
|
||||
public interface PlayerStats {
|
||||
|
||||
/** Gets an instance of the {@link PlayerStatsAPI}.
|
||||
|
||||
@return the PlayerStats API
|
||||
@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 a StatRequest object that can be used to look up a player-statistic.
|
||||
This StatRequest will have all default settings already configured,
|
||||
and will be processed as soon as you call one of its methods.
|
||||
|
||||
@return a PlayerStatRequest that can be used to look up a statistic for the
|
||||
Player whose name is provided*/
|
||||
PlayerStatRequest playerStat(String playerName);
|
||||
|
||||
/** Gets a StatRequest object that can be used to look up a server-statistic.
|
||||
This StatRequest will have all default settings already configured,
|
||||
and will be processed as soon as you call one of its methods.
|
||||
<br>
|
||||
<br> Don't call this from the main Thread! (see class description)
|
||||
|
||||
@return a ServerStatRequest that can be used to look up a server total*/
|
||||
ServerStatRequest serverStat();
|
||||
|
||||
/** Gets a StatRequest object that can be used to look up a top-x-statistic.
|
||||
This StatRequest will have all default settings already configured, and will be
|
||||
processed as soon as you call one of its methods.
|
||||
<br>
|
||||
<br> Don't call this from the main Thread! (see class description)
|
||||
|
||||
@param topListSize how big the top-x should be (10 by default)
|
||||
@return a TopStatRequest that can be used to look up a top statistic*/
|
||||
TopStatRequest topStat(int topListSize);
|
||||
|
||||
Formatter getFormatter();
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.*;
|
||||
|
||||
import static org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
/** The implementation of the API Interface */
|
||||
public final class PlayerStatsAPI implements PlayerStats {
|
||||
|
||||
private static StatCalculator statCalculator;
|
||||
private static StatFormatter statFormatter;
|
||||
|
||||
@Internal
|
||||
public PlayerStatsAPI(StatManager stat, StatFormatter format) {
|
||||
statCalculator = stat;
|
||||
statFormatter = format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatRequest playerStat(String playerName) {
|
||||
StatRequestHandler statRequestHandler = StatRequestHandler.playerRequestHandler(playerName);
|
||||
return new PlayerStatRequest(statRequestHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest serverStat() {
|
||||
StatRequestHandler statRequestHandler = StatRequestHandler.serverRequestHandler();
|
||||
return new ServerStatRequest(statRequestHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest topStat(int topListSize) {
|
||||
StatRequestHandler statRequestHandler = StatRequestHandler.topRequestHandler(topListSize);
|
||||
return new TopStatRequest(statRequestHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter getFormatter() {
|
||||
return statFormatter;
|
||||
}
|
||||
|
||||
static StatCalculator statCalculator() {
|
||||
return statCalculator;
|
||||
}
|
||||
|
||||
static StatFormatter statFormatter() {
|
||||
return statFormatter;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.StatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** Completes a basic {@link StatRequest} provided by the {@link PlayerStatsAPI}
|
||||
and performs a statistic lookup with the information that is stored inside this StatRequest.*/
|
||||
public interface RequestExecutor<T> {
|
||||
|
||||
@Internal
|
||||
default StatCalculator getStatCalculator() {
|
||||
return PlayerStatsAPI.statCalculator();
|
||||
}
|
||||
|
||||
@Internal
|
||||
default StatFormatter getStatFormatter() {
|
||||
return PlayerStatsAPI.statFormatter();
|
||||
}
|
||||
|
||||
/** Gets a StatResult for a Statistic of Statistic.Type {@code Untyped}.
|
||||
|
||||
@param statistic a Statistic of Type.Untyped
|
||||
@return a {@link StatResult}
|
||||
@throws IllegalArgumentException if <code>statistic</code> is not of Type.Untyped*/
|
||||
StatResult<T> untyped(@NotNull Statistic statistic) throws IllegalArgumentException;
|
||||
|
||||
/** Gets a StatResult for a Statistic of Statistic.Type Block or Item.
|
||||
|
||||
@param statistic a Statistic of Type.Block or Type.Item
|
||||
@param material a block if the <code>statistic</code> is of Type.Block,
|
||||
and an item if the <code>statistic</code> is of Type.Item
|
||||
@throws IllegalArgumentException if <code>statistic</code> is not of Type.Block
|
||||
(with a block as <code>material</code>), or <code>statistic</code> is not of Type.Item
|
||||
(with an item as <code>material</code>)
|
||||
@return a {@link StatResult} */
|
||||
StatResult<T> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException;
|
||||
|
||||
/** Gets a StatResult for a Statistic of Statistic.Type Entity.
|
||||
|
||||
@param statistic a Statistic of Type.Entity
|
||||
@param entityType an EntityType
|
||||
@throws IllegalArgumentException if <code>statistic</code> is not of Type.Entity,
|
||||
or <code>entityType</code> is not a valid EntityType
|
||||
@return a {@link StatResult} */
|
||||
StatResult<T> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** Turns user input into a completed {@link StatRequest}. This StatRequest should hold all
|
||||
the information PlayerStats needs to work with, and is used by the {@link StatCalculator}
|
||||
to get the desired statistic data.*/
|
||||
public interface RequestGenerator {
|
||||
|
||||
/** Gets a StatRequest for a Statistic of Statistic.Type {@code Untyped}.
|
||||
|
||||
@param statistic a Statistic of Type.Untyped
|
||||
@return a {@link StatRequest}
|
||||
@throws IllegalArgumentException if <code>statistic</code> is not of Type.Untyped*/
|
||||
StatRequest untyped(@NotNull Statistic statistic) throws IllegalArgumentException;
|
||||
|
||||
/** Gets a StatRequest for a Statistic of Statistic.Type Block or Item.
|
||||
|
||||
@param statistic a Statistic of Type.Block or Type.Item
|
||||
@param material a block if the <code>statistic</code> is of Type.Block,
|
||||
and an item if the <code>statistic</code> is of Type.Item
|
||||
@return a {@link StatRequest}
|
||||
@throws IllegalArgumentException if <code>statistic</code> is not of Type.Block
|
||||
(with a block as <code>material</code>), or <code>statistic</code> is not of Type.Item
|
||||
(with an item as <code>material</code>) */
|
||||
StatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException;
|
||||
|
||||
/** Gets a StatRequest for a Statistic of Statistic.Type Entity.
|
||||
|
||||
@param statistic a Statistic of Type.Entity
|
||||
@param entityType an EntityType
|
||||
@return a {@link StatRequest}
|
||||
@throws IllegalArgumentException if <code>statistic</code> is not of Type.Entity*/
|
||||
StatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException;
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/** The {@link StatCalculator} is responsible for getting, calculating and/or ordering raw numbers.
|
||||
It represents the actual statistic-getting magic that happens once a valid
|
||||
{@link StatRequest} is passed to it.
|
||||
<br>
|
||||
<br>The StatCalculator gets its data from the vanilla statistic files (stored by the server). It can return three kinds of data,
|
||||
depending on the chosen {@link Target}:
|
||||
<br>- int (for {@link Target#PLAYER})
|
||||
<br>- long (for {@link Target#SERVER})
|
||||
<br>- LinkedHashMap[String player-name, Integer number] (for {@link Target#TOP})
|
||||
<br>
|
||||
<br>For more information on how to create a valid StatRequest,
|
||||
see the class description for {@link StatRequest}.*/
|
||||
@Internal
|
||||
public interface StatCalculator {
|
||||
|
||||
/** Returns the requested Statistic*/
|
||||
int getPlayerStat(StatRequest statRequest);
|
||||
|
||||
/** Don't call from main Thread!*/
|
||||
long getServerStat(StatRequest statRequest);
|
||||
|
||||
/** Don't call from main Thread!*/
|
||||
LinkedHashMap<String, Integer> getTopStats(StatRequest statRequest);
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.*;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/** The {@link StatFormatter} formats raw numbers into pretty messages.
|
||||
This Formatter takes a {@link StatRequest} and combines it with the raw number(s)
|
||||
returned by the {@link StatCalculator}, and transforms those into a pretty message
|
||||
(by default a TextComponent) with all the relevant information in it.
|
||||
<br>
|
||||
<br>The output is ready to be sent to a Minecraft client or console with the Adventure library.
|
||||
To send a Component, you need to get a {@link BukkitAudiences} object. Normally you would
|
||||
have to add the library as a dependency, but since the library is included in PlayerStats, you can
|
||||
access it directly. Information on how to get and use the BukkitAudiences object can be found on
|
||||
<a href="https://docs.adventure.kyori.net/platform/bukkit.html">Adventure's website</a>.
|
||||
<br>
|
||||
<br>Alternatively, you can also turn your TextComponent into a plain String with
|
||||
{@link #TextComponentToString(TextComponent)}. Don't use Adventure's method .content()
|
||||
on your formattedValue to do this - because of the way the TextComponent is built by PlayerStats,
|
||||
you won't be able to get the full content that way.*/
|
||||
@Internal
|
||||
public
|
||||
interface StatFormatter extends Formatter {
|
||||
|
||||
/** @return a TextComponent with the following parts:
|
||||
<br>[player-name]: [number] [stat-name] {sub-stat-name}*/
|
||||
TextComponent formatPlayerStat(StatRequest statRequest, int playerStat);
|
||||
|
||||
/** @return a TextComponent with the following parts:
|
||||
<br>[Total on] [server-name]: [number] [stat-name] [sub-stat-name]*/
|
||||
TextComponent formatServerStat(StatRequest statRequest, 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 formatTopStat(StatRequest statRequest, LinkedHashMap<String, Integer> topStats);
|
||||
}
|
@ -7,7 +7,7 @@ import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ReloadCommand implements CommandExecutor {
|
||||
public final class ReloadCommand implements CommandExecutor {
|
||||
|
||||
private static ThreadManager threadManager;
|
||||
|
||||
|
@ -2,7 +2,7 @@ package com.gmail.artemis.the.gr8.playerstats.commands;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ShareManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.gmail.artemis.the.gr8.playerstats.models.StatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.InternalStatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import org.bukkit.command.Command;
|
||||
@ -10,9 +10,7 @@ import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ShareCommand implements CommandExecutor {
|
||||
public final class ShareCommand implements CommandExecutor {
|
||||
|
||||
private static ShareManager shareManager;
|
||||
private static OutputManager outputManager;
|
||||
@ -24,12 +22,12 @@ public class ShareCommand implements CommandExecutor {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) {
|
||||
if (args.length == 1 && shareManager.isEnabled()) {
|
||||
UUID shareCode;
|
||||
if (args.length == 1 && ShareManager.isEnabled()) {
|
||||
int shareCode;
|
||||
try {
|
||||
shareCode = UUID.fromString(args[0]);
|
||||
shareCode = Integer.parseInt(args[0]);
|
||||
} catch (IllegalArgumentException e) {
|
||||
MyLogger.logException(e, "ShareCommand", "/statshare is being called without a valid UUID argument");
|
||||
MyLogger.logException(e, "ShareCommand", "/statshare is being called without a valid share-code!");
|
||||
return false;
|
||||
}
|
||||
if (shareManager.requestAlreadyShared(shareCode)) {
|
||||
@ -39,11 +37,11 @@ public class ShareCommand implements CommandExecutor {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.STILL_ON_SHARE_COOLDOWN);
|
||||
}
|
||||
else {
|
||||
StatResult result = shareManager.getStatResult(sender.getName(), shareCode);
|
||||
if (result == null) { //at this point the only possible cause of statResult being null is the request being older than 25 player-requests ago
|
||||
InternalStatResult result = shareManager.getStatResult(sender.getName(), shareCode);
|
||||
if (result == null) { //at this point the only possible cause of formattedValue being null is the request being older than 25 player-requests ago
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.STAT_RESULTS_TOO_OLD);
|
||||
} else {
|
||||
outputManager.shareStatResults(result.statResult());
|
||||
outputManager.sendToAllPlayers(result.formattedValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,31 +3,24 @@ package com.gmail.artemis.the.gr8.playerstats.commands;
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequestHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.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.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
public class StatCommand implements CommandExecutor {
|
||||
public final class StatCommand implements CommandExecutor {
|
||||
|
||||
private static ThreadManager threadManager;
|
||||
private static OutputManager outputManager;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
public StatCommand(OutputManager m, ThreadManager t, OfflinePlayerHandler o) {
|
||||
public StatCommand(OutputManager m, ThreadManager t) {
|
||||
threadManager = t;
|
||||
outputManager = m;
|
||||
offlinePlayerHandler = o;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -40,138 +33,44 @@ public class StatCommand implements CommandExecutor {
|
||||
outputManager.sendExamples(sender);
|
||||
}
|
||||
else {
|
||||
StatRequest request = generateRequest(sender, args);
|
||||
if (requestIsValid(request)) {
|
||||
threadManager.startStatThread(request);
|
||||
StatRequestHandler statRequestHandler = StatRequestHandler.internalRequestHandler(sender);
|
||||
StatRequest statRequest = statRequestHandler.getRequestFromArgs(args);
|
||||
|
||||
if (statRequest.isValid()) {
|
||||
threadManager.startStatThread(statRequest);
|
||||
} else {
|
||||
sendFeedback(statRequest);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Create a StatRequest Object with all the relevant information from the args[]. */
|
||||
private StatRequest generateRequest(CommandSender sender, String[] args) {
|
||||
StatRequest request = new StatRequest(sender);
|
||||
for (String arg : args) {
|
||||
//check for statName
|
||||
if (EnumHandler.isStatistic(arg) && request.getStatistic() == null) {
|
||||
request.setStatistic(EnumHandler.getStatEnum(arg));
|
||||
}
|
||||
//check for subStatEntry and playerFlag
|
||||
else if (EnumHandler.isSubStatEntry(arg)) {
|
||||
if (arg.equalsIgnoreCase("player") && !request.playerFlag()) {
|
||||
request.setPlayerFlag(true);
|
||||
}
|
||||
else {
|
||||
if (request.getSubStatEntry() == null) request.setSubStatEntry(arg);
|
||||
}
|
||||
}
|
||||
//check for selection
|
||||
else if (arg.equalsIgnoreCase("top")) {
|
||||
request.setSelection(Target.TOP);
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("server")) {
|
||||
request.setSelection(Target.SERVER);
|
||||
}
|
||||
else if (arg.equalsIgnoreCase("me")) {
|
||||
if (sender instanceof Player) {
|
||||
request.setPlayerName(sender.getName());
|
||||
request.setSelection(Target.PLAYER);
|
||||
}
|
||||
else if (sender instanceof ConsoleCommandSender) {
|
||||
request.setSelection(Target.SERVER);
|
||||
}
|
||||
}
|
||||
else if (offlinePlayerHandler.isRelevantPlayer(arg) && request.getPlayerName() == null) {
|
||||
request.setPlayerName(arg);
|
||||
request.setSelection(Target.PLAYER);
|
||||
}
|
||||
}
|
||||
patchRequest(request);
|
||||
return request;
|
||||
}
|
||||
/** If a given {@link StatRequest} does not result in a valid statistic look-up,
|
||||
this will send a feedback message to the CommandSender that made the request.
|
||||
<br> 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 statRequest the StatRequest to give feedback on
|
||||
*/
|
||||
private void sendFeedback(StatRequest statRequest) {
|
||||
CommandSender sender = statRequest.getCommandSender();
|
||||
|
||||
/** Adjust the StatRequest object if needed: unpack the playerFlag into a subStatEntry,
|
||||
try to retrieve the corresponding Enum Constant for any relevant block/entity/item,
|
||||
and remove any unnecessary subStatEntries.*/
|
||||
private void patchRequest(StatRequest request) {
|
||||
if (request.getStatistic() != null) {
|
||||
Statistic.Type type = request.getStatistic().getType();
|
||||
|
||||
if (request.playerFlag()) { //unpack the playerFlag
|
||||
if (type == Statistic.Type.ENTITY && request.getSubStatEntry() == null) {
|
||||
request.setSubStatEntry("player");
|
||||
}
|
||||
else {
|
||||
request.setSelection(Target.PLAYER);
|
||||
}
|
||||
}
|
||||
|
||||
String subStatEntry = request.getSubStatEntry();
|
||||
switch (type) { //attempt to convert relevant subStatEntries into their corresponding Enum Constant
|
||||
case BLOCK -> {
|
||||
Material block = EnumHandler.getBlockEnum(subStatEntry);
|
||||
if (block != null) request.setBlock(block);
|
||||
}
|
||||
case ENTITY -> {
|
||||
EntityType entity = EnumHandler.getEntityEnum(subStatEntry);
|
||||
if (entity != null) request.setEntity(entity);
|
||||
}
|
||||
case ITEM -> {
|
||||
Material item = EnumHandler.getItemEnum(subStatEntry);
|
||||
if (item != null) request.setItem(item);
|
||||
}
|
||||
case UNTYPED -> { //remove unnecessary subStatEntries
|
||||
if (subStatEntry != null) request.setSubStatEntry(null);
|
||||
}
|
||||
}
|
||||
if (statRequest.getStatistic() == null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_STAT_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method validates the StatRequest and returns feedback to the player if it returns false.
|
||||
It checks the following:
|
||||
<p>1. Is a Statistic set?</p>
|
||||
<p>2. Is a subStat needed, and is a subStat Enum Constant present? (block/entity/item)</p>
|
||||
<p>3. If the target is PLAYER, is a valid PlayerName provided? </p>
|
||||
@return true if the Request is valid, and false + an explanation message otherwise. */
|
||||
private boolean requestIsValid(StatRequest request) {
|
||||
if (request.getStatistic() == null) {
|
||||
outputManager.sendFeedbackMsg(request.getCommandSender(), StandardMessage.MISSING_STAT_NAME);
|
||||
return false;
|
||||
}
|
||||
Statistic.Type type = request.getStatistic().getType();
|
||||
if (request.getSubStatEntry() == null && type != Statistic.Type.UNTYPED) {
|
||||
outputManager.sendFeedbackMsgMissingSubStat(request.getCommandSender(), type);
|
||||
return false;
|
||||
}
|
||||
else if (!matchingSubStat(request)) {
|
||||
outputManager.sendFeedbackMsgWrongSubStat(request.getCommandSender(), type, request.getSubStatEntry());
|
||||
return false;
|
||||
}
|
||||
else if (request.getSelection() == Target.PLAYER && request.getPlayerName() == null) {
|
||||
outputManager.sendFeedbackMsg(request.getCommandSender(), StandardMessage.MISSING_PLAYER_NAME);
|
||||
return false;
|
||||
else if (statRequest.getTarget() == Target.PLAYER && statRequest.getPlayerName() == null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_PLAYER_NAME);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchingSubStat(StatRequest request) {
|
||||
Statistic.Type type = request.getStatistic().getType();
|
||||
switch (type) {
|
||||
case BLOCK -> {
|
||||
return request.getBlock() != null;
|
||||
}
|
||||
case ENTITY -> {
|
||||
return request.getEntity() != null;
|
||||
}
|
||||
case ITEM -> {
|
||||
return request.getItem() != null;
|
||||
}
|
||||
default -> {
|
||||
return true;
|
||||
Statistic.Type type = statRequest.getStatistic().getType();
|
||||
if (type != Statistic.Type.UNTYPED && statRequest.getSubStatEntryName() == null) {
|
||||
outputManager.sendFeedbackMsgMissingSubStat(sender, type);
|
||||
} else {
|
||||
outputManager.sendFeedbackMsgWrongSubStat(sender, type, statRequest.getSubStatEntryName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,16 +12,18 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
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(OfflinePlayerHandler o) {
|
||||
offlinePlayerHandler = o;
|
||||
tabCompleteHelper = new TabCompleteHelper();
|
||||
public TabCompleter(EnumHandler enumHandler, OfflinePlayerHandler offlinePlayerHandler) {
|
||||
this.enumHandler = enumHandler;
|
||||
this.offlinePlayerHandler = offlinePlayerHandler;
|
||||
tabCompleteHelper = new TabCompleteHelper(enumHandler);
|
||||
|
||||
commandOptions = new ArrayList<>();
|
||||
commandOptions.add("top");
|
||||
@ -33,8 +35,8 @@ public class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
|
||||
//args[0] = statistic (length = 1)
|
||||
//args[1] = commandOption (top/player/me) OR substatistic (block/item/entitytype) (length = 2)
|
||||
//args[2] = playerName OR commandOption (top/player/me) (length = 3)
|
||||
//args[3] = playerName (length = 4)
|
||||
//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) {
|
||||
@ -50,7 +52,7 @@ public class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
else { //after checking if args[0] is a viable statistic, suggest substatistic OR commandOptions
|
||||
String previousArg = args[args.length -2];
|
||||
|
||||
if (EnumHandler.isStatistic(previousArg)) {
|
||||
if (enumHandler.isStatistic(previousArg)) {
|
||||
Statistic stat = EnumHandler.getStatEnum(previousArg);
|
||||
if (stat != null) {
|
||||
tabSuggestions = getTabSuggestions(getRelevantList(stat), currentArg);
|
||||
@ -60,7 +62,7 @@ public class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
//if previous arg = "player"
|
||||
else if (previousArg.equalsIgnoreCase("player")) {
|
||||
|
||||
if (args.length >= 3 && EnumHandler.isEntityStatistic(args[args.length-3])) {
|
||||
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
|
||||
@ -69,7 +71,7 @@ public class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
}
|
||||
|
||||
//after a substatistic, suggest commandOptions
|
||||
else if (EnumHandler.isSubStatEntry(previousArg)) {
|
||||
else if (enumHandler.isSubStatEntry(previousArg)) {
|
||||
tabSuggestions = commandOptions;
|
||||
}
|
||||
}
|
||||
@ -78,7 +80,7 @@ public class TabCompleter implements org.bukkit.command.TabCompleter {
|
||||
}
|
||||
|
||||
private List<String> getFirstArgSuggestions(String currentArg) {
|
||||
List<String> suggestions = EnumHandler.getStatNames();
|
||||
List<String> suggestions = enumHandler.getStatNames();
|
||||
suggestions.add("examples");
|
||||
suggestions.add("help");
|
||||
return getTabSuggestions(suggestions, currentArg);
|
||||
|
@ -10,15 +10,17 @@ 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() {
|
||||
public TabCompleteHelper(EnumHandler enumHandler) {
|
||||
this.enumHandler = enumHandler;
|
||||
prepareLists();
|
||||
}
|
||||
|
||||
public List<String> getAllItemNames() {
|
||||
return EnumHandler.getItemNames();
|
||||
return enumHandler.getItemNames();
|
||||
}
|
||||
|
||||
public List<String> getItemBrokenSuggestions() {
|
||||
@ -26,7 +28,7 @@ public final class TabCompleteHelper {
|
||||
}
|
||||
|
||||
public List<String> getAllBlockNames() {
|
||||
return EnumHandler.getBlockNames();
|
||||
return enumHandler.getBlockNames();
|
||||
}
|
||||
|
||||
public List<String> getEntitySuggestions() {
|
||||
@ -52,4 +54,4 @@ public final class TabCompleteHelper {
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package com.gmail.artemis.the.gr8.playerstats.config;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
@ -10,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ConfigHandler {
|
||||
public final class ConfigHandler {
|
||||
|
||||
private static Main plugin;
|
||||
private static int configVersion;
|
||||
@ -18,8 +19,8 @@ public class ConfigHandler {
|
||||
private File configFile;
|
||||
private FileConfiguration config;
|
||||
|
||||
public ConfigHandler(Main p) {
|
||||
plugin = p;
|
||||
public ConfigHandler(Main plugin) {
|
||||
ConfigHandler.plugin = plugin;
|
||||
configVersion = 6;
|
||||
|
||||
saveDefaultConfig();
|
||||
@ -29,11 +30,12 @@ public class ConfigHandler {
|
||||
MyLogger.setDebugLevel(getDebugLevel());
|
||||
}
|
||||
|
||||
/** Checks the number that "config-version" returns to see if the config needs updating, and if so, send it to the Updater.
|
||||
<p>PlayerStats 1.1: "config-version" doesn't exist.</p>
|
||||
<p>PlayerStats 1.2: "config-version" is 2.</p>
|
||||
<p>PlayerStats 1.3: "config-version" is 3. </P>
|
||||
<p>PlayerStats 1.4: "config-version" is 4.</p>*/
|
||||
/** Checks the number that "config-version" returns to see if the config needs updating, and if so, send it to the {@link ConfigUpdateHandler}.
|
||||
<br></br>
|
||||
<br>PlayerStats 1.1: "config-version" doesn't exist.</br>
|
||||
<br>PlayerStats 1.2: "config-version" is 2.</br>
|
||||
<br>PlayerStats 1.3: "config-version" is 3. </br>
|
||||
<br>PlayerStats 1.4: "config-version" is 4.</br>*/
|
||||
private void checkConfigVersion() {
|
||||
if (!config.contains("config-version") || config.getInt("config-version") != configVersion) {
|
||||
new ConfigUpdateHandler(plugin, configFile, configVersion);
|
||||
@ -49,7 +51,7 @@ public class ConfigHandler {
|
||||
}
|
||||
|
||||
/** 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 MyLogger. */
|
||||
Also reads the value for debug-level and passes it on to {@link MyLogger}. */
|
||||
public boolean reloadConfig() {
|
||||
if (!configFile.exists()) {
|
||||
saveDefaultConfig();
|
||||
@ -65,76 +67,81 @@ public class ConfigHandler {
|
||||
}
|
||||
|
||||
/** Returns the desired debugging level.
|
||||
<p>1 = low (only show unexpected errors)</p>
|
||||
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
|
||||
<p>3 = high (log all tasks and time taken)</p>
|
||||
<p>Default: 1</p>*/
|
||||
<br></br>
|
||||
<br>1 = low (only show unexpected errors)</br>
|
||||
<br>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</br>
|
||||
<br>3 = high (log all tasks and time taken)</br>
|
||||
<br></br>
|
||||
<br>Default: 1</br>*/
|
||||
public int getDebugLevel() {
|
||||
return config.getInt("debug-level", 1);
|
||||
}
|
||||
|
||||
/** Returns true if command-senders should be limited to one stat-request at a time.
|
||||
<p>Default: true</p>*/
|
||||
<br>Default: true</br>*/
|
||||
public boolean limitStatRequests() {
|
||||
return config.getBoolean("only-allow-one-lookup-at-a-time-per-player", true);
|
||||
}
|
||||
|
||||
/** Returns true if stat-sharing is allowed.
|
||||
<p>Default: true</p>*/
|
||||
<br>Default: true</br>*/
|
||||
public boolean allowStatSharing() {
|
||||
return config.getBoolean("enable-stat-sharing", true);
|
||||
}
|
||||
|
||||
/** Returns the number of minutes a player has to wait before being able to
|
||||
share another stat-result.
|
||||
<p>Default: 0</p>*/
|
||||
<br>Default: 0</br>*/
|
||||
public int getStatShareWaitingTime() {
|
||||
return config.getInt("waiting-time-before-sharing-again", 0);
|
||||
}
|
||||
|
||||
/** Returns the config setting for include-whitelist-only.
|
||||
<p>Default: false</p>*/
|
||||
<br>Default: false</br>*/
|
||||
public boolean whitelistOnly() {
|
||||
return config.getBoolean("include-whitelist-only", false);
|
||||
}
|
||||
|
||||
/** Returns the config setting for exclude-banned-players.
|
||||
<p>Default: false</p>*/
|
||||
<br>Default: false</br>*/
|
||||
public boolean excludeBanned() {
|
||||
return config.getBoolean("exclude-banned-players", false);
|
||||
}
|
||||
|
||||
/** Returns the number of maximum days since a player has last been online.
|
||||
<p>Default: 0 (which signals not to use this limit)</p>*/
|
||||
<br>Default: 0 (which signals not to use this limit)</br>*/
|
||||
public int getLastPlayedLimit() {
|
||||
return config.getInt("number-of-days-since-last-joined", 0);
|
||||
}
|
||||
|
||||
/** Whether to use TranslatableComponents wherever possible.
|
||||
Currently supported: statistic, block, item and entity names.
|
||||
<p>Default: true</p>*/
|
||||
<br>Default: true</br>*/
|
||||
public boolean useTranslatableComponents() {
|
||||
return config.getBoolean("translate-to-client-language", true);
|
||||
}
|
||||
|
||||
/** Whether to use HoverComponents for additional information.
|
||||
<p>Default: true</p>*/
|
||||
<br>Default: true</br>*/
|
||||
public boolean useHoverText() {
|
||||
return config.getBoolean("enable-hover-text", true);
|
||||
}
|
||||
|
||||
/** Whether to use festive formatting, such as pride colors.
|
||||
<p>Default: true</p> */
|
||||
<br>Default: true</br> */
|
||||
public boolean useFestiveFormatting() {
|
||||
return config.getBoolean("enable-festive-formatting", true);
|
||||
}
|
||||
|
||||
/** Whether to use rainbow colors for the [PlayerStats] prefix rather than the default gold/purple.
|
||||
<p>Default: false</p> */
|
||||
<br>Default: false</br> */
|
||||
public boolean useRainbowMode() {
|
||||
return config.getBoolean("rainbow-mode", false);
|
||||
}
|
||||
|
||||
/** Whether to use enters before the statistic output in chat.
|
||||
Enters create some separation between the previous things that have been said in chat and the stat-result.
|
||||
<br>Default: true for non-shared top statistics, false for everything else</br>*/
|
||||
public boolean useEnters(Target selection, boolean getSharedSetting) {
|
||||
ConfigurationSection section = config.getConfigurationSection("use-enters");
|
||||
boolean def = selection == Target.TOP && !getSharedSetting;
|
||||
@ -153,112 +160,112 @@ public class ConfigHandler {
|
||||
}
|
||||
|
||||
/** Returns the config setting for use-dots.
|
||||
<p>Default: true</p>*/
|
||||
<br>Default: true</br>*/
|
||||
public boolean useDots() {
|
||||
return config.getBoolean("use-dots", true);
|
||||
}
|
||||
|
||||
/** Returns the config setting for top-list-max-size.
|
||||
<p>Default: 10</p> */
|
||||
<br>Default: 10</br> */
|
||||
public int getTopListMaxSize() {
|
||||
return config.getInt("top-list-max-size", 10);
|
||||
}
|
||||
|
||||
/** Returns a String that represents the title for a top statistic.
|
||||
<p>Default: "Top"</p>*/
|
||||
<br>Default: "Top"</br>*/
|
||||
public String getTopStatsTitle() {
|
||||
return config.getString("top-list-title", "Top");
|
||||
}
|
||||
|
||||
/** Returns a String that represents the title for a server stat.
|
||||
<p>Default: "Total on"</p> */
|
||||
<br>Default: "Total on"</br> */
|
||||
public String getServerTitle() {
|
||||
return config.getString("total-server-stat-title", "Total on");
|
||||
}
|
||||
|
||||
/** Returns the specified server name for a server stat title.
|
||||
<p>Default: "this server"</p>*/
|
||||
<br>Default: "this server"</br>*/
|
||||
public String getServerName() {
|
||||
return config.getString("your-server-name", "this server");
|
||||
}
|
||||
|
||||
/** Returns the unit that should be used for distance-related statistics.
|
||||
<p>Default: Blocks for plain text, km for hover-text</p>*/
|
||||
public String getDistanceUnit(boolean isHoverText) {
|
||||
return getUnitString(isHoverText, "blocks", "km", "distance-unit");
|
||||
<br>Default: Blocks for plain text, km for hover-text</br>*/
|
||||
public String getDistanceUnit(boolean isUnitForHoverText) {
|
||||
return getUnitString(isUnitForHoverText, "blocks", "km", "distance-unit");
|
||||
}
|
||||
|
||||
/** Returns the unit that should be used for damage-based statistics.
|
||||
<p>Default: Hearts for plain text, HP for hover-text.</p>*/
|
||||
public String getDamageUnit(boolean isHoverText) {
|
||||
return getUnitString(isHoverText, "hearts", "hp", "damage-unit");
|
||||
<br>Default: Hearts for plain text, HP for hover-text.</br>*/
|
||||
public String getDamageUnit(boolean isUnitForHoverText) {
|
||||
return getUnitString(isUnitForHoverText, "hearts", "hp", "damage-unit");
|
||||
}
|
||||
|
||||
/** Whether PlayerStats should automatically detect the most suitable unit to use for time-based statistics.
|
||||
<p>Default: true</p>*/
|
||||
public boolean autoDetectTimeUnit(boolean isHoverText) {
|
||||
<br>Default: true</br>*/
|
||||
public boolean autoDetectTimeUnit(boolean isUnitForHoverText) {
|
||||
String path = "auto-detect-biggest-time-unit";
|
||||
if (isHoverText) {
|
||||
if (isUnitForHoverText) {
|
||||
path = path + "-for-hover-text";
|
||||
}
|
||||
boolean defaultValue = !isHoverText;
|
||||
boolean defaultValue = !isUnitForHoverText;
|
||||
return config.getBoolean(path, defaultValue);
|
||||
}
|
||||
|
||||
/** How many additional units should be displayed next to the most suitable largest unit for time-based statistics.
|
||||
<p>Default: 1 for plain text, 0 for hover-text</p>*/
|
||||
public int getNumberOfExtraTimeUnits(boolean isHoverText) {
|
||||
<br>Default: 1 for plain text, 0 for hover-text</br>*/
|
||||
public int getNumberOfExtraTimeUnits(boolean isUnitForHoverText) {
|
||||
String path = "number-of-extra-units";
|
||||
if (isHoverText) {
|
||||
if (isUnitForHoverText) {
|
||||
path = path + "-for-hover-text";
|
||||
}
|
||||
int defaultValue = isHoverText ? 0 : 1;
|
||||
int defaultValue = isUnitForHoverText ? 0 : 1;
|
||||
return config.getInt(path, defaultValue);
|
||||
}
|
||||
|
||||
/** Returns the unit that should be used for time-based statistics.
|
||||
(this will return the largest unit that should be used).
|
||||
<p>Default: days for plain text, hours for hover-text</p>*/
|
||||
public String getTimeUnit(boolean isHoverText) {
|
||||
return getTimeUnit(isHoverText, false);
|
||||
<br>Default: days for plain text, hours for hover-text</br>*/
|
||||
public String getTimeUnit(boolean isUnitForHoverText) {
|
||||
return getTimeUnit(isUnitForHoverText, false);
|
||||
}
|
||||
|
||||
/** Returns the unit that should be used for time-based statistics. If the optional smallUnit flag is true,
|
||||
this will return the smallest unit (and otherwise the largest).
|
||||
<p>Default: hours for plain text, seconds for hover-text</p>*/
|
||||
public String getTimeUnit(boolean isHoverText, boolean smallUnit) {
|
||||
<br>Default: hours for plain text, seconds for hover-text</br>*/
|
||||
public String getTimeUnit(boolean isUnitForHoverText, boolean smallUnit) {
|
||||
if (smallUnit) {
|
||||
return getUnitString(isHoverText, "hours", "seconds", "smallest-time-unit");
|
||||
return getUnitString(isUnitForHoverText, "hours", "seconds", "smallest-time-unit");
|
||||
}
|
||||
return getUnitString(isHoverText, "days", "hours", "biggest-time-unit");
|
||||
return getUnitString(isUnitForHoverText, "days", "hours", "biggest-time-unit");
|
||||
}
|
||||
|
||||
/** Returns an integer between 0 and 100 that represents how much lighter a hoverColor should be.
|
||||
So 20 would mean 20% lighter.
|
||||
<p>Default: 20</p>*/
|
||||
<br>Default: 20</br>*/
|
||||
public int getHoverTextAmountLighter() {
|
||||
return config.getInt("hover-text-amount-lighter", 20);
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
|
||||
* <p>Style: "italic"</p>
|
||||
* <p>Color: "gray"</p>*/
|
||||
* <br>Style: "italic"</br>
|
||||
* <br>Color: "gray"</br>*/
|
||||
public String getSharedByTextDecoration(boolean getStyleSetting) {
|
||||
String def = getStyleSetting ? "italic" : "gray";
|
||||
return getDecorationString(null, getStyleSetting, def, "shared-by");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
|
||||
* <p>Style: "none"</p>
|
||||
* <p>Color: "#845EC2"</p>*/
|
||||
* <br>Style: "none"</br>
|
||||
* <br>Color: "#845EC2"</br>*/
|
||||
public String getSharerNameDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(null, getStyleSetting, "#845EC2", "player-name");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
|
||||
<p>Style: "none"</p>
|
||||
<p>Color Top: "green"</p>
|
||||
<p>Color Individual/Server: "gold"</p>*/
|
||||
<br>Style: "none"</br>
|
||||
<br>Color Top: "green"</br>
|
||||
<br>Color Individual/Server: "gold"</br>*/
|
||||
public String getPlayerNameDecoration(Target selection, boolean getStyleSetting) {
|
||||
String def;
|
||||
if (selection == Target.TOP) {
|
||||
@ -270,10 +277,10 @@ public class ConfigHandler {
|
||||
return getDecorationString(selection, getStyleSetting, def, "player-names");
|
||||
}
|
||||
|
||||
/** Returns true if playerNames Style is "bold", false if it is not.
|
||||
<p>Default: false</p>*/
|
||||
/** Returns true if playerNames Style is "bold" for a top-stat, false if it is not.
|
||||
<br>Default: false</br>*/
|
||||
public boolean playerNameIsBold() {
|
||||
ConfigurationSection style = getRelevantSection(Target.PLAYER);
|
||||
ConfigurationSection style = getRelevantSection(Target.TOP);
|
||||
|
||||
if (style != null) {
|
||||
String styleString = style.getString("player-names");
|
||||
@ -283,23 +290,23 @@ public class ConfigHandler {
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
|
||||
<p>Style: "none"</p>
|
||||
<p>Color: "yellow"</p>*/
|
||||
<br>Style: "none"</br>
|
||||
<br>Color: "yellow"</br>*/
|
||||
public String getStatNameDecoration(Target selection, boolean getStyleSetting) {
|
||||
return getDecorationString(selection, getStyleSetting, "yellow", "stat-names");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
|
||||
<p>Style: "none"</p>
|
||||
<p>Color: "#FFD52B"</p>*/
|
||||
<br>Style: "none"</br>
|
||||
<br>Color: "#FFD52B"</br>*/
|
||||
public String getSubStatNameDecoration(Target selection, boolean getStyleSetting) {
|
||||
return getDecorationString(selection, getStyleSetting, "#FFD52B", "sub-stat-names");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
|
||||
<p>Style: "none"</p>
|
||||
<p>Color Top: "#55AAFF"</p>
|
||||
<p>Color Individual/Server: "#ADE7FF"</p> */
|
||||
<br>Style: "none"</br>
|
||||
<br>Color Top: "#55AAFF"</br>
|
||||
<br>Color Individual/Server: "#ADE7FF"</br> */
|
||||
public String getStatNumberDecoration(Target selection, boolean getStyleSetting) {
|
||||
String def;
|
||||
if (selection == Target.TOP) {
|
||||
@ -312,9 +319,9 @@ public class ConfigHandler {
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
|
||||
<p>Style: "none"</p>
|
||||
<p>Color Top: "yellow"</p>
|
||||
<p>Color Server: "gold"</p>*/
|
||||
<br>Style: "none"</br>
|
||||
<br>Color Top: "yellow"</br>
|
||||
<br>Color Server: "gold"</br>*/
|
||||
public String getTitleDecoration(Target selection, boolean getStyleSetting) {
|
||||
String def;
|
||||
if (selection == Target.TOP) {
|
||||
@ -327,34 +334,34 @@ public class ConfigHandler {
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
|
||||
<p>Style: "none"</p>
|
||||
<p>Color: "gold"</p>*/
|
||||
<br>Style: "none"</br>
|
||||
<br>Color: "gold"</br>*/
|
||||
public String getTitleNumberDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(Target.TOP, getStyleSetting, "gold", "title-number");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
|
||||
<p>Style: "none"</p>
|
||||
<p>Color: "#FFB80E"</p>*/
|
||||
<br>Style: "none"</br>
|
||||
<br>Color: "#FFB80E"</br>*/
|
||||
public String getServerNameDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(Target.SERVER, getStyleSetting, "#FFB80E", "server-name");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
|
||||
<p>Style: "none"</p>
|
||||
<p>Color: "gold"</p>*/
|
||||
<br>Style: "none"</br>
|
||||
<br>Color: "gold"</br>*/
|
||||
public String getRankNumberDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(Target.TOP, getStyleSetting, "gold", "rank-numbers");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
|
||||
<p>Style: "none"</p>
|
||||
<p>Color: "dark_gray"</p> */
|
||||
<br>Style: "none"</br>
|
||||
<br>Color: "dark_gray"</br> */
|
||||
public String getDotsDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(Target.TOP, getStyleSetting, "dark_gray", "dots");
|
||||
}
|
||||
|
||||
/** Returns a String representing the Unit that should be used for a certain Unit.Type.
|
||||
/** Returns a String representing the {@link Unit} that should be used for a certain {@link Unit.Type}.
|
||||
If no String can be retrieved from the config, the supplied defaultValue will be returned.
|
||||
If the defaultValue is different for hoverText, an optional String defaultHoverValue can be supplied.
|
||||
@param isHoverText if true, the unit for hovering text is returned, otherwise the unit for plain text
|
||||
|
@ -9,7 +9,7 @@ import java.io.IOException;
|
||||
|
||||
import com.tchristofferson.configupdater.ConfigUpdater;
|
||||
|
||||
public class ConfigUpdateHandler {
|
||||
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) {
|
||||
|
@ -1,5 +1,10 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.enums;
|
||||
|
||||
/** Represents the debugging level that PlayerStats can use.
|
||||
<br></br>
|
||||
<br>1 = low (only show unexpected errors)</br>
|
||||
<br>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</br>
|
||||
<br>3 = high (log all tasks and time taken)</br>*/
|
||||
public enum DebugLevel {
|
||||
LOW, MEDIUM, HIGH
|
||||
}
|
@ -6,34 +6,63 @@ import net.kyori.adventure.text.format.TextColor;
|
||||
import java.util.Random;
|
||||
|
||||
/** This enum represents the colorscheme PlayerStats uses in its output messages.
|
||||
<p>GRAY: ChatColor Gray</p>
|
||||
<p>DARK_PURPLE: #6E3485 (used for default sub-titles, title-underscores and brackets)</p>
|
||||
<p>MEDIUM_BLUE: #55AAFF (used for all plain feedback and error messages)</p>
|
||||
<p>LIGHT_BLUE: #55C6FF (used for default hover-text)</p>
|
||||
<p>GOLD: ChatColor Gold (used for first parts of usage messages and for first parts of hover-text accent)</p>
|
||||
<p>MEDIUM_GOLD: #FFD52B (used for second parts of usage messages and for second parts of hover-text accent) </p>
|
||||
<p>LIGHT_GOLD: #FFEA40 (used for third parts of usage messages)</p>
|
||||
<p>LIGHT_YELLOW: #FFFF8E (used for last parts of explanation message)</p>
|
||||
*/
|
||||
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 {
|
||||
GRAY (NamedTextColor.GRAY), //#AAAAAA
|
||||
/** 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")),
|
||||
GOLD (NamedTextColor.GOLD), //#FFAA00
|
||||
|
||||
/** 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
|
||||
NAME_2 (TextColor.fromHexString("#4287F5")), //between blue and medium_blue
|
||||
NAME_3 (TextColor.fromHexString("#55AAFF")), //same as medium_blue
|
||||
NAME_4 (TextColor.fromHexString("#D65DB1")), //magenta-purple
|
||||
NAME_5 (TextColor.fromHexString("#EE8A19")), //dark orange
|
||||
NAME_6 (TextColor.fromHexString("#01C1A7")), //aqua-cyan-green-ish
|
||||
NAME_7 (TextColor.fromHexString("#46D858")); //light green
|
||||
|
||||
/** 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;
|
||||
@ -42,18 +71,23 @@ public enum PluginColor {
|
||||
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)) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.enums;
|
||||
|
||||
/** All standard messages PlayerStats can send as feedback.
|
||||
These are all the messages that can be sent without needing additional parameters.*/
|
||||
public enum StandardMessage {
|
||||
RELOADED_CONFIG,
|
||||
STILL_RELOADING,
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.enums;
|
||||
|
||||
/** This enum represents the targets PlayerStats accepts for a stat-lookup (Player, Server and Top).*/
|
||||
public enum Target {
|
||||
PLAYER, SERVER, TOP
|
||||
}
|
@ -3,6 +3,7 @@ package com.gmail.artemis.the.gr8.playerstats.enums;
|
||||
import org.bukkit.Statistic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** All the units PlayerStats can display statistics in, separated by Type.*/
|
||||
public enum Unit {
|
||||
NUMBER (Type.UNTYPED, "Times"),
|
||||
KM (Type.DISTANCE, "km"),
|
||||
@ -31,10 +32,15 @@ public enum Unit {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
/** Returns the Type this enum constant belongs to.*/
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/** For Type Time, Damage and Distance, this will return a smaller Unit than the current one
|
||||
(if there is a smaller Unit, that is, otherwise it will return itself).
|
||||
So for DAY, for example, it can return HOUR, MINUTE or SECOND.
|
||||
@param stepsSmaller how many steps smaller the returned Unit should be*/
|
||||
public Unit getSmallerUnit(int stepsSmaller) {
|
||||
switch (this) {
|
||||
case DAY -> {
|
||||
@ -93,6 +99,7 @@ public enum Unit {
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts the current Unit into seconds (and returns -1 if the current Unit is not of Type TIME)*/
|
||||
public double getSeconds() {
|
||||
return switch (this) {
|
||||
case DAY -> 86400;
|
||||
|
@ -5,6 +5,7 @@ import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
/** Listens for new Players that join, and reloads PlayerStats if someone joins that hasn't joined before.*/
|
||||
public class JoinListener implements Listener {
|
||||
|
||||
private static ThreadManager threadManager;
|
||||
|
@ -1,108 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.models;
|
||||
|
||||
import com.gmail.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.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class StatRequest {
|
||||
|
||||
private final CommandSender sender;
|
||||
private Statistic statistic;
|
||||
private String playerName;
|
||||
private Target selection;
|
||||
|
||||
private String subStatEntry;
|
||||
private EntityType entity;
|
||||
private Material block;
|
||||
private Material item;
|
||||
private boolean playerFlag;
|
||||
|
||||
//make a StatRequest for a given CommandSender with some default values
|
||||
public StatRequest(@NotNull CommandSender s) {
|
||||
sender = s;
|
||||
selection = Target.TOP;
|
||||
playerFlag = false;
|
||||
}
|
||||
|
||||
public @NotNull CommandSender getCommandSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public boolean isConsoleSender() {
|
||||
return sender instanceof ConsoleCommandSender;
|
||||
}
|
||||
|
||||
public void setStatistic(Statistic statistic) {
|
||||
this.statistic = statistic;
|
||||
}
|
||||
|
||||
/** Returns the set enum constant Statistic, or null if none was set. */
|
||||
public Statistic getStatistic() {
|
||||
return statistic;
|
||||
}
|
||||
|
||||
/** Sets the subStatEntry, and automatically tries to get the corresponding item/block/entity if there is a valid statType present.
|
||||
If the subStatEntry is set to null, any present item/block/entity is set to null again. */
|
||||
public void setSubStatEntry(String subStatEntry) {
|
||||
this.subStatEntry = subStatEntry;
|
||||
}
|
||||
|
||||
public String getSubStatEntry() {
|
||||
return subStatEntry;
|
||||
}
|
||||
|
||||
public void setPlayerName(String playerName) {
|
||||
this.playerName = playerName;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/** False by default, set to true if args[] contains "player". */
|
||||
public void setPlayerFlag(boolean playerFlag) {
|
||||
this.playerFlag = playerFlag;
|
||||
}
|
||||
|
||||
/** The "player" arg is a special case, because it could either be a valid subStatEntry, or indicate that the lookup action should target a specific player.
|
||||
This is why the playerFlag exists - if this is true, and playerName is null, subStatEntry should be set to "player". */
|
||||
public boolean playerFlag() {
|
||||
return playerFlag;
|
||||
}
|
||||
|
||||
public void setSelection(Target selection) {
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
public @NotNull Target getSelection() {
|
||||
return selection;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.models;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record StatResult(String playerName, TextComponent statResult, int ID, UUID uuid) {
|
||||
}
|
@ -5,12 +5,11 @@ import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Unit;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.ExampleMessage;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.HelpMessage;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.*;
|
||||
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
@ -26,36 +25,49 @@ import static net.kyori.adventure.text.Component.*;
|
||||
|
||||
/** Composes messages to send to a Player or Console. This class is responsible
|
||||
for constructing a final Component with the text content of the desired message.
|
||||
The component parts (with appropriate formatting) are supplied by a ComponentFactory.
|
||||
The component parts (with appropriate formatting) are supplied by a {@link ComponentFactory}.
|
||||
By default, this class works with the default ComponentFactory, but you can
|
||||
give it a different ComponentFactory upon creation.*/
|
||||
public class MessageWriter {
|
||||
public final class MessageBuilder {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private boolean useHoverText;
|
||||
private boolean isConsoleBuilder;
|
||||
|
||||
private final ComponentFactory componentFactory;
|
||||
private final LanguageKeyHandler languageKeyHandler;
|
||||
private final NumberFormatter formatter;
|
||||
|
||||
private MessageWriter(ConfigHandler config) {
|
||||
private MessageBuilder(ConfigHandler config) {
|
||||
this (config, new ComponentFactory(config));
|
||||
}
|
||||
|
||||
private MessageWriter(ConfigHandler configHandler, ComponentFactory factory) {
|
||||
private MessageBuilder(ConfigHandler configHandler, ComponentFactory factory) {
|
||||
config = configHandler;
|
||||
useHoverText = config.useHoverText();
|
||||
componentFactory = factory;
|
||||
|
||||
formatter = new NumberFormatter();
|
||||
languageKeyHandler = new LanguageKeyHandler();
|
||||
MyLogger.logMsg("MessageWriter created with factory: " + componentFactory.getClass().getSimpleName(), DebugLevel.MEDIUM);
|
||||
MyLogger.logMsg("MessageBuilder created with factory: " + componentFactory.getClass().getSimpleName(), DebugLevel.MEDIUM);
|
||||
}
|
||||
|
||||
public static MessageWriter defaultWriter(ConfigHandler config) {
|
||||
return new MessageWriter(config);
|
||||
public static MessageBuilder defaultBuilder(ConfigHandler config) {
|
||||
return new MessageBuilder(config);
|
||||
}
|
||||
|
||||
public static MessageWriter fromComponentFactory(ConfigHandler config, ComponentFactory factory) {
|
||||
return new MessageWriter(config, factory);
|
||||
public static MessageBuilder fromComponentFactory(ConfigHandler config, ComponentFactory factory) {
|
||||
return new MessageBuilder(config, 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;
|
||||
}
|
||||
|
||||
public TextComponent reloadedConfig() {
|
||||
@ -90,7 +102,7 @@ public class MessageWriter {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please add a valid " + EnumHandler.getSubStatTypeName(statType) + " to look up this statistic!"));
|
||||
"Please add a valid " + getSubStatTypeName(statType) + " to look up this statistic!"));
|
||||
}
|
||||
|
||||
public TextComponent missingPlayerName() {
|
||||
@ -106,7 +118,7 @@ public class MessageWriter {
|
||||
.append(componentFactory.messageAccent().content("\"" + subStatName + "\""))
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"is not a valid " + EnumHandler.getSubStatTypeName(statType) + "!"));
|
||||
"is not a valid " + getSubStatTypeName(statType) + "!"));
|
||||
}
|
||||
|
||||
public TextComponent requestAlreadyRunning() {
|
||||
@ -156,48 +168,63 @@ public class MessageWriter {
|
||||
return ExampleMessage.construct(componentFactory);
|
||||
}
|
||||
|
||||
public TextComponent helpMsg(boolean isConsoleSender) {
|
||||
public TextComponent helpMsg() {
|
||||
int listSize = config.getTopListMaxSize();
|
||||
if (!isConsoleSender && config.useHoverText()) {
|
||||
if (!isConsoleBuilder && useHoverText) {
|
||||
return HelpMessage.constructHoverMsg(componentFactory, listSize);
|
||||
} else {
|
||||
return HelpMessage.constructPlainMsg(componentFactory, listSize);
|
||||
}
|
||||
}
|
||||
|
||||
public BiFunction<UUID, CommandSender, TextComponent> formattedPlayerStatFunction(int stat, @NotNull StatRequest request) {
|
||||
/** Returns a BiFunction for a player statistic. This BiFunction will return a formattedValue,
|
||||
the shape of which is determined by the 2 parameters the BiFunction gets.
|
||||
<p>- Integer shareCode: if a shareCode is provided, a clickable "share" button will be added.
|
||||
<br>- CommandSender sender: if a sender is provided, a signature with "shared by sender-name" will be added.</br>
|
||||
<br>- If both parameters are null, the formattedValue will be returned as is.</br>*/
|
||||
public BiFunction<Integer, CommandSender, TextComponent> formattedPlayerStatFunction(int stat, @NotNull StatRequest statRequest) {
|
||||
TextComponent playerStat = Component.text()
|
||||
.append(componentFactory.playerName(request.getPlayerName(), Target.PLAYER)
|
||||
.append(componentFactory.playerName(statRequest.getPlayerName(), Target.PLAYER)
|
||||
.append(text(":"))
|
||||
.append(space()))
|
||||
.append(getStatNumberComponent(request.getStatistic(), stat, Target.PLAYER, request.isConsoleSender()))
|
||||
.append(getStatNumberComponent(statRequest, stat))
|
||||
.append(space())
|
||||
.append(getStatNameComponent(request))
|
||||
.append(getStatUnitComponent(request.getStatistic(), request.getSelection(), request.isConsoleSender())) //space is provided by statUnitComponent
|
||||
.append(getStatNameComponent(statRequest))
|
||||
.append(getStatUnitComponent(statRequest.getStatistic(), statRequest.getTarget())) //space is provided by statUnitComponent
|
||||
.build();
|
||||
|
||||
return getFormattingFunction(playerStat, Target.PLAYER);
|
||||
}
|
||||
|
||||
public BiFunction<UUID, CommandSender, TextComponent> formattedServerStatFunction(long stat, @NotNull StatRequest request) {
|
||||
/** Returns a BiFunction for a server statistic. This BiFunction will return a formattedValue,
|
||||
the shape of which is determined by the 2 parameters the BiFunction gets.
|
||||
<p>- Integer shareCode: if a shareCode is provided, a clickable "share" button will be added.
|
||||
<br>- CommandSender sender: if a sender is provided, a signature with "shared by sender-name" will be added.</br>
|
||||
<br>- If both parameters are null, the formattedValue will be returned as is.</br>*/
|
||||
public BiFunction<Integer, CommandSender, TextComponent> formattedServerStatFunction(long stat, @NotNull StatRequest statRequest) {
|
||||
TextComponent serverStat = text()
|
||||
.append(componentFactory.title(config.getServerTitle(), Target.SERVER))
|
||||
.append(space())
|
||||
.append(componentFactory.serverName(config.getServerName()))
|
||||
.append(space())
|
||||
.append(getStatNumberComponent(request.getStatistic(), stat, Target.SERVER, request.isConsoleSender()))
|
||||
.append(getStatNumberComponent(statRequest, stat))
|
||||
.append(space())
|
||||
.append(getStatNameComponent(request))
|
||||
.append(getStatUnitComponent(request.getStatistic(), request.getSelection(), request.isConsoleSender())) //space is provided by statUnit
|
||||
.append(getStatNameComponent(statRequest))
|
||||
.append(getStatUnitComponent(statRequest.getStatistic(), statRequest.getTarget())) //space is provided by statUnit
|
||||
.build();
|
||||
|
||||
return getFormattingFunction(serverStat, Target.SERVER);
|
||||
}
|
||||
|
||||
public BiFunction<UUID, CommandSender, TextComponent> formattedTopStatFunction(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull StatRequest request) {
|
||||
final TextComponent title = getTopStatsTitleComponent(request, topStats.size());
|
||||
final TextComponent shortTitle = getTopStatsTitleShortComponent(request, topStats.size());
|
||||
final TextComponent list = getTopStatListComponent(topStats, request);
|
||||
/** Returns a BiFunction for a top statistic. This BiFunction will return a formattedValue,
|
||||
the shape of which is determined by the 2 parameters the BiFunction gets.
|
||||
<p>- Integer shareCode: if a shareCode is provided, a clickable "share" button will be added.
|
||||
<br>- CommandSender sender: if a sender is provided, a signature with "shared by sender-name" will be added.</br>
|
||||
<br>- If both parameters are null, the formattedValue will be returned as is.</br>*/
|
||||
public BiFunction<Integer, CommandSender, TextComponent> formattedTopStatFunction(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull StatRequest statRequest) {
|
||||
final TextComponent title = getTopStatsTitleComponent(statRequest, topStats.size());
|
||||
final TextComponent shortTitle = getTopStatDescription(statRequest, topStats.size());
|
||||
final TextComponent list = getTopStatListComponent(topStats, statRequest);
|
||||
final boolean useEnters = config.useEnters(Target.TOP, false);
|
||||
final boolean useEntersForShared = config.useEnters(Target.TOP, true);
|
||||
|
||||
@ -241,9 +268,226 @@ public class MessageWriter {
|
||||
};
|
||||
}
|
||||
|
||||
private BiFunction<UUID, CommandSender, TextComponent> getFormattingFunction(@NotNull TextComponent statResult, Target selection) {
|
||||
boolean useEnters = config.useEnters(selection, false);
|
||||
boolean useEntersForShared = config.useEnters(selection, true);
|
||||
public TextComponent singleTopStatLine(int positionInTopList, String playerName, long statNumber, Statistic statistic) {
|
||||
TextComponent.Builder topStatLineBuilder = Component.text()
|
||||
.append(space())
|
||||
.append(componentFactory.rankNumber(positionInTopList))
|
||||
.append(space());
|
||||
|
||||
if (config.useDots()) {
|
||||
topStatLineBuilder.append(getPlayerNameWithDotsComponent(positionInTopList, playerName));
|
||||
} else {
|
||||
topStatLineBuilder.append(componentFactory.playerName(playerName + ":", Target.TOP));
|
||||
}
|
||||
|
||||
return topStatLineBuilder
|
||||
.append(space())
|
||||
.append(getStatNumberComponent(statistic, Target.TOP, statNumber))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Component getSharerNameComponent(CommandSender sender) {
|
||||
if (sender instanceof Player player) {
|
||||
Component senderName = EasterEggProvider.getPlayerName(player);
|
||||
if (senderName != null) {
|
||||
return senderName;
|
||||
}
|
||||
}
|
||||
return componentFactory.sharerName(sender.getName());
|
||||
}
|
||||
|
||||
private TextComponent getTopStatsTitleComponent(StatRequest statRequest, int statListSize) {
|
||||
return Component.text()
|
||||
.append(componentFactory.pluginPrefix()).append(space())
|
||||
.append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)).append(space())
|
||||
.append(componentFactory.titleNumber(statListSize)).append(space())
|
||||
.append(getStatNameComponent(statRequest)) //space is provided by statUnitComponent
|
||||
.append(getStatUnitComponent(statRequest.getStatistic(), statRequest.getTarget()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent getTopStatDescription(StatRequest statRequest, int statListSize) {
|
||||
return Component.text()
|
||||
.append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)).append(space())
|
||||
.append(componentFactory.titleNumber(statListSize)).append(space())
|
||||
.append(getStatNameComponent(statRequest)) //space is provided by statUnitComponent
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent getTopStatListComponent(LinkedHashMap<String, Integer> topStats, StatRequest statRequest) {
|
||||
TextComponent.Builder topList = Component.text();
|
||||
Set<String> playerNames = topStats.keySet();
|
||||
boolean useDots = config.useDots();
|
||||
|
||||
int count = 0;
|
||||
for (String playerName : playerNames) {
|
||||
topList.append(newline())
|
||||
.append(space())
|
||||
.append(componentFactory.rankNumber(++count))
|
||||
.append(space());
|
||||
if (useDots) {
|
||||
topList.append(getPlayerNameWithDotsComponent(count, playerName));
|
||||
}
|
||||
else {
|
||||
topList.append(componentFactory.playerName(playerName + ":", Target.TOP));
|
||||
}
|
||||
topList.append(space()).append(getStatNumberComponent(statRequest, topStats.get(playerName)));
|
||||
}
|
||||
return topList.build();
|
||||
}
|
||||
|
||||
private TextComponent getPlayerNameWithDotsComponent(int positionInTopList, String playerName) {
|
||||
int dots = FontUtils.getNumberOfDotsToAlign(positionInTopList + ". " + playerName, isConsoleBuilder, config.playerNameIsBold());
|
||||
|
||||
TextComponent.Builder nameWithDots = Component.text()
|
||||
.append(componentFactory.playerName(playerName, Target.TOP))
|
||||
.append(space());
|
||||
if (dots >= 1) {
|
||||
nameWithDots.append(componentFactory.dots().append(text(".".repeat(dots))));
|
||||
}
|
||||
return nameWithDots.build();
|
||||
}
|
||||
|
||||
/** Depending on the config settings, return either a TranslatableComponent representing
|
||||
the statName (and potential subStatName), or a TextComponent with capitalized English names.*/
|
||||
private TextComponent getStatNameComponent(StatRequest statRequest) {
|
||||
if (config.useTranslatableComponents()) {
|
||||
String statKey = languageKeyHandler.getStatKey(statRequest.getStatistic());
|
||||
String subStatKey = statRequest.getSubStatEntryName();
|
||||
if (subStatKey != null) {
|
||||
switch (statRequest.getStatistic().getType()) {
|
||||
case BLOCK -> subStatKey = languageKeyHandler.getBlockKey(statRequest.getBlock());
|
||||
case ENTITY -> subStatKey = languageKeyHandler.getEntityKey(statRequest.getEntity());
|
||||
case ITEM -> subStatKey = languageKeyHandler.getItemKey(statRequest.getItem());
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return componentFactory.statAndSubStatNameTranslatable(statKey, subStatKey, statRequest.getTarget());
|
||||
}
|
||||
else {
|
||||
return componentFactory.statAndSubStatName(
|
||||
StringUtils.prettify(statRequest.getStatistic().toString()),
|
||||
StringUtils.prettify(statRequest.getSubStatEntryName()),
|
||||
statRequest.getTarget());
|
||||
}
|
||||
}
|
||||
|
||||
private TextComponent getStatNumberComponent(StatRequest request, long statNumber) {
|
||||
return getStatNumberComponent(request.getStatistic(), request.getTarget(), statNumber);
|
||||
}
|
||||
|
||||
private TextComponent getStatNumberComponent(Statistic statistic, Target target, long statNumber) {
|
||||
Unit.Type statUnitType = Unit.getTypeFromStatistic(statistic);
|
||||
return switch (statUnitType) {
|
||||
case DISTANCE -> getDistanceNumberComponent(statNumber, target);
|
||||
case DAMAGE -> getDamageNumberComponent(statNumber, target);
|
||||
case TIME -> getTimeNumberComponent(statNumber, target);
|
||||
default -> getDefaultNumberComponent(statNumber, target);
|
||||
};
|
||||
}
|
||||
|
||||
private TextComponent getDistanceNumberComponent(long statNumber, Target target) {
|
||||
Unit statUnit = Unit.fromString(config.getDistanceUnit(false));
|
||||
String prettyNumber = formatter.formatDistanceNumber(statNumber, statUnit);
|
||||
if (!useHoverText) {
|
||||
return componentFactory.distanceNumber(prettyNumber, target);
|
||||
}
|
||||
|
||||
Unit hoverUnit = Unit.fromString(config.getDistanceUnit(true));
|
||||
String hoverNumber = formatter.formatDistanceNumber(statNumber, hoverUnit);
|
||||
if (config.useTranslatableComponents()) {
|
||||
String unitKey = languageKeyHandler.getUnitKey(hoverUnit);
|
||||
if (unitKey != null) {
|
||||
return componentFactory.distanceNumberWithTranslatableHoverText(prettyNumber, hoverNumber, unitKey, target);
|
||||
}
|
||||
}
|
||||
return componentFactory.distanceNumberWithHoverText(prettyNumber, hoverNumber, hoverUnit.getLabel(), target);
|
||||
}
|
||||
|
||||
private TextComponent getDamageNumberComponent(long statNumber, Target target) {
|
||||
Unit statUnit = Unit.fromString(config.getDamageUnit(false));
|
||||
String prettyNumber = formatter.formatDamageNumber(statNumber, statUnit);
|
||||
if (!useHoverText) {
|
||||
return componentFactory.damageNumber(prettyNumber, target);
|
||||
}
|
||||
|
||||
Unit hoverUnit = Unit.fromString(config.getDamageUnit(true));
|
||||
String prettyHoverNumber = formatter.formatDamageNumber(statNumber, hoverUnit);
|
||||
if (hoverUnit == Unit.HEART) {
|
||||
return componentFactory.damageNumberWithHeartUnitInHoverText(prettyNumber, prettyHoverNumber, target);
|
||||
}
|
||||
return componentFactory.damageNumberWithHoverText(prettyNumber, prettyHoverNumber, hoverUnit.getLabel(), target);
|
||||
}
|
||||
|
||||
private TextComponent getTimeNumberComponent(long statNumber, Target target) {
|
||||
ArrayList<Unit> unitRange = getTimeUnitRange(statNumber);
|
||||
if (unitRange.size() <= 1 || (useHoverText && unitRange.size() <= 3)) {
|
||||
MyLogger.logMsg(
|
||||
"There is something wrong with the time-units you specified, please check your config!",
|
||||
true);
|
||||
return componentFactory.statNumber("-", target);
|
||||
}
|
||||
else {
|
||||
String mainNumber = formatter.formatTimeNumber(statNumber, unitRange.get(0), unitRange.get(1));
|
||||
if (!useHoverText) {
|
||||
return componentFactory.statNumber(mainNumber, target);
|
||||
} else {
|
||||
String hoverNumber = formatter.formatTimeNumber(statNumber, unitRange.get(2), unitRange.get(3));
|
||||
MyLogger.logMsg("mainNumber: " + mainNumber + ", hoverNumber: " + hoverNumber, DebugLevel.HIGH);
|
||||
return componentFactory.statNumberWithHoverText(mainNumber, hoverNumber,
|
||||
null, null, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TextComponent getDefaultNumberComponent(long statNumber, Target target) {
|
||||
return componentFactory.statNumber(formatter.formatNumber(statNumber), target);
|
||||
}
|
||||
|
||||
private TextComponent getStatUnitComponent(Statistic statistic, Target target) {
|
||||
return switch (Unit.getTypeFromStatistic(statistic)) {
|
||||
case DAMAGE -> getDamageUnit(target);
|
||||
case DISTANCE -> getDistanceUnit(target);
|
||||
default -> Component.empty();
|
||||
};
|
||||
}
|
||||
|
||||
private TextComponent getDistanceUnit(Target target) {
|
||||
Unit statUnit = Unit.fromString(config.getDistanceUnit(false));
|
||||
if (config.useTranslatableComponents()) {
|
||||
String unitKey = languageKeyHandler.getUnitKey(statUnit);
|
||||
if (unitKey != null) {
|
||||
return Component.space()
|
||||
.append(componentFactory.statUnitTranslatable(unitKey, target));
|
||||
}
|
||||
}
|
||||
return Component.space()
|
||||
.append(componentFactory.statUnit(statUnit.getLabel(), target));
|
||||
}
|
||||
|
||||
private TextComponent getDamageUnit(Target target) {
|
||||
Unit statUnit = Unit.fromString(config.getDamageUnit(false));
|
||||
if (statUnit == Unit.HEART) {
|
||||
TextComponent heartUnit;
|
||||
if (isConsoleBuilder) {
|
||||
heartUnit = componentFactory.consoleHeart();
|
||||
} else if (useHoverText) {
|
||||
heartUnit = componentFactory.clientHeartWithHoverText();
|
||||
} else {
|
||||
heartUnit = componentFactory.clientHeart(false);
|
||||
}
|
||||
return Component.space()
|
||||
.append(heartUnit);
|
||||
}
|
||||
return Component.space()
|
||||
.append(componentFactory.statUnit(statUnit.getLabel(), target));
|
||||
}
|
||||
|
||||
|
||||
private BiFunction<Integer, CommandSender, TextComponent> getFormattingFunction(@NotNull TextComponent statResult, Target target) {
|
||||
boolean useEnters = config.useEnters(target, false);
|
||||
boolean useEntersForShared = config.useEnters(target, true);
|
||||
|
||||
return (shareCode, sender) -> {
|
||||
TextComponent.Builder statBuilder = text();
|
||||
@ -278,141 +522,6 @@ public class MessageWriter {
|
||||
};
|
||||
}
|
||||
|
||||
private Component getSharerNameComponent(CommandSender sender) {
|
||||
if (sender instanceof Player player) {
|
||||
Component senderName = EasterEggProvider.getPlayerName(player);
|
||||
if (senderName != null) {
|
||||
return senderName;
|
||||
}
|
||||
}
|
||||
return componentFactory.sharerName(sender.getName());
|
||||
}
|
||||
|
||||
private TextComponent getTopStatsTitleComponent(StatRequest request, int statListSize) {
|
||||
return Component.text()
|
||||
.append(componentFactory.pluginPrefix()).append(space())
|
||||
.append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)).append(space())
|
||||
.append(componentFactory.titleNumber(statListSize)).append(space())
|
||||
.append(getStatNameComponent(request)) //space is provided by statUnitComponent
|
||||
.append(getStatUnitComponent(request.getStatistic(), request.getSelection(), request.isConsoleSender()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent getTopStatsTitleShortComponent(StatRequest request, int statListSize) {
|
||||
return Component.text()
|
||||
.append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)).append(space())
|
||||
.append(componentFactory.titleNumber(statListSize)).append(space())
|
||||
.append(getStatNameComponent(request)) //space is provided by statUnitComponent
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent getTopStatListComponent(LinkedHashMap<String, Integer> topStats, StatRequest request) {
|
||||
TextComponent.Builder topList = Component.text();
|
||||
boolean useDots = config.useDots();
|
||||
boolean boldNames = config.playerNameIsBold();
|
||||
Set<String> playerNames = topStats.keySet();
|
||||
|
||||
int count = 0;
|
||||
for (String playerName : playerNames) {
|
||||
TextComponent.Builder playerNameBuilder = componentFactory.playerName(playerName, Target.TOP).toBuilder();
|
||||
topList.append(newline())
|
||||
.append(componentFactory.rankNumber(" " + ++count + "."))
|
||||
.append(space());
|
||||
if (useDots) {
|
||||
topList.append(playerNameBuilder)
|
||||
.append(space());
|
||||
int dots = FontUtils.getNumberOfDotsToAlign(count + ". " + playerName, request.isConsoleSender(), boldNames);
|
||||
if (dots >= 1) {
|
||||
topList.append(componentFactory.dots().append(text((".".repeat(dots)))));
|
||||
}
|
||||
}
|
||||
else {
|
||||
topList.append(playerNameBuilder.append(text(":")));
|
||||
}
|
||||
topList.append(space()).append(getStatNumberComponent(request.getStatistic(), topStats.get(playerName), Target.TOP, request.isConsoleSender()));
|
||||
}
|
||||
return topList.build();
|
||||
}
|
||||
|
||||
/** Depending on the config settings, return either a TranslatableComponent representing
|
||||
the statName (and potential subStatName), or a TextComponent with capitalized English names.*/
|
||||
private TextComponent getStatNameComponent(StatRequest request) {
|
||||
if (config.useTranslatableComponents()) {
|
||||
String statKey = languageKeyHandler.getStatKey(request.getStatistic());
|
||||
String subStatKey = request.getSubStatEntry();
|
||||
if (subStatKey != null) {
|
||||
switch (request.getStatistic().getType()) {
|
||||
case BLOCK -> subStatKey = languageKeyHandler.getBlockKey(request.getBlock());
|
||||
case ENTITY -> subStatKey = languageKeyHandler.getEntityKey(request.getEntity());
|
||||
case ITEM -> subStatKey = languageKeyHandler.getItemKey(request.getItem());
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return componentFactory.statAndSubStatNameTranslatable(statKey, subStatKey, request.getSelection());
|
||||
}
|
||||
else {
|
||||
return componentFactory.statAndSubStatName(
|
||||
StringUtils.prettify(request.getStatistic().toString()),
|
||||
StringUtils.prettify(request.getSubStatEntry()),
|
||||
request.getSelection());
|
||||
}
|
||||
}
|
||||
|
||||
private TextComponent getStatNumberComponent(Statistic statistic, long statNumber, Target selection, boolean isConsoleSender) {
|
||||
Unit.Type type = Unit.getTypeFromStatistic(statistic);
|
||||
Unit statUnit;
|
||||
switch (type) {
|
||||
case DISTANCE -> statUnit = Unit.fromString(config.getDistanceUnit(false));
|
||||
case DAMAGE -> statUnit = Unit.fromString(config.getDamageUnit(false));
|
||||
case TIME -> {
|
||||
return getTimeNumberComponent(statNumber, selection, getTimeUnitRange(statNumber));
|
||||
}
|
||||
default -> statUnit = Unit.NUMBER;
|
||||
}
|
||||
String prettyNumber = formatter.format(statNumber, statUnit);
|
||||
if (!config.useHoverText() || statUnit == Unit.NUMBER) {
|
||||
return componentFactory.statNumber(prettyNumber, selection);
|
||||
}
|
||||
Unit hoverUnit = type == Unit.Type.DISTANCE ? Unit.fromString(config.getDistanceUnit(true)) :
|
||||
Unit.fromString(config.getDamageUnit(true));
|
||||
String prettyHoverNumber = formatter.format(statNumber, hoverUnit);
|
||||
MyLogger.logMsg("mainNumber: " + prettyNumber + ", hoverNumber: " + prettyHoverNumber, DebugLevel.HIGH);
|
||||
|
||||
if (hoverUnit == Unit.HEART) {
|
||||
return componentFactory.damageNumberWithHoverText(
|
||||
prettyNumber, prettyHoverNumber,
|
||||
componentFactory.heart(isConsoleSender, true), selection);
|
||||
}
|
||||
if (config.useTranslatableComponents()) {
|
||||
String unitKey = languageKeyHandler.getUnitKey(hoverUnit);
|
||||
if (unitKey != null) {
|
||||
return componentFactory.statNumberWithHoverText(prettyNumber, prettyHoverNumber, null, unitKey, selection);
|
||||
}
|
||||
}
|
||||
return componentFactory.statNumberWithHoverText(prettyNumber, prettyHoverNumber, hoverUnit.getLabel(), null, selection);
|
||||
}
|
||||
|
||||
private TextComponent getTimeNumberComponent(long statNumber, Target selection, ArrayList<Unit> unitRange) {
|
||||
if (unitRange.size() <= 1 || (config.useHoverText() && unitRange.size() <= 3)) {
|
||||
MyLogger.logMsg(
|
||||
"There is something wrong with the time-units you specified, please check your config!",
|
||||
true);
|
||||
return componentFactory.statNumber("-", selection);
|
||||
}
|
||||
else {
|
||||
String mainNumber = formatter.format(statNumber, unitRange.get(0), unitRange.get(1));
|
||||
if (!config.useHoverText()) {
|
||||
return componentFactory.statNumber(mainNumber, selection);
|
||||
} else {
|
||||
String hoverNumber = formatter.format(statNumber, unitRange.get(2), unitRange.get(3));
|
||||
MyLogger.logMsg("mainNumber: " + mainNumber + ", hoverNumber: " + hoverNumber, DebugLevel.HIGH);
|
||||
return componentFactory.statNumberWithHoverText(mainNumber, hoverNumber,
|
||||
null, null, selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Get an ArrayList consisting of 2 or 4 timeUnits. The order of items is:
|
||||
<p>0. maxUnit</p>
|
||||
<p>1. minUnit</p>
|
||||
@ -429,7 +538,7 @@ public class MessageWriter {
|
||||
unitRange.add(bigUnit);
|
||||
unitRange.add(bigUnit.getSmallerUnit(config.getNumberOfExtraTimeUnits(false)));
|
||||
}
|
||||
if (config.useHoverText()) {
|
||||
if (useHoverText) {
|
||||
if (!config.autoDetectTimeUnit(true)) {
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(true)));
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(true, true)));
|
||||
@ -444,28 +553,15 @@ public class MessageWriter {
|
||||
return unitRange;
|
||||
}
|
||||
|
||||
private TextComponent getStatUnitComponent(Statistic statistic, Target selection, boolean isConsoleSender) {
|
||||
Unit statUnit;
|
||||
switch (Unit.getTypeFromStatistic(statistic)) {
|
||||
case DAMAGE -> statUnit = Unit.fromString(config.getDamageUnit(false));
|
||||
case DISTANCE -> statUnit = Unit.fromString(config.getDistanceUnit(false));
|
||||
default -> {
|
||||
return Component.empty();
|
||||
}
|
||||
/** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */
|
||||
public static String getSubStatTypeName(Statistic.Type statType) {
|
||||
String subStat = "sub-statistic";
|
||||
if (statType == null) return subStat;
|
||||
switch (statType) {
|
||||
case BLOCK -> subStat = "block";
|
||||
case ENTITY -> subStat = "entity";
|
||||
case ITEM -> subStat = "item";
|
||||
}
|
||||
if (config.useTranslatableComponents()) {
|
||||
String unitKey = languageKeyHandler.getUnitKey(statUnit);
|
||||
if (unitKey != null) {
|
||||
return Component.space()
|
||||
.append(componentFactory.statUnit(null, unitKey, selection));
|
||||
}
|
||||
}
|
||||
String statName = statUnit.getLabel();
|
||||
if (statUnit == Unit.HEART) { //console can do u2665, u2764 looks better in-game
|
||||
return Component.space()
|
||||
.append(componentFactory.heart(isConsoleSender, false));
|
||||
}
|
||||
return Component.space()
|
||||
.append(componentFactory.statUnit(statName, null, selection));
|
||||
return subStat;
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.ShareManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.StatFormatter;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.BukkitConsoleComponentFactory;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.PrideComponentFactory;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
@ -14,162 +15,210 @@ import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage.*;
|
||||
|
||||
public final class OutputManager {
|
||||
|
||||
private static volatile OutputManager instance;
|
||||
/** This class manages all PlayerStats output. It is the only place where messages are sent.
|
||||
It gets the messages from a {@link MessageBuilder}, which is different for a Console as for Players
|
||||
(mainly to deal with the lack of hover-text, and for Bukkit consoles to make up for the lack of hex-colors).*/
|
||||
public final class OutputManager implements StatFormatter {
|
||||
|
||||
private static BukkitAudiences adventure;
|
||||
private static ConfigHandler config;
|
||||
private static ShareManager shareManager;
|
||||
private static MessageWriter writer;
|
||||
private static MessageWriter consoleWriter;
|
||||
private static MessageBuilder messageBuilder;
|
||||
private static MessageBuilder consoleMessageBuilder;
|
||||
|
||||
private static EnumMap<StandardMessage, Function<MessageWriter, TextComponent>> standardMessages;
|
||||
private static EnumMap<StandardMessage, Function<MessageBuilder, TextComponent>> standardMessages;
|
||||
|
||||
private OutputManager(ConfigHandler config) {
|
||||
adventure = Main.adventure();
|
||||
shareManager = ShareManager.getInstance(config);
|
||||
public OutputManager(BukkitAudiences adventure, ConfigHandler config, ShareManager shareManager) {
|
||||
OutputManager.adventure = adventure;
|
||||
OutputManager.config = config;
|
||||
OutputManager.shareManager = shareManager;
|
||||
|
||||
getMessageWriters(config);
|
||||
getMessageBuilders();
|
||||
prepareFunctions();
|
||||
}
|
||||
|
||||
public static OutputManager getInstance(ConfigHandler config) {
|
||||
OutputManager outputManager = instance;
|
||||
if (outputManager != null) {
|
||||
return outputManager;
|
||||
}
|
||||
synchronized (OutputManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new OutputManager(config);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
public static void updateMessageBuilders() {
|
||||
getMessageBuilders();
|
||||
}
|
||||
|
||||
public void updateMessageWriters(ConfigHandler config) {
|
||||
getMessageWriters(config);
|
||||
@Override
|
||||
public TextComponent getPluginPrefix() {
|
||||
ComponentFactory factory = new ComponentFactory(config);
|
||||
return factory.pluginPrefix();
|
||||
}
|
||||
|
||||
public void sendFeedbackMsg(CommandSender sender, StandardMessage message) {
|
||||
@Override
|
||||
public TextComponent getRainbowPluginPrefix() {
|
||||
ComponentFactory prideFactory = new PrideComponentFactory(config);
|
||||
return prideFactory.pluginPrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getPluginPrefixAsTitle() {
|
||||
ComponentFactory factory = new ComponentFactory(config);
|
||||
return factory.pluginPrefixAsTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getRainbowPluginPrefixAsTitle() {
|
||||
ComponentFactory prideFactory = new PrideComponentFactory(config);
|
||||
return prideFactory.pluginPrefixAsTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatPlayerStat(@NotNull StatRequest statRequest, int playerStat) {
|
||||
BiFunction<Integer, CommandSender, TextComponent> playerStatFunction =
|
||||
getMessageBuilder(statRequest).formattedPlayerStatFunction(playerStat, statRequest);
|
||||
|
||||
return processFunction(statRequest.getCommandSender(), playerStatFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStat(@NotNull StatRequest statRequest, long serverStat) {
|
||||
BiFunction<Integer, CommandSender, TextComponent> serverStatFunction =
|
||||
getMessageBuilder(statRequest).formattedServerStatFunction(serverStat, statRequest);
|
||||
|
||||
return processFunction(statRequest.getCommandSender(), serverStatFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatSingleTopStatLine(int positionInTopList, String playerName, long statNumber, Statistic statistic) {
|
||||
return messageBuilder.singleTopStatLine(positionInTopList, playerName, statNumber, statistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatTopStat(@NotNull StatRequest statRequest, @NotNull LinkedHashMap<String, Integer> topStats) {
|
||||
BiFunction<Integer, CommandSender, TextComponent> topStatFunction =
|
||||
getMessageBuilder(statRequest).formattedTopStatFunction(topStats, statRequest);
|
||||
|
||||
return processFunction(statRequest.getCommandSender(), topStatFunction);
|
||||
}
|
||||
|
||||
public void sendFeedbackMsg(@NotNull CommandSender sender, StandardMessage message) {
|
||||
if (message != null) {
|
||||
adventure.sender(sender).sendMessage(standardMessages.get(message)
|
||||
.apply(getWriter(sender)));
|
||||
.apply(getMessageBuilder(sender)));
|
||||
}
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgWaitAMoment(CommandSender sender, boolean longWait) {
|
||||
adventure.sender(sender).sendMessage(getWriter(sender)
|
||||
public void sendFeedbackMsgWaitAMoment(@NotNull CommandSender sender, boolean longWait) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.waitAMoment(longWait));
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgMissingSubStat(CommandSender sender, Statistic.Type statType) {
|
||||
adventure.sender(sender).sendMessage(getWriter(sender)
|
||||
public void sendFeedbackMsgMissingSubStat(@NotNull CommandSender sender, Statistic.Type statType) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.missingSubStatName(statType));
|
||||
}
|
||||
|
||||
public void sendFeedbackMsgWrongSubStat(CommandSender sender, Statistic.Type statType, String subStatName) {
|
||||
public void sendFeedbackMsgWrongSubStat(@NotNull CommandSender sender, Statistic.Type statType, @Nullable String subStatName) {
|
||||
if (subStatName == null) {
|
||||
sendFeedbackMsgMissingSubStat(sender, statType);
|
||||
} else {
|
||||
adventure.sender(sender).sendMessage(getWriter(sender)
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.wrongSubStatType(statType, subStatName));
|
||||
}
|
||||
}
|
||||
|
||||
public void sendExamples(CommandSender sender) {
|
||||
adventure.sender(sender).sendMessage(getWriter(sender)
|
||||
public void sendExamples(@NotNull CommandSender sender) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.usageExamples());
|
||||
}
|
||||
|
||||
public void sendHelp(CommandSender sender) {
|
||||
adventure.sender(sender).sendMessage(getWriter(sender)
|
||||
.helpMsg(sender instanceof ConsoleCommandSender));
|
||||
public void sendHelp(@NotNull CommandSender sender) {
|
||||
adventure.sender(sender).sendMessage(getMessageBuilder(sender)
|
||||
.helpMsg());
|
||||
}
|
||||
|
||||
public void shareStatResults(@NotNull TextComponent statResult) {
|
||||
adventure.players().sendMessage(statResult);
|
||||
public void sendToAllPlayers(@NotNull TextComponent component) {
|
||||
adventure.players().sendMessage(component);
|
||||
}
|
||||
|
||||
public void sendPlayerStat(@NotNull StatRequest request, int playerStat) {
|
||||
CommandSender sender = request.getCommandSender();
|
||||
BiFunction<UUID, CommandSender, TextComponent> buildFunction =
|
||||
getWriter(sender).formattedPlayerStatFunction(playerStat, request);
|
||||
|
||||
processAndSend(sender, buildFunction);
|
||||
public void sendToCommandSender(@NotNull CommandSender sender, @NotNull TextComponent component) {
|
||||
adventure.sender(sender).sendMessage(component);
|
||||
}
|
||||
|
||||
public void sendServerStat(@NotNull StatRequest request, long serverStat) {
|
||||
CommandSender sender = request.getCommandSender();
|
||||
BiFunction<UUID, CommandSender, TextComponent> buildFunction =
|
||||
getWriter(sender).formattedServerStatFunction(serverStat, request);
|
||||
private TextComponent processFunction(CommandSender sender, @NotNull BiFunction<Integer, CommandSender, TextComponent> statResultFunction) {
|
||||
boolean saveOutput = !(sender instanceof ConsoleCommandSender) &&
|
||||
ShareManager.isEnabled() &&
|
||||
shareManager.senderHasPermission(sender);
|
||||
|
||||
processAndSend(sender, buildFunction);
|
||||
}
|
||||
|
||||
public void sendTopStat(@NotNull StatRequest request, LinkedHashMap<String, Integer> topStats) {
|
||||
CommandSender sender = request.getCommandSender();
|
||||
BiFunction<UUID, CommandSender, TextComponent> buildFunction =
|
||||
getWriter(sender).formattedTopStatFunction(topStats, request);
|
||||
|
||||
processAndSend(sender, buildFunction);
|
||||
}
|
||||
|
||||
private void processAndSend(CommandSender sender, BiFunction<UUID, CommandSender, TextComponent> buildFunction) {
|
||||
if (shareManager.isEnabled() && shareManager.senderHasPermission(sender)) {
|
||||
|
||||
UUID shareCode = shareManager.saveStatResult(sender.getName(), buildFunction.apply(null, sender));
|
||||
adventure.sender(sender).sendMessage(
|
||||
buildFunction.apply(shareCode, null));
|
||||
if (saveOutput) {
|
||||
int shareCode =
|
||||
shareManager.saveStatResult(sender.getName(), statResultFunction.apply(null, sender));
|
||||
return statResultFunction.apply(shareCode, null);
|
||||
}
|
||||
else {
|
||||
adventure.sender(sender).sendMessage(
|
||||
buildFunction.apply(null, null));
|
||||
return statResultFunction.apply(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageWriter getWriter(CommandSender sender) {
|
||||
return sender instanceof ConsoleCommandSender ? consoleWriter : writer;
|
||||
private MessageBuilder getMessageBuilder(CommandSender sender) {
|
||||
return sender instanceof ConsoleCommandSender ? consoleMessageBuilder : messageBuilder;
|
||||
}
|
||||
|
||||
private void getMessageWriters(ConfigHandler config) {
|
||||
boolean isBukkit = Bukkit.getName().equalsIgnoreCase("CraftBukkit");
|
||||
if (config.useRainbowMode() ||
|
||||
(config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE))) {
|
||||
writer = MessageWriter.fromComponentFactory(config, new PrideComponentFactory(config));
|
||||
}
|
||||
else {
|
||||
writer = MessageWriter.defaultWriter(config);
|
||||
}
|
||||
|
||||
if (!isBukkit) {
|
||||
consoleWriter = writer;
|
||||
private MessageBuilder getMessageBuilder(StatRequest statRequest) {
|
||||
if (statRequest.isAPIRequest() || !statRequest.isConsoleSender()) {
|
||||
return messageBuilder;
|
||||
} else {
|
||||
consoleWriter = MessageWriter.fromComponentFactory(config, new BukkitConsoleComponentFactory(config));
|
||||
return consoleMessageBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
private static void getMessageBuilders() {
|
||||
messageBuilder = getClientMessageBuilder();
|
||||
consoleMessageBuilder = getConsoleMessageBuilder();
|
||||
}
|
||||
|
||||
private static MessageBuilder getClientMessageBuilder() {
|
||||
if (useRainbowStyle()) {
|
||||
return MessageBuilder.fromComponentFactory(config, new PrideComponentFactory(config));
|
||||
}
|
||||
return MessageBuilder.defaultBuilder(config);
|
||||
}
|
||||
|
||||
private static MessageBuilder getConsoleMessageBuilder() {
|
||||
MessageBuilder consoleBuilder;
|
||||
if (isBukkit()) {
|
||||
consoleBuilder = MessageBuilder.fromComponentFactory(config, new BukkitConsoleComponentFactory(config));
|
||||
} else {
|
||||
consoleBuilder = getClientMessageBuilder();
|
||||
}
|
||||
consoleBuilder.setConsoleBuilder(true);
|
||||
consoleBuilder.toggleHoverUse(false);
|
||||
return consoleBuilder;
|
||||
}
|
||||
|
||||
private static boolean useRainbowStyle() {
|
||||
return config.useRainbowMode() || (config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE));
|
||||
}
|
||||
|
||||
private static boolean isBukkit() {
|
||||
return Bukkit.getName().equalsIgnoreCase("CraftBukkit");
|
||||
}
|
||||
|
||||
private void prepareFunctions() {
|
||||
standardMessages = new EnumMap<>(StandardMessage.class);
|
||||
|
||||
standardMessages.put(RELOADED_CONFIG, (MessageWriter::reloadedConfig));
|
||||
standardMessages.put(STILL_RELOADING, (MessageWriter::stillReloading));
|
||||
standardMessages.put(MISSING_STAT_NAME, (MessageWriter::missingStatName));
|
||||
standardMessages.put(MISSING_PLAYER_NAME, (MessageWriter::missingPlayerName));
|
||||
standardMessages.put(REQUEST_ALREADY_RUNNING, (MessageWriter::requestAlreadyRunning));
|
||||
standardMessages.put(STILL_ON_SHARE_COOLDOWN, (MessageWriter::stillOnShareCoolDown));
|
||||
standardMessages.put(RESULTS_ALREADY_SHARED, (MessageWriter::resultsAlreadyShared));
|
||||
standardMessages.put(STAT_RESULTS_TOO_OLD, (MessageWriter::statResultsTooOld));
|
||||
standardMessages.put(UNKNOWN_ERROR, (MessageWriter::unknownError));
|
||||
standardMessages.put(RELOADED_CONFIG, (MessageBuilder::reloadedConfig));
|
||||
standardMessages.put(STILL_RELOADING, (MessageBuilder::stillReloading));
|
||||
standardMessages.put(MISSING_STAT_NAME, (MessageBuilder::missingStatName));
|
||||
standardMessages.put(MISSING_PLAYER_NAME, (MessageBuilder::missingPlayerName));
|
||||
standardMessages.put(REQUEST_ALREADY_RUNNING, (MessageBuilder::requestAlreadyRunning));
|
||||
standardMessages.put(STILL_ON_SHARE_COOLDOWN, (MessageBuilder::stillOnShareCoolDown));
|
||||
standardMessages.put(RESULTS_ALREADY_SHARED, (MessageBuilder::resultsAlreadyShared));
|
||||
standardMessages.put(STAT_RESULTS_TOO_OLD, (MessageBuilder::statResultsTooOld));
|
||||
standardMessages.put(UNKNOWN_ERROR, (MessageBuilder::unknownError));
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
/** The {@link ComponentFactory} that is used to build messages for a Bukkit Console.
|
||||
Bukkit consoles don't support hex colors, unlike Paper consoles.*/
|
||||
public class BukkitConsoleComponentFactory extends ComponentFactory {
|
||||
|
||||
public BukkitConsoleComponentFactory(ConfigHandler config) {
|
||||
@ -22,6 +24,7 @@ public class BukkitConsoleComponentFactory extends ComponentFactory {
|
||||
PREFIX = PluginColor.GOLD.getConsoleColor();
|
||||
BRACKETS = PluginColor.GRAY.getConsoleColor();
|
||||
UNDERSCORE = PluginColor.DARK_PURPLE.getConsoleColor();
|
||||
HEARTS = PluginColor.RED.getConsoleColor();
|
||||
|
||||
MSG_MAIN = PluginColor.MEDIUM_BLUE.getConsoleColor();
|
||||
MSG_ACCENT = PluginColor.BLUE.getConsoleColor();
|
||||
|
@ -4,6 +4,8 @@ import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageBuilder;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
@ -18,15 +20,11 @@ import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
/** Creates Components with the desired formatting. This class can put Strings
|
||||
into formatted Components with TextColor and TextDecoration, or return empty Components
|
||||
or ComponentBuilders with the desired formatting.*/
|
||||
/** Creates Components with the desired formatting for the {@link MessageBuilder} to build messages with.
|
||||
This class can put Strings into formatted Components with TextColor
|
||||
and TextDecoration, or return empty Components with the desired formatting.*/
|
||||
public class ComponentFactory {
|
||||
|
||||
private static ConfigHandler config;
|
||||
@ -34,6 +32,7 @@ public class ComponentFactory {
|
||||
protected TextColor PREFIX; //gold
|
||||
protected TextColor BRACKETS; //gray
|
||||
protected TextColor UNDERSCORE; //dark_purple
|
||||
protected TextColor HEARTS; //red
|
||||
|
||||
protected TextColor MSG_MAIN; //medium_blue
|
||||
protected TextColor MSG_ACCENT; //blue
|
||||
@ -56,6 +55,7 @@ public class ComponentFactory {
|
||||
PREFIX = PluginColor.GOLD.getColor();
|
||||
BRACKETS = PluginColor.GRAY.getColor();
|
||||
UNDERSCORE = PluginColor.DARK_PURPLE.getColor();
|
||||
HEARTS = PluginColor.RED.getColor();
|
||||
|
||||
MSG_MAIN = PluginColor.MEDIUM_BLUE.getColor();
|
||||
MSG_ACCENT = PluginColor.BLUE.getColor();
|
||||
@ -109,10 +109,10 @@ public class ComponentFactory {
|
||||
return text().color(MSG_ACCENT).build();
|
||||
}
|
||||
|
||||
public TextComponent title(String content, Target selection) {
|
||||
public TextComponent title(String content, Target target) {
|
||||
return getComponent(content,
|
||||
getColorFromString(config.getTitleDecoration(selection, false)),
|
||||
getStyleFromString(config.getTitleDecoration(selection, true)));
|
||||
getColorFromString(config.getTitleDecoration(target, false)),
|
||||
getStyleFromString(config.getTitleDecoration(target, true)));
|
||||
}
|
||||
|
||||
public TextComponent titleNumber(int number) {
|
||||
@ -121,8 +121,8 @@ public class ComponentFactory {
|
||||
getStyleFromString(config.getTitleNumberDecoration(true)));
|
||||
}
|
||||
|
||||
public TextComponent rankNumber(String number) {
|
||||
return getComponent(number,
|
||||
public TextComponent rankNumber(int number) {
|
||||
return getComponent(number + ".",
|
||||
getColorFromString(config.getRankNumberDecoration(false)),
|
||||
getStyleFromString(config.getRankNumberDecoration(true)));
|
||||
}
|
||||
@ -141,10 +141,10 @@ public class ComponentFactory {
|
||||
.append(colon);
|
||||
}
|
||||
|
||||
public TextComponent playerName(String playerName, Target selection) {
|
||||
public TextComponent playerName(String playerName, Target target) {
|
||||
return getComponent(playerName,
|
||||
getColorFromString(config.getPlayerNameDecoration(selection, false)),
|
||||
getStyleFromString(config.getPlayerNameDecoration(selection, true)));
|
||||
getColorFromString(config.getPlayerNameDecoration(target, false)),
|
||||
getStyleFromString(config.getPlayerNameDecoration(target, true)));
|
||||
}
|
||||
|
||||
public TextComponent sharerName(String sharerName) {
|
||||
@ -153,8 +153,8 @@ public class ComponentFactory {
|
||||
getStyleFromString(config.getSharerNameDecoration(true)));
|
||||
}
|
||||
|
||||
public TextComponent shareButton(UUID shareCode) {
|
||||
return surroundingBrackets(
|
||||
public TextComponent shareButton(int shareCode) {
|
||||
return surroundWithBrackets(
|
||||
text("Share")
|
||||
.color(MSG_HOVER)
|
||||
.clickEvent(ClickEvent.runCommand("/statshare " + shareCode))
|
||||
@ -163,7 +163,7 @@ public class ComponentFactory {
|
||||
}
|
||||
|
||||
public TextComponent sharedByMessage(Component playerName) {
|
||||
return surroundingBrackets(
|
||||
return surroundWithBrackets(
|
||||
text().append(
|
||||
getComponent("Shared by",
|
||||
getColorFromString(config.getSharedByTextDecoration(false)),
|
||||
@ -174,7 +174,7 @@ public class ComponentFactory {
|
||||
}
|
||||
|
||||
public TextComponent statResultInHoverText(TextComponent statResult) {
|
||||
return surroundingBrackets(
|
||||
return surroundWithBrackets(
|
||||
text().append(text("Hover Here")
|
||||
.color(MSG_CLICKED)
|
||||
.decorate(TextDecoration.ITALIC)
|
||||
@ -184,31 +184,31 @@ public class ComponentFactory {
|
||||
|
||||
/** @param prettyStatName a statName with underscores removed and each word capitalized
|
||||
@param prettySubStatName if present, a subStatName with underscores removed and each word capitalized*/
|
||||
public TextComponent statAndSubStatName(String prettyStatName, @Nullable String prettySubStatName, Target selection) {
|
||||
public TextComponent statAndSubStatName(String prettyStatName, @Nullable String prettySubStatName, Target target) {
|
||||
TextComponent.Builder totalStatNameBuilder = getComponentBuilder(prettyStatName,
|
||||
getColorFromString(config.getStatNameDecoration(selection, false)),
|
||||
getStyleFromString(config.getStatNameDecoration(selection, true)));
|
||||
TextComponent subStat = subStatName(prettySubStatName, selection);
|
||||
getColorFromString(config.getStatNameDecoration(target, false)),
|
||||
getStyleFromString(config.getStatNameDecoration(target, true)));
|
||||
TextComponent subStat = subStatName(prettySubStatName, target);
|
||||
|
||||
if (!subStat.equals(Component.empty())) {
|
||||
totalStatNameBuilder
|
||||
.append(space().decorations(TextDecoration.NAMES.values(), false))
|
||||
.append(subStatName(prettySubStatName, selection));
|
||||
.append(subStatName(prettySubStatName, target));
|
||||
}
|
||||
return totalStatNameBuilder.build();
|
||||
}
|
||||
|
||||
/** Returns a TextComponent with TranslatableComponent as a child.*/
|
||||
public TextComponent statAndSubStatNameTranslatable(String statKey, String subStatKey, Target selection) {
|
||||
public TextComponent statAndSubStatNameTranslatable(String statKey, String subStatKey, Target target) {
|
||||
TextComponent.Builder totalStatNameBuilder = getComponentBuilder(null,
|
||||
getColorFromString(config.getStatNameDecoration(selection, false)),
|
||||
getStyleFromString(config.getStatNameDecoration(selection, true)));
|
||||
getColorFromString(config.getStatNameDecoration(target, false)),
|
||||
getStyleFromString(config.getStatNameDecoration(target, true)));
|
||||
|
||||
TextComponent subStat = subStatNameTranslatable(subStatKey, selection);
|
||||
if (statKey.equalsIgnoreCase("stat_type.minecraft.killed")) {
|
||||
TextComponent subStat = subStatNameTranslatable(subStatKey, target);
|
||||
if (LanguageKeyHandler.isKeyForKillEntity(statKey)) {
|
||||
return totalStatNameBuilder.append(killEntityBuilder(subStat)).build();
|
||||
}
|
||||
else if (statKey.equalsIgnoreCase("stat_type.minecraft.killed_by")) {
|
||||
else if (LanguageKeyHandler.isKeyForEntityKilledBy(statKey)) {
|
||||
return totalStatNameBuilder.append(entityKilledByBuilder(subStat)).build();
|
||||
}
|
||||
else {
|
||||
@ -222,63 +222,95 @@ public class ComponentFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public TextComponent statNumber(String prettyNumber, Target selection) {
|
||||
public TextComponent statNumber(String prettyNumber, Target target) {
|
||||
return getComponent(prettyNumber,
|
||||
getColorFromString(config.getStatNumberDecoration(selection, false)),
|
||||
getStyleFromString(config.getStatNumberDecoration(selection, true)));
|
||||
getColorFromString(config.getStatNumberDecoration(target, false)),
|
||||
getStyleFromString(config.getStatNumberDecoration(target, true)));
|
||||
}
|
||||
|
||||
public TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, Target selection) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, hoverUnitKey, null, selection);
|
||||
public TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, Target target) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, hoverUnitKey, null, target);
|
||||
}
|
||||
|
||||
public TextComponent damageNumberWithHoverText(String mainNumber, String hoverNumber, TextComponent heart, Target selection) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, null, null, heart, selection);
|
||||
public TextComponent damageNumber(String prettyNumber, Target target) {
|
||||
return statNumber(prettyNumber, target);
|
||||
}
|
||||
public TextComponent damageNumberWithHoverText(String mainNumber, String hoverNumber, String hoverUnitName, Target target) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, null, null, target);
|
||||
}
|
||||
|
||||
public TextComponent statUnit(String unitName, String unitKey, Target selection) {
|
||||
if (!(unitName == null && unitKey == null)) {
|
||||
TextComponent.Builder statUnitBuilder = getComponentBuilder(null,
|
||||
getColorFromString(config.getSubStatNameDecoration(selection, false)),
|
||||
getStyleFromString(config.getSubStatNameDecoration(selection, true)));
|
||||
if (unitKey != null) {
|
||||
statUnitBuilder.append(translatable()
|
||||
.key(unitKey));
|
||||
} else {
|
||||
statUnitBuilder.append(text(unitName));
|
||||
}
|
||||
return surroundingBrackets(statUnitBuilder.build());
|
||||
}
|
||||
else {
|
||||
return Component.empty();
|
||||
}
|
||||
public TextComponent damageNumberWithHeartUnitInHoverText(String mainNumber, String hoverNumber, Target target) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, null, null, clientHeart(true), target);
|
||||
}
|
||||
|
||||
public TextComponent heart(boolean isConsoleSender, boolean isHoverUnit) {
|
||||
TextColor heartColor = TextColor.fromHexString("#FF1313");
|
||||
char heart = isConsoleSender ? '\u2665' : '\u2764';
|
||||
if (isHoverUnit) {
|
||||
return Component.text(heart).color(heartColor);
|
||||
public TextComponent distanceNumber(String prettyNumber, Target target) {
|
||||
return statNumber(prettyNumber, target);
|
||||
}
|
||||
|
||||
public TextComponent distanceNumberWithHoverText(String mainNumber, String hoverNumber, String hoverUnitName, Target target) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, null, target);
|
||||
}
|
||||
|
||||
public TextComponent distanceNumberWithTranslatableHoverText(String mainNumber, String hoverNumber, String hoverUnitKey, Target target) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, null, hoverUnitKey, target);
|
||||
}
|
||||
|
||||
public TextComponent statUnit(String unitName, Target target) {
|
||||
TextComponent statUnit = getComponentBuilder(unitName,
|
||||
getColorFromString(config.getSubStatNameDecoration(target, false)),
|
||||
getStyleFromString(config.getSubStatNameDecoration(target, true)))
|
||||
.build();
|
||||
return surroundWithBrackets(statUnit);
|
||||
}
|
||||
|
||||
public TextComponent statUnitTranslatable(String unitKey, Target target) {
|
||||
TextComponent statUnit = getComponentBuilder(null,
|
||||
getColorFromString(config.getSubStatNameDecoration(target, false)),
|
||||
getStyleFromString(config.getSubStatNameDecoration(target, true)))
|
||||
.append(translatable()
|
||||
.key(unitKey))
|
||||
.build();
|
||||
return surroundWithBrackets(statUnit);
|
||||
}
|
||||
|
||||
public TextComponent clientHeart(boolean isDisplayedInHoverText) {
|
||||
TextComponent basicHeartComponent = basicHeartComponent('\u2764');
|
||||
if (isDisplayedInHoverText) {
|
||||
return basicHeartComponent;
|
||||
}
|
||||
TextComponent.Builder heartComponent = Component.text()
|
||||
.content(String.valueOf(heart))
|
||||
.color(heartColor);
|
||||
if (config.useHoverText()) {
|
||||
heartComponent.hoverEvent(HoverEvent.showText(
|
||||
text(Unit.HEART.getLabel())
|
||||
.color(MSG_HOVER_ACCENT)));
|
||||
}
|
||||
return surroundingBrackets(heartComponent.build());
|
||||
return surroundWithBrackets(basicHeartComponent);
|
||||
}
|
||||
|
||||
public TextComponent clientHeartWithHoverText() {
|
||||
TextComponent basicHeartComponent = basicHeartComponent('\u2764')
|
||||
.toBuilder()
|
||||
.hoverEvent(HoverEvent.showText(
|
||||
text(Unit.HEART.getLabel())
|
||||
.color(MSG_HOVER_ACCENT)))
|
||||
.build();
|
||||
return surroundWithBrackets(basicHeartComponent);
|
||||
}
|
||||
|
||||
public TextComponent consoleHeart() {
|
||||
return surroundWithBrackets(basicHeartComponent('\u2665'));
|
||||
}
|
||||
|
||||
//console can do u2665, u2764 looks better in-game
|
||||
private TextComponent basicHeartComponent(char heartChar) {
|
||||
return Component.text()
|
||||
.content(String.valueOf(heartChar))
|
||||
.color(HEARTS)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Returns a TextComponent for the subStatName, or an empty component.*/
|
||||
private TextComponent subStatName(@Nullable String prettySubStatName, Target selection) {
|
||||
private TextComponent subStatName(@Nullable String prettySubStatName, Target target) {
|
||||
if (prettySubStatName == null) {
|
||||
return Component.empty();
|
||||
} else {
|
||||
return getComponentBuilder(null,
|
||||
getColorFromString(config.getSubStatNameDecoration(selection, false)),
|
||||
getStyleFromString(config.getSubStatNameDecoration(selection, true)))
|
||||
getColorFromString(config.getSubStatNameDecoration(target, false)),
|
||||
getStyleFromString(config.getSubStatNameDecoration(target, true)))
|
||||
.append(text("("))
|
||||
.append(text(prettySubStatName))
|
||||
.append(text(")"))
|
||||
@ -287,11 +319,11 @@ public class ComponentFactory {
|
||||
}
|
||||
|
||||
/** Returns a TranslatableComponent for the subStatName, or an empty component.*/
|
||||
private TextComponent subStatNameTranslatable(String subStatKey, Target selection) {
|
||||
private TextComponent subStatNameTranslatable(String subStatKey, Target target) {
|
||||
if (subStatKey != null) {
|
||||
return getComponentBuilder(null,
|
||||
getColorFromString(config.getSubStatNameDecoration(selection, false)),
|
||||
getStyleFromString(config.getSubStatNameDecoration(selection, true)))
|
||||
getColorFromString(config.getSubStatNameDecoration(target, false)),
|
||||
getStyleFromString(config.getSubStatNameDecoration(target, true)))
|
||||
.append(text("("))
|
||||
.append(translatable()
|
||||
.key(subStatKey))
|
||||
@ -305,7 +337,7 @@ public class ComponentFactory {
|
||||
@return a TranslatableComponent Builder with the subStat Component as args.*/
|
||||
private TranslatableComponent.Builder killEntityBuilder(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key("commands.kill.success.single") //"Killed %s"
|
||||
.key(LanguageKeyHandler.getAlternativeKeyForKillEntity()) //"Killed %s"
|
||||
.args(subStat);
|
||||
}
|
||||
|
||||
@ -315,21 +347,21 @@ public class ComponentFactory {
|
||||
with book.byAuthor as key and the subStat Component as args.*/
|
||||
private TranslatableComponent.Builder entityKilledByBuilder(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key("stat.minecraft.deaths") //"Number of Deaths"
|
||||
.key(LanguageKeyHandler.getAlternativeKeyForEntityKilledBy()) //"Number of Deaths"
|
||||
.append(space())
|
||||
.append(translatable()
|
||||
.key("book.byAuthor") //"by %s"
|
||||
.key(LanguageKeyHandler.getAlternativeKeyForEntityKilledByArg()) //"by %s"
|
||||
.args(subStat));
|
||||
}
|
||||
|
||||
private TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, @Nullable TextComponent heart, Target selection) {
|
||||
TextColor baseColor = getColorFromString(config.getStatNumberDecoration(selection, false));
|
||||
TextDecoration style = getStyleFromString(config.getStatNumberDecoration(selection, true));
|
||||
private TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, @Nullable TextComponent heartComponent, Target target) {
|
||||
TextColor baseColor = getColorFromString(config.getStatNumberDecoration(target, false));
|
||||
TextDecoration style = getStyleFromString(config.getStatNumberDecoration(target, true));
|
||||
|
||||
TextComponent.Builder hoverText = getComponentBuilder(hoverNumber, getLighterColor(baseColor), style);
|
||||
if (heart != null) {
|
||||
if (heartComponent != null) {
|
||||
hoverText.append(space())
|
||||
.append(heart);
|
||||
.append(heartComponent);
|
||||
}
|
||||
else if (hoverUnitKey != null) {
|
||||
hoverText.append(space())
|
||||
@ -342,7 +374,7 @@ public class ComponentFactory {
|
||||
return getComponent(mainNumber, baseColor, style).hoverEvent(HoverEvent.showText(hoverText));
|
||||
}
|
||||
|
||||
private TextComponent surroundingBrackets(TextComponent component) {
|
||||
private TextComponent surroundWithBrackets(TextComponent component) {
|
||||
return getComponent(null, BRACKETS, null)
|
||||
.append(text("["))
|
||||
.append(component)
|
||||
|
@ -0,0 +1,76 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.components;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.StringUtils;
|
||||
import net.kyori.adventure.text.*;
|
||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
/** A utility class for handling Adventure's Components.
|
||||
Its main function is currently to help serialize Components into String.*/
|
||||
public final class ComponentUtils {
|
||||
|
||||
/** Returns a LegacyComponentSerializer that is capable of serializing TranslatableComponents,
|
||||
and capable of dealing with the custom language-keys I am using to improve the entity-related
|
||||
statistic names. This serializer will create a String with hex colors and styles, and it will
|
||||
turn language keys into prettified, readable English. */
|
||||
public static LegacyComponentSerializer getTranslatableComponentSerializer() {
|
||||
LegacyComponentSerializer serializer = getTextComponentSerializer();
|
||||
|
||||
ComponentFlattener flattener = ComponentFlattener.basic().toBuilder()
|
||||
.mapper(TranslatableComponent.class, trans -> {
|
||||
StringBuilder totalPrettyName = new StringBuilder();
|
||||
if (LanguageKeyHandler.isKeyForEntityKilledByArg(trans.key())) {
|
||||
return "";
|
||||
}
|
||||
else if (LanguageKeyHandler.isKeyForEntityKilledBy(trans.key()) ||
|
||||
LanguageKeyHandler.isKeyForKillEntity(trans.key())) {
|
||||
|
||||
TextComponent.Builder temp = Component.text();
|
||||
trans.iterator(ComponentIteratorType.DEPTH_FIRST, ComponentIteratorFlag.INCLUDE_TRANSLATABLE_COMPONENT_ARGUMENTS)
|
||||
.forEachRemaining(component -> {
|
||||
if (component instanceof TextComponent text) {
|
||||
if (!text.children().isEmpty()) {
|
||||
text.iterator(ComponentIteratorType.DEPTH_FIRST).forEachRemaining(component1 -> {
|
||||
if (component1 instanceof TextComponent text1 && text1.content().contains("(")) {
|
||||
temp.style(text.style()).color(text.color());
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (component instanceof TranslatableComponent translatable) {
|
||||
if (translatable.key().contains("entity")) {
|
||||
temp.append(Component.space())
|
||||
.append(Component.text("(")
|
||||
.append(Component.text(StringUtils.prettify(LanguageKeyHandler.convertToName(translatable.key()))))
|
||||
.append(Component.text(")")));
|
||||
totalPrettyName.append(
|
||||
serializer.serialize(temp.build()));
|
||||
} else if (!LanguageKeyHandler.isKeyForEntityKilledByArg(translatable.key())) {
|
||||
totalPrettyName.append(
|
||||
StringUtils.prettify(
|
||||
LanguageKeyHandler.convertToName(
|
||||
translatable.key())));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
return StringUtils.prettify(
|
||||
LanguageKeyHandler.convertToName(
|
||||
trans.key()));
|
||||
}
|
||||
return totalPrettyName.toString();
|
||||
})
|
||||
.build();
|
||||
|
||||
return serializer.toBuilder().flattener(flattener).build();
|
||||
}
|
||||
|
||||
private static LegacyComponentSerializer getTextComponentSerializer() {
|
||||
return LegacyComponentSerializer
|
||||
.builder()
|
||||
.hexColors()
|
||||
.useUnusualXRepeatedCharacterHexFormat()
|
||||
.build();
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import java.util.List;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
/** A fully constructed message with examples on how to use PlayerStats.*/
|
||||
public final class ExampleMessage implements TextComponent {
|
||||
|
||||
private final TextComponent exampleMessage;
|
||||
|
@ -13,6 +13,7 @@ import java.util.List;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
/** The help message that explains how to use PlayerStats.*/
|
||||
public final class HelpMessage implements TextComponent {
|
||||
|
||||
private final TextComponent helpMessage;
|
||||
|
@ -11,6 +11,7 @@ import java.util.Random;
|
||||
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
|
||||
/** A festive version of the {@link ComponentFactory}*/
|
||||
public class PrideComponentFactory extends ComponentFactory {
|
||||
|
||||
public PrideComponentFactory(ConfigHandler c) {
|
||||
@ -22,6 +23,7 @@ public class PrideComponentFactory extends ComponentFactory {
|
||||
PREFIX = PluginColor.GOLD.getColor();
|
||||
BRACKETS = PluginColor.GRAY.getColor();
|
||||
UNDERSCORE = PluginColor.DARK_PURPLE.getColor();
|
||||
HEARTS = PluginColor.RED.getColor();
|
||||
|
||||
MSG_MAIN = PluginColor.GRAY.getColor(); //difference 1
|
||||
MSG_ACCENT = PluginColor.LIGHT_GOLD.getColor(); //difference 2
|
||||
|
@ -1,12 +1,18 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.Tag;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**This class is just for fun, and adds some silly names for players on my server.
|
||||
/**This class is just for fun and adds some silly names for players on my server.
|
||||
It does not impact the rest of the plugin, and will only be used for the players mentioned in here.*/
|
||||
public final class EasterEggProvider {
|
||||
|
||||
@ -96,7 +102,7 @@ public final class EasterEggProvider {
|
||||
if (playerName == null) {
|
||||
return null;
|
||||
} else {
|
||||
return MiniMessage.miniMessage().deserialize(playerName);
|
||||
return MiniMessage.miniMessage().deserialize(playerName, papiTag(player));
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,4 +113,16 @@ public final class EasterEggProvider {
|
||||
private static boolean sillyNumberIsBetween(int sillyNumber, int lowerBound, int upperBound) {
|
||||
return sillyNumber >= lowerBound && sillyNumber <= upperBound;
|
||||
}
|
||||
|
||||
private static TagResolver papiTag(final @NotNull Player player) {
|
||||
return TagResolver.resolver("papi", (argumentQueue, context) -> {
|
||||
final String papiPlaceholder = argumentQueue.popOr("papi tag requires an argument").value();
|
||||
final String parsedPlaceholder = PlaceholderAPI.setPlaceholders(player, '%' + papiPlaceholder + '%');
|
||||
TextComponent componentPlaceholder = LegacyComponentSerializer.legacyAmpersand().deserialize(parsedPlaceholder);
|
||||
if (!componentPlaceholder.content().isEmpty()) {
|
||||
componentPlaceholder = componentPlaceholder.toBuilder().append(Component.space()).build();
|
||||
}
|
||||
return Tag.selfClosingInserting(componentPlaceholder);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
|
||||
import org.bukkit.map.MinecraftFont;
|
||||
|
||||
/** A small utility class that helps calculate how many dots to use to get the numbers of a top-statistic aligned. */
|
||||
public final class FontUtils {
|
||||
|
||||
private FontUtils() {
|
||||
|
@ -11,6 +11,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
/** A utility class that provides language keys to be put in a TranslatableComponent.*/
|
||||
public final class LanguageKeyHandler {
|
||||
|
||||
private static HashMap<Statistic, String> statNameKeys;
|
||||
@ -19,6 +20,74 @@ public final class LanguageKeyHandler {
|
||||
statNameKeys = generateStatNameKeys();
|
||||
}
|
||||
|
||||
/** Checks if a given Key is the language key "stat_type.minecraft.killed"
|
||||
or "commands.kill.success.single" (which results in "Killed %s").*/
|
||||
public static boolean isKeyForKillEntity(String statKey) {
|
||||
return statKey.equalsIgnoreCase("stat_type.minecraft.killed") ||
|
||||
statKey.equalsIgnoreCase("commands.kill.success.single");
|
||||
}
|
||||
|
||||
/** Returns a language key to replace the default Statistic.Kill_Entity key.
|
||||
@return the key "commands.kill.success.single", which results in "Killed %s" */
|
||||
public static String getAlternativeKeyForKillEntity() {
|
||||
return "commands.kill.success.single";
|
||||
}
|
||||
|
||||
/** Checks if a given Key is the language key "stat_type.minecraft.killed_by"
|
||||
or "stat.minecraft.deaths" (which results in "Number of Deaths").*/
|
||||
public static boolean isKeyForEntityKilledBy(String statKey) {
|
||||
return statKey.equalsIgnoreCase("stat_type.minecraft.killed_by") ||
|
||||
statKey.equalsIgnoreCase("stat.minecraft.deaths");
|
||||
}
|
||||
|
||||
/** Returns a language key to replace the default Statistic.Entity_Killed_By key.
|
||||
@return the key "stat.minecraft.deaths", which results in "Number of Deaths"
|
||||
(meant to be followed by {@link #getAlternativeKeyForEntityKilledByArg()})*/
|
||||
public static String getAlternativeKeyForEntityKilledBy() {
|
||||
return "stat.minecraft.deaths";
|
||||
}
|
||||
|
||||
/** Checks if a given Key is the language key "book.byAuthor"
|
||||
(which results in "by %s"). */
|
||||
public static boolean isKeyForEntityKilledByArg(String statKey) {
|
||||
return statKey.equalsIgnoreCase("book.byAuthor");
|
||||
}
|
||||
|
||||
/** Returns a language key to complete the alternative key for Statistic.Entity_Killed_By.
|
||||
@return the key "book.byAuthor", which results in "by %". If used after
|
||||
{@link #getAlternativeKeyForEntityKilledBy()}, you will get "Number of Deaths" "by %s"*/
|
||||
public static String getAlternativeKeyForEntityKilledByArg() {
|
||||
return "book.byAuthor";
|
||||
}
|
||||
|
||||
public static String convertToName(String key) {
|
||||
if (key.equalsIgnoreCase("soundCategory.block")) {
|
||||
return Unit.BLOCK.getLabel();
|
||||
} else if (isKeyForKillEntity(key)) {
|
||||
return "times_killed";
|
||||
} else if (isKeyForEntityKilledBy(key)) {
|
||||
return "number_of_times_killed_by";
|
||||
} else if (isKeyForEntityKilledByArg(key)) { //this one returns nothing, because the previous one returns the full text
|
||||
return "";
|
||||
}
|
||||
String toReplace = "";
|
||||
if (key.contains("stat")) {
|
||||
if (key.contains("type")) {
|
||||
toReplace = "stat_type";
|
||||
} else {
|
||||
toReplace = "stat";
|
||||
}
|
||||
} else if (key.contains("entity")) { //for the two entity-related ones, put brackets around it to make up for the multiple-keys/args-serializer issues
|
||||
toReplace = "entity";
|
||||
} else if (key.contains("block")) {
|
||||
toReplace = "block";
|
||||
} else if (key.contains("item")) {
|
||||
toReplace = "item";
|
||||
}
|
||||
toReplace = toReplace + ".minecraft.";
|
||||
return key.replace(toReplace, "");
|
||||
}
|
||||
|
||||
public String getStatKey(@NotNull Statistic statistic) {
|
||||
if (statistic.getType() == Statistic.Type.UNTYPED) {
|
||||
return "stat.minecraft." + statNameKeys.get(statistic);
|
||||
@ -33,7 +102,7 @@ public final class LanguageKeyHandler {
|
||||
public @Nullable String getEntityKey(EntityType entity) {
|
||||
if (entity == null || entity == EntityType.UNKNOWN) return null;
|
||||
else {
|
||||
return "entity.minecraft." + entity.getKey().getKey();
|
||||
return "entity.minecraft." + entity.getKey().getKey();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,9 @@ import com.gmail.artemis.the.gr8.playerstats.enums.Unit;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/** A utility class that formats statistic numbers into something more readable.
|
||||
It transforms {@link Unit} Type TIME, DAMAGE, and DISTANCE into a more Minecraft-appropriate Unit,
|
||||
and adds commas to break up large numbers.*/
|
||||
public final class NumberFormatter {
|
||||
|
||||
private final DecimalFormat format;
|
||||
@ -17,25 +20,13 @@ public final class NumberFormatter {
|
||||
/** Turns the input number into a more readable format depending on its type
|
||||
(number-of-times, time-, damage- or distance-based) according to the
|
||||
corresponding config settings, and adds commas in groups of 3.*/
|
||||
public String format(long number, Unit statUnit) {
|
||||
return format(number, statUnit, null);
|
||||
}
|
||||
|
||||
public String format(long number, Unit statUnit, Unit smallTimeUnit) {
|
||||
if (smallTimeUnit == null) {
|
||||
return switch (statUnit.getType()) {
|
||||
case DISTANCE -> formatDistance(number, statUnit);
|
||||
case DAMAGE -> formatDamage(number, statUnit);
|
||||
default -> format.format(number);
|
||||
};
|
||||
} else {
|
||||
return formatTime(number, statUnit, smallTimeUnit);
|
||||
}
|
||||
public String formatNumber(long number) {
|
||||
return format.format(number);
|
||||
}
|
||||
|
||||
/** The unit of damage-based statistics is half a heart by default.
|
||||
This method turns the number into hearts. */
|
||||
private String formatDamage(long number, Unit statUnit) { //7 statistics
|
||||
public String formatDamageNumber(long number, Unit statUnit) { //7 statistics
|
||||
if (statUnit == Unit.HEART) {
|
||||
return format.format(Math.round(number / 2.0));
|
||||
} else {
|
||||
@ -45,7 +36,7 @@ public final class NumberFormatter {
|
||||
|
||||
/** The unit of distance-based statistics is cm by default. This method turns it into blocks by default,
|
||||
and turns it into km or leaves it as cm otherwise, depending on the config settings. */
|
||||
private String formatDistance(long number, Unit statUnit) { //15 statistics
|
||||
public String formatDistanceNumber(long number, Unit statUnit) { //15 statistics
|
||||
switch (statUnit) {
|
||||
case CM -> {
|
||||
return format.format(number);
|
||||
@ -63,7 +54,7 @@ public final class NumberFormatter {
|
||||
}
|
||||
|
||||
/** The unit of time-based statistics is ticks by default.*/
|
||||
private String formatTime(long number, Unit bigUnit, Unit smallUnit) { //5 statistics
|
||||
public String formatTimeNumber(long number, Unit bigUnit, Unit smallUnit) { //5 statistics
|
||||
if (number == 0) {
|
||||
return "-";
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
|
||||
/** A small utility class that helps make enum constant names prettier for output in stat-messages.*/
|
||||
public final class StringUtils {
|
||||
|
||||
private StringUtils() {
|
||||
|
@ -9,7 +9,8 @@ import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RecursiveAction;
|
||||
|
||||
public final class ReloadAction extends RecursiveAction {
|
||||
/** The action that is executed when a reload-command is triggered. */
|
||||
final class ReloadAction extends RecursiveAction {
|
||||
|
||||
private static int threshold;
|
||||
|
||||
|
@ -7,6 +7,7 @@ import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import org.bukkit.Bukkit;
|
||||
@ -20,26 +21,20 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class ReloadThread extends Thread {
|
||||
/** The Thread that is in charge of reloading PlayerStats. */
|
||||
public final class ReloadThread extends Thread {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static OutputManager messageSender;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
private static ShareManager shareManager;
|
||||
private static OutputManager outputManager;
|
||||
|
||||
private final int reloadThreadID;
|
||||
private final StatThread statThread;
|
||||
|
||||
private final CommandSender sender;
|
||||
|
||||
|
||||
public ReloadThread(ConfigHandler c, OutputManager m, OfflinePlayerHandler o, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
|
||||
public ReloadThread(ConfigHandler c, OutputManager m, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
|
||||
config = c;
|
||||
messageSender = m;
|
||||
offlinePlayerHandler = o;
|
||||
|
||||
shareManager = ShareManager.getInstance(c);
|
||||
outputManager = m;
|
||||
|
||||
reloadThreadID = ID;
|
||||
statThread = s;
|
||||
@ -49,6 +44,11 @@ public class ReloadThread extends Thread {
|
||||
MyLogger.threadCreated(this.getName());
|
||||
}
|
||||
|
||||
/** This method will perform a series of tasks. If a {@link StatThread} is still running,
|
||||
it will join the statThread and wait for it to finish. Then, it will reload the config,
|
||||
update the offlinePlayerList in the {@link OfflinePlayerHandler}, update the {@link DebugLevel},
|
||||
update the share-settings in {@link ShareManager} and topListSize-settings in {@link StatManager},
|
||||
and update the MessageBuilders in the {@link OutputManager}.*/
|
||||
@Override
|
||||
public void run() {
|
||||
long time = System.currentTimeMillis();
|
||||
@ -69,21 +69,21 @@ public class ReloadThread extends Thread {
|
||||
reloadEverything();
|
||||
|
||||
if (sender != null) {
|
||||
messageSender.sendFeedbackMsg(sender, StandardMessage.RELOADED_CONFIG);
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.RELOADED_CONFIG);
|
||||
}
|
||||
}
|
||||
else { //during first start-up
|
||||
MyLogger.setDebugLevel(config.getDebugLevel());
|
||||
offlinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadEverything() {
|
||||
MyLogger.setDebugLevel(config.getDebugLevel());
|
||||
messageSender.updateMessageWriters(config);
|
||||
offlinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
|
||||
shareManager.updateSettings(config);
|
||||
OutputManager.updateMessageBuilders();
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
|
||||
ShareManager.updateSettings(config);
|
||||
}
|
||||
|
||||
private ConcurrentHashMap<String, UUID> loadOfflinePlayers() {
|
||||
@ -125,7 +125,7 @@ public class ReloadThread extends Thread {
|
||||
MyLogger.actionFinished(1);
|
||||
|
||||
MyLogger.logTimeTaken("ReloadThread",
|
||||
("loaded " + playerMap.size() + " offline players"), time);
|
||||
("loaded " + playerMap.size() + " offline players"), time, DebugLevel.LOW);
|
||||
return playerMap;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -9,17 +9,17 @@ import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RecursiveAction;
|
||||
import java.util.concurrent.RecursiveTask;
|
||||
|
||||
|
||||
public final class StatAction extends RecursiveAction {
|
||||
/** The action that is executed when a stat-command is triggered. */
|
||||
final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>> {
|
||||
|
||||
private static int threshold;
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private final ImmutableList<String> playerNames;
|
||||
private final StatRequest request;
|
||||
private final ConcurrentHashMap<String, Integer> playerStats;
|
||||
private final StatRequest statRequest;
|
||||
private final ConcurrentHashMap<String, Integer> allStats;
|
||||
|
||||
/**
|
||||
* Gets the statistic numbers for all players whose name is on the list, puts them in a ConcurrentHashMap
|
||||
@ -27,34 +27,36 @@ public final class StatAction extends RecursiveAction {
|
||||
* @param offlinePlayerHandler the OfflinePlayerHandler to convert playerNames into Players
|
||||
* @param playerNames ImmutableList of playerNames for players that should be included in stat calculations
|
||||
* @param statRequest a validated statRequest
|
||||
* @param playerStats the ConcurrentHashMap to put the results on
|
||||
* @param allStats the ConcurrentHashMap to put the results on
|
||||
*/
|
||||
public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList<String> playerNames, StatRequest statRequest, ConcurrentHashMap<String, Integer> playerStats) {
|
||||
public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList<String> playerNames, StatRequest statRequest, ConcurrentHashMap<String, Integer> allStats) {
|
||||
threshold = ThreadManager.getTaskThreshold();
|
||||
|
||||
this.offlinePlayerHandler = offlinePlayerHandler;
|
||||
this.playerNames = playerNames;
|
||||
this.request = statRequest;
|
||||
this.playerStats = playerStats;
|
||||
this.statRequest = statRequest;
|
||||
this.allStats = allStats;
|
||||
|
||||
MyLogger.subActionCreated(Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void compute() {
|
||||
protected ConcurrentHashMap<String, Integer> compute() {
|
||||
if (playerNames.size() < threshold) {
|
||||
getStatsDirectly();
|
||||
return getStatsDirectly();
|
||||
}
|
||||
else {
|
||||
final StatAction subTask1 = new StatAction(offlinePlayerHandler, playerNames.subList(0, playerNames.size()/2), request, playerStats);
|
||||
final StatAction subTask2 = new StatAction(offlinePlayerHandler, playerNames.subList(playerNames.size()/2, playerNames.size()), request, playerStats);
|
||||
final StatAction subTask1 = new StatAction(offlinePlayerHandler, playerNames.subList(0, playerNames.size()/2), statRequest, allStats);
|
||||
final StatAction subTask2 = new StatAction(offlinePlayerHandler, playerNames.subList(playerNames.size()/2, playerNames.size()), statRequest, allStats);
|
||||
|
||||
//queue and compute all subtasks in the right order
|
||||
invokeAll(subTask1, subTask2);
|
||||
subTask1.fork();
|
||||
subTask2.compute();
|
||||
return subTask1.join();
|
||||
}
|
||||
}
|
||||
|
||||
private void getStatsDirectly() {
|
||||
private ConcurrentHashMap<String, Integer> getStatsDirectly() {
|
||||
Iterator<String> iterator = playerNames.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
do {
|
||||
@ -63,17 +65,18 @@ public final class StatAction extends RecursiveAction {
|
||||
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(playerName);
|
||||
if (player != null) {
|
||||
int statistic = 0;
|
||||
switch (request.getStatistic().getType()) {
|
||||
case UNTYPED -> statistic = player.getStatistic(request.getStatistic());
|
||||
case ENTITY -> statistic = player.getStatistic(request.getStatistic(), request.getEntity());
|
||||
case BLOCK -> statistic = player.getStatistic(request.getStatistic(), request.getBlock());
|
||||
case ITEM -> statistic = player.getStatistic(request.getStatistic(), request.getItem());
|
||||
switch (statRequest.getStatistic().getType()) {
|
||||
case UNTYPED -> statistic = player.getStatistic(statRequest.getStatistic());
|
||||
case ENTITY -> statistic = player.getStatistic(statRequest.getStatistic(), statRequest.getEntity());
|
||||
case BLOCK -> statistic = player.getStatistic(statRequest.getStatistic(), statRequest.getBlock());
|
||||
case ITEM -> statistic = player.getStatistic(statRequest.getStatistic(), statRequest.getItem());
|
||||
}
|
||||
if (statistic > 0) {
|
||||
playerStats.put(playerName, statistic);
|
||||
allStats.put(playerName, statistic);
|
||||
}
|
||||
}
|
||||
} while (iterator.hasNext());
|
||||
}
|
||||
return allStats;
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.StatCalculator;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class StatManager implements StatCalculator {
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
public StatManager(OfflinePlayerHandler offlinePlayerHandler) {
|
||||
this.offlinePlayerHandler = offlinePlayerHandler;
|
||||
}
|
||||
|
||||
/** Gets the statistic data for an individual player. If somehow the player
|
||||
cannot be found, this returns 0.*/
|
||||
@Override
|
||||
public int getPlayerStat(StatRequest statRequest) {
|
||||
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(statRequest.getPlayerName());
|
||||
if (player != null) {
|
||||
return switch (statRequest.getStatistic().getType()) {
|
||||
case UNTYPED -> player.getStatistic(statRequest.getStatistic());
|
||||
case ENTITY -> player.getStatistic(statRequest.getStatistic(), statRequest.getEntity());
|
||||
case BLOCK -> player.getStatistic(statRequest.getStatistic(), statRequest.getBlock());
|
||||
case ITEM -> player.getStatistic(statRequest.getStatistic(), statRequest.getItem());
|
||||
};
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<String, Integer> getTopStats(StatRequest statRequest) {
|
||||
return getAllStatsAsync(statRequest).entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||
.limit(statRequest.getTopListSize())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getServerStat(StatRequest statRequest) {
|
||||
List<Integer> numbers = getAllStatsAsync(statRequest)
|
||||
.values()
|
||||
.parallelStream()
|
||||
.toList();
|
||||
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
|
||||
}
|
||||
|
||||
/** Invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players
|
||||
that are stored in the {@link OfflinePlayerHandler}) */
|
||||
private @NotNull ConcurrentHashMap<String, Integer> getAllStatsAsync(StatRequest statRequest) {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
ForkJoinPool commonPool = ForkJoinPool.commonPool();
|
||||
ConcurrentHashMap<String, Integer> allStats;
|
||||
|
||||
try {
|
||||
allStats = commonPool.invoke(getStatTask(statRequest));
|
||||
} catch (ConcurrentModificationException e) {
|
||||
MyLogger.logMsg("The statRequest could not be executed due to a ConcurrentModificationException. " +
|
||||
"This likely happened because Bukkit hasn't fully initialized all player-data yet. " +
|
||||
"Try again and it should be fine!", true);
|
||||
throw new ConcurrentModificationException(e.toString());
|
||||
}
|
||||
|
||||
MyLogger.actionFinished(2);
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
MyLogger.logTimeTaken("StatThread", "calculated all stats", time, DebugLevel.MEDIUM);
|
||||
|
||||
return allStats;
|
||||
}
|
||||
|
||||
private StatAction getStatTask(StatRequest statRequest) {
|
||||
int size = offlinePlayerHandler.getOfflinePlayerCount() != 0 ? offlinePlayerHandler.getOfflinePlayerCount() : 16;
|
||||
ConcurrentHashMap<String, Integer> allStats = new ConcurrentHashMap<>(size);
|
||||
ImmutableList<String> playerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
|
||||
|
||||
StatAction task = new StatAction(offlinePlayerHandler, playerNames, statRequest, allStats);
|
||||
MyLogger.actionCreated(playerNames.size());
|
||||
|
||||
return task;
|
||||
}
|
||||
}
|
@ -2,41 +2,34 @@ package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class StatThread extends Thread {
|
||||
/** The Thread that is in charge of getting and calculating statistics.*/
|
||||
public final class StatThread extends Thread {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private final OutputManager outputManager;
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static OutputManager outputManager;
|
||||
private static StatManager statManager;
|
||||
|
||||
private final ReloadThread reloadThread;
|
||||
private final StatRequest request;
|
||||
private final StatRequest statRequest;
|
||||
|
||||
public StatThread(ConfigHandler c, OutputManager m, OfflinePlayerHandler o, int ID, StatRequest s, @Nullable ReloadThread r) {
|
||||
config = c;
|
||||
public StatThread(OutputManager m, StatManager t, int ID, StatRequest s, @Nullable ReloadThread r) {
|
||||
outputManager = m;
|
||||
offlinePlayerHandler = o;
|
||||
statManager = t;
|
||||
|
||||
reloadThread = r;
|
||||
request = s;
|
||||
statRequest = s;
|
||||
|
||||
this.setName("StatThread-" + request.getCommandSender().getName() + "-" + ID);
|
||||
this.setName("StatThread-" + statRequest.getCommandSender().getName() + "-" + ID);
|
||||
MyLogger.threadCreated(this.getName());
|
||||
}
|
||||
|
||||
@ -44,13 +37,13 @@ public class StatThread extends Thread {
|
||||
public void run() throws IllegalStateException, NullPointerException {
|
||||
MyLogger.threadStart(this.getName());
|
||||
|
||||
if (request == null) {
|
||||
throw new NullPointerException("No statistic request was found!");
|
||||
if (statRequest == null) {
|
||||
throw new NullPointerException("No statistic statRequest was found!");
|
||||
}
|
||||
if (reloadThread != null && reloadThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.waitingForOtherThread(this.getName(), reloadThread.getName());
|
||||
outputManager.sendFeedbackMsg(request.getCommandSender(), StandardMessage.STILL_RELOADING);
|
||||
outputManager.sendFeedbackMsg(statRequest.getCommandSender(), StandardMessage.STILL_RELOADING);
|
||||
reloadThread.join();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
@ -61,74 +54,29 @@ public class StatThread extends Thread {
|
||||
|
||||
long lastCalc = ThreadManager.getLastRecordedCalcTime();
|
||||
if (lastCalc > 2000) {
|
||||
outputManager.sendFeedbackMsgWaitAMoment(request.getCommandSender(), lastCalc > 20000);
|
||||
outputManager.sendFeedbackMsgWaitAMoment(statRequest.getCommandSender(), lastCalc > 20000);
|
||||
}
|
||||
|
||||
Target selection = request.getSelection();
|
||||
Target selection = statRequest.getTarget();
|
||||
try {
|
||||
switch (selection) {
|
||||
case PLAYER -> outputManager.sendPlayerStat(request, getIndividualStat());
|
||||
case TOP -> outputManager.sendTopStat(request, getTopStats());
|
||||
case SERVER -> outputManager.sendServerStat(request, getServerTotal());
|
||||
}
|
||||
} catch (ConcurrentModificationException e) {
|
||||
if (!request.isConsoleSender()) {
|
||||
outputManager.sendFeedbackMsg(request.getCommandSender(), StandardMessage.UNKNOWN_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, Integer> getTopStats() throws ConcurrentModificationException {
|
||||
return getAllStats().entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||
.limit(config.getTopListMaxSize()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private long getServerTotal() {
|
||||
List<Integer> numbers = getAllStats().values().parallelStream().toList();
|
||||
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
|
||||
}
|
||||
|
||||
//invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players in the list)
|
||||
private @NotNull ConcurrentHashMap<String, Integer> getAllStats() throws ConcurrentModificationException {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
int size = offlinePlayerHandler.getOfflinePlayerCount() != 0 ? offlinePlayerHandler.getOfflinePlayerCount() : 16;
|
||||
ConcurrentHashMap<String, Integer> playerStats = new ConcurrentHashMap<>(size);
|
||||
ImmutableList<String> playerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
|
||||
|
||||
StatAction task = new StatAction(offlinePlayerHandler, playerNames, request, playerStats);
|
||||
MyLogger.actionCreated(playerNames.size());
|
||||
ForkJoinPool commonPool = ForkJoinPool.commonPool();
|
||||
|
||||
try {
|
||||
commonPool.invoke(task);
|
||||
} catch (ConcurrentModificationException e) {
|
||||
MyLogger.logMsg("The request could not be executed due to a ConcurrentModificationException. " +
|
||||
"This likely happened because Bukkit hasn't fully initialized all player-data yet. " +
|
||||
"Try again and it should be fine!", true);
|
||||
throw new ConcurrentModificationException(e.toString());
|
||||
}
|
||||
|
||||
MyLogger.actionFinished(2);
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
MyLogger.logTimeTaken("StatThread", "calculated all stats", time);
|
||||
|
||||
return playerStats;
|
||||
}
|
||||
|
||||
/** Gets the statistic data for an individual player. If somehow the player
|
||||
cannot be found, this returns 0.*/
|
||||
private int getIndividualStat() {
|
||||
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(request.getPlayerName());
|
||||
if (player != null) {
|
||||
return switch (request.getStatistic().getType()) {
|
||||
case UNTYPED -> player.getStatistic(request.getStatistic());
|
||||
case ENTITY -> player.getStatistic(request.getStatistic(), request.getEntity());
|
||||
case BLOCK -> player.getStatistic(request.getStatistic(), request.getBlock());
|
||||
case ITEM -> player.getStatistic(request.getStatistic(), request.getItem());
|
||||
TextComponent statResult = switch (selection) {
|
||||
case PLAYER -> outputManager.formatPlayerStat(statRequest, statManager.getPlayerStat(statRequest));
|
||||
case TOP -> outputManager.formatTopStat(statRequest, statManager.getTopStats(statRequest));
|
||||
case SERVER -> outputManager.formatServerStat(statRequest, statManager.getServerStat(statRequest));
|
||||
};
|
||||
if (statRequest.isAPIRequest()) {
|
||||
String msg = ComponentUtils.getTranslatableComponentSerializer()
|
||||
.serialize(statResult);
|
||||
statRequest.getCommandSender().sendMessage(msg);
|
||||
}
|
||||
else {
|
||||
outputManager.sendToCommandSender(statRequest.getCommandSender(), statResult);
|
||||
}
|
||||
}
|
||||
catch (ConcurrentModificationException e) {
|
||||
if (!statRequest.isConsoleSender()) {
|
||||
outputManager.sendFeedbackMsg(statRequest.getCommandSender(), StandardMessage.UNKNOWN_ERROR);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.RequestExecutor;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.PlayerStatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.StatResult;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class PlayerStatRequest implements RequestExecutor<Integer> {
|
||||
|
||||
private final StatRequestHandler statRequestHandler;
|
||||
|
||||
public PlayerStatRequest(StatRequestHandler statRequestHandler) {
|
||||
this.statRequestHandler = statRequestHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<Integer> untyped(@NotNull Statistic statistic) {
|
||||
StatRequest completedRequest = statRequestHandler.untyped(statistic);
|
||||
return getStatResult(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<Integer> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
StatRequest completedRequest = statRequestHandler.blockOrItemType(statistic, material);
|
||||
return getStatResult(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<Integer> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
StatRequest completedRequest = statRequestHandler.entityType(statistic, entityType);
|
||||
return getStatResult(completedRequest);
|
||||
}
|
||||
|
||||
private PlayerStatResult getStatResult(StatRequest completedRequest) {
|
||||
int stat = RequestExecutor.super.getStatCalculator()
|
||||
.getPlayerStat(completedRequest);
|
||||
TextComponent prettyStat = RequestExecutor.super.getStatFormatter()
|
||||
.formatPlayerStat(completedRequest, stat);
|
||||
|
||||
return new PlayerStatResult(stat, prettyStat);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.RequestExecutor;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.ServerStatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.StatResult;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ServerStatRequest implements RequestExecutor<Long> {
|
||||
|
||||
private final StatRequestHandler statRequestHandler;
|
||||
|
||||
public ServerStatRequest(StatRequestHandler statRequestHandler) {
|
||||
this.statRequestHandler = statRequestHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<Long> untyped(@NotNull Statistic statistic) {
|
||||
StatRequest completedRequest = statRequestHandler.untyped(statistic);
|
||||
return getStatResult(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<Long> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
StatRequest completedRequest = statRequestHandler.blockOrItemType(statistic, material);
|
||||
return getStatResult(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<Long> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
StatRequest completedRequest = statRequestHandler.entityType(statistic, entityType);
|
||||
return getStatResult(completedRequest);
|
||||
}
|
||||
|
||||
private ServerStatResult getStatResult(StatRequest completedRequest) {
|
||||
long stat = RequestExecutor.super.getStatCalculator()
|
||||
.getServerStat(completedRequest);
|
||||
TextComponent prettyStat = RequestExecutor.super.getStatFormatter()
|
||||
.formatServerStat(completedRequest, stat);
|
||||
|
||||
return new ServerStatResult(stat, prettyStat);
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** A StatRequest is the object PlayerStats uses to calculate and format the requested statistic.
|
||||
This object can be generated from two different sources:
|
||||
<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 StatRequest to be valid, it needs the following values:
|
||||
<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> (automatically set for all API-requests)
|
||||
<li> if the <code>target</code> is Target.Player, a <code>playerName</code> needs to be added
|
||||
</ul>*/
|
||||
public final class StatRequest {
|
||||
|
||||
private final CommandSender sender;
|
||||
private boolean isAPIRequest;
|
||||
private Statistic statistic;
|
||||
private String playerName;
|
||||
private Target target;
|
||||
private int topListSize;
|
||||
|
||||
private String subStatEntryName;
|
||||
private EntityType entity;
|
||||
private Material block;
|
||||
private Material item;
|
||||
private boolean playerFlag;
|
||||
|
||||
/** Create a new {@link StatRequest} with default values:
|
||||
<br>- CommandSender sender (provided)
|
||||
<br>- Target target = {@link Target#TOP}
|
||||
<br>- int topListSize = 10
|
||||
<br>- boolean playerFlag = false
|
||||
<br>- boolean isAPIRequest
|
||||
|
||||
@param sender the CommandSender who prompted this RequestGenerator
|
||||
@param isAPIRequest whether this RequestGenerator is coming through the API or the onCommand
|
||||
*/
|
||||
private StatRequest(@NotNull CommandSender sender, boolean isAPIRequest) {
|
||||
this.sender = sender;
|
||||
this.isAPIRequest = isAPIRequest;
|
||||
target = Target.TOP;
|
||||
playerFlag = false;
|
||||
}
|
||||
|
||||
public static StatRequest getBasicRequest(CommandSender sender) {
|
||||
return new StatRequest(sender, false);
|
||||
}
|
||||
|
||||
public static StatRequest getBasicAPIRequest() {
|
||||
return new StatRequest(Bukkit.getConsoleSender(), true);
|
||||
}
|
||||
|
||||
public void setAPIRequest() {
|
||||
this.isAPIRequest = true;
|
||||
}
|
||||
|
||||
public boolean isAPIRequest() {
|
||||
return isAPIRequest;
|
||||
}
|
||||
|
||||
public @NotNull CommandSender getCommandSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public boolean isConsoleSender() {
|
||||
return sender instanceof ConsoleCommandSender;
|
||||
}
|
||||
|
||||
public void setStatistic(Statistic statistic) {
|
||||
this.statistic = statistic;
|
||||
}
|
||||
|
||||
public Statistic getStatistic() {
|
||||
return statistic;
|
||||
}
|
||||
|
||||
public void setSubStatEntryName(String subStatEntry) {
|
||||
this.subStatEntryName = subStatEntry;
|
||||
}
|
||||
|
||||
public String getSubStatEntryName() {
|
||||
return subStatEntryName;
|
||||
}
|
||||
|
||||
public void setPlayerName(String playerName) {
|
||||
this.playerName = playerName;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public void setPlayerFlag(boolean playerFlag) {
|
||||
this.playerFlag = playerFlag;
|
||||
}
|
||||
|
||||
public boolean getPlayerFlag() {
|
||||
return playerFlag;
|
||||
}
|
||||
|
||||
public void setTarget(Target target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public @NotNull Target getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTopListSize(int topListSize) {
|
||||
this.topListSize = topListSize;
|
||||
}
|
||||
|
||||
public int getTopListSize() {
|
||||
return this.topListSize;
|
||||
}
|
||||
|
||||
public void setEntity(EntityType entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public EntityType getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
public void setBlock(Material block) {
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
public Material getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
public void setItem(Material item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public Material getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
if (statistic == null) {
|
||||
return false;
|
||||
} else if (target == Target.PLAYER && playerName == null) {
|
||||
return false;
|
||||
} else if (statistic.getType() != Statistic.Type.UNTYPED &&
|
||||
subStatEntryName == null) {
|
||||
return false;
|
||||
} else {
|
||||
return hasMatchingSubStat();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasMatchingSubStat() {
|
||||
switch (statistic.getType()) {
|
||||
case BLOCK -> {
|
||||
return block != null;
|
||||
}
|
||||
case ENTITY -> {
|
||||
return entity != null;
|
||||
}
|
||||
case ITEM -> {
|
||||
return item != null;
|
||||
}
|
||||
default -> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record StatRequestHandler(StatRequest statRequest) implements RequestGenerator {
|
||||
|
||||
public static StatRequestHandler playerRequestHandler(String playerName) {
|
||||
StatRequest request = StatRequest.getBasicAPIRequest();
|
||||
request.setTarget(Target.PLAYER);
|
||||
request.setPlayerName(playerName);
|
||||
return new StatRequestHandler(request);
|
||||
}
|
||||
|
||||
public static StatRequestHandler serverRequestHandler() {
|
||||
StatRequest request = StatRequest.getBasicAPIRequest();
|
||||
request.setTarget(Target.SERVER);
|
||||
return new StatRequestHandler(request);
|
||||
}
|
||||
|
||||
public static StatRequestHandler topRequestHandler(int topListSize) {
|
||||
StatRequest request = StatRequest.getBasicAPIRequest();
|
||||
request.setTarget(Target.TOP);
|
||||
request.setTopListSize(topListSize != 0 ? topListSize : Main.getConfigHandler().getTopListMaxSize());
|
||||
return new StatRequestHandler(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@param sender the CommandSender that requested this specific statistic
|
||||
*/
|
||||
public static StatRequestHandler internalRequestHandler(CommandSender sender) {
|
||||
StatRequest request = StatRequest.getBasicRequest(sender);
|
||||
request.setTopListSize(Main.getConfigHandler().getTopListMaxSize());
|
||||
return new StatRequestHandler(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest untyped(@NotNull Statistic statistic) throws IllegalArgumentException {
|
||||
if (statistic.getType() == Statistic.Type.UNTYPED) {
|
||||
statRequest.setStatistic(statistic);
|
||||
return statRequest;
|
||||
}
|
||||
throw new IllegalArgumentException("This statistic is not of Type.Untyped");
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
|
||||
Statistic.Type type = statistic.getType();
|
||||
if (type == Statistic.Type.BLOCK && material.isBlock()) {
|
||||
statRequest.setBlock(material);
|
||||
}
|
||||
else if (type == Statistic.Type.ITEM && material.isItem()){
|
||||
statRequest.setItem(material);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Either this statistic is not of Type.Block or Type.Item, or no valid block or item has been provided");
|
||||
}
|
||||
statRequest.setSubStatEntryName(material.toString());
|
||||
return statRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
|
||||
if (statistic.getType() == Statistic.Type.ENTITY) {
|
||||
statRequest.setSubStatEntryName(entityType.toString());
|
||||
statRequest.setEntity(entityType);
|
||||
return statRequest;
|
||||
}
|
||||
throw new IllegalArgumentException("This statistic is not of Type.Entity");
|
||||
}
|
||||
|
||||
/**
|
||||
This will create a {@link StatRequest} from the provided args, with the requesting Player (or Console)
|
||||
as CommandSender. This CommandSender will receive feedback messages if the StatRequest could not be created.
|
||||
|
||||
@param args an Array of args such as a CommandSender would put in Minecraft chat:
|
||||
<p>- a <code>statName</code> (example: "mine_block")</p>
|
||||
<p>- if applicable, a <code>subStatEntryName</code> (example: diorite)(</p>
|
||||
<p>- a <code>target</code> for this lookup: can be "top", "server", "player" (or "me" to indicate the current CommandSender)</p>
|
||||
<p>- if "player" was chosen, include a <code>playerName</code></p>
|
||||
@return the generated StatRequest
|
||||
*/
|
||||
public StatRequest getRequestFromArgs(String[] args) {
|
||||
EnumHandler enumHandler = Main.getEnumHandler();
|
||||
OfflinePlayerHandler offlinePlayerHandler = Main.getOfflinePlayerHandler();
|
||||
CommandSender sender = statRequest.getCommandSender();
|
||||
|
||||
for (String arg : args) {
|
||||
//check for statName
|
||||
if (enumHandler.isStatistic(arg) && statRequest.getStatistic() == null) {
|
||||
statRequest.setStatistic(EnumHandler.getStatEnum(arg));
|
||||
}
|
||||
//check for subStatEntry and playerFlag
|
||||
else if (enumHandler.isSubStatEntry(arg)) {
|
||||
if (arg.equalsIgnoreCase("player") && !statRequest.getPlayerFlag()) {
|
||||
statRequest.setPlayerFlag(true);
|
||||
} else {
|
||||
if (statRequest.getSubStatEntryName() == null) statRequest.setSubStatEntryName(arg);
|
||||
}
|
||||
}
|
||||
//check for selection
|
||||
else if (arg.equalsIgnoreCase("top")) {
|
||||
statRequest.setTarget(Target.TOP);
|
||||
} else if (arg.equalsIgnoreCase("server")) {
|
||||
statRequest.setTarget(Target.SERVER);
|
||||
} else if (arg.equalsIgnoreCase("me")) {
|
||||
if (sender instanceof Player) {
|
||||
statRequest.setPlayerName(sender.getName());
|
||||
statRequest.setTarget(Target.PLAYER);
|
||||
} else if (sender instanceof ConsoleCommandSender) {
|
||||
statRequest.setTarget(Target.SERVER);
|
||||
}
|
||||
} else if (offlinePlayerHandler.isRelevantPlayer(arg) && statRequest.getPlayerName() == null) {
|
||||
statRequest.setPlayerName(arg);
|
||||
statRequest.setTarget(Target.PLAYER);
|
||||
} else if (arg.equalsIgnoreCase("api")) {
|
||||
statRequest.setAPIRequest();
|
||||
}
|
||||
}
|
||||
patchRequest(statRequest);
|
||||
return statRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
Adjust the StatRequest object if needed: unpack the playerFlag into a subStatEntry,
|
||||
try to retrieve the corresponding Enum Constant for any relevant block/entity/item,
|
||||
and remove any unnecessary subStatEntries.
|
||||
*/
|
||||
private void patchRequest(StatRequest statRequest) {
|
||||
if (statRequest.getStatistic() != null) {
|
||||
Statistic.Type type = statRequest.getStatistic().getType();
|
||||
|
||||
if (statRequest.getPlayerFlag()) { //unpack the playerFlag
|
||||
if (type == Statistic.Type.ENTITY && statRequest.getSubStatEntryName() == null) {
|
||||
statRequest.setSubStatEntryName("player");
|
||||
} else {
|
||||
statRequest.setTarget(Target.PLAYER);
|
||||
}
|
||||
}
|
||||
|
||||
String subStatEntry = statRequest.getSubStatEntryName();
|
||||
switch (type) { //attempt to convert relevant subStatEntries into their corresponding Enum Constant
|
||||
case BLOCK -> {
|
||||
Material block = EnumHandler.getBlockEnum(subStatEntry);
|
||||
if (block != null) statRequest.setBlock(block);
|
||||
}
|
||||
case ENTITY -> {
|
||||
EntityType entity = EnumHandler.getEntityEnum(subStatEntry);
|
||||
if (entity != null) statRequest.setEntity(entity);
|
||||
}
|
||||
case ITEM -> {
|
||||
Material item = EnumHandler.getItemEnum(subStatEntry);
|
||||
if (item != null) statRequest.setItem(item);
|
||||
}
|
||||
case UNTYPED -> { //remove unnecessary subStatEntries
|
||||
if (subStatEntry != null) statRequest.setSubStatEntryName(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.RequestExecutor;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.StatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.TopStatResult;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public final class TopStatRequest implements RequestExecutor<LinkedHashMap<String, Integer>> {
|
||||
|
||||
private final StatRequestHandler statRequestHandler;
|
||||
|
||||
public TopStatRequest(StatRequestHandler statRequestHandler) {
|
||||
this.statRequestHandler = statRequestHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<LinkedHashMap<String, Integer>> untyped(@NotNull Statistic statistic) {
|
||||
StatRequest completedRequest = statRequestHandler.untyped(statistic);
|
||||
return getStatResult(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<LinkedHashMap<String, Integer>> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
StatRequest completedRequest = statRequestHandler.blockOrItemType(statistic, material);
|
||||
return getStatResult(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<LinkedHashMap<String, Integer>> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
StatRequest completedRequest = statRequestHandler.entityType(statistic, entityType);
|
||||
return getStatResult(completedRequest);
|
||||
}
|
||||
|
||||
private TopStatResult getStatResult(StatRequest completedRequest) {
|
||||
LinkedHashMap<String, Integer> stat = RequestExecutor.super.getStatCalculator()
|
||||
.getTopStats(completedRequest);
|
||||
TextComponent prettyStat = RequestExecutor.super.getStatFormatter()
|
||||
.formatTopStat(completedRequest, stat);
|
||||
|
||||
return new TopStatResult(stat, prettyStat);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
/** This Record is used to store stat-results internally, so Players can share them by clicking a share-button.*/
|
||||
public record InternalStatResult(String executorName, TextComponent formattedValue, int ID) implements StatResult<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 toString() {
|
||||
return ComponentUtils.getTranslatableComponentSerializer()
|
||||
.serialize(formattedValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public record PlayerStatResult(int value, TextComponent formattedValue) implements StatResult<Integer> {
|
||||
|
||||
@Override
|
||||
public Integer getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ComponentUtils.getTranslatableComponentSerializer()
|
||||
.serialize(formattedValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public record ServerStatResult(long value, TextComponent formattedValue) implements StatResult<Long> {
|
||||
|
||||
@Override
|
||||
public Long getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ComponentUtils.getTranslatableComponentSerializer()
|
||||
.serialize(formattedValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.api.Formatter;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
/** Holds the result of a completed stat-lookup. The <code>Type</code> parameter
|
||||
<code>T</code> of this StatResult represents the data type of the stored number:
|
||||
<ul>
|
||||
<li> <code>Integer</code> for playerStat
|
||||
<li> <code>Long</code> for serverStat
|
||||
<li> <code>LinkedHashMap(String, Integer)</code> for topStat
|
||||
</ul>
|
||||
You can get these raw numbers with {@link #getNumericalValue()}. Additionally,
|
||||
you can get a formatted message that contains the following information:
|
||||
<ul>
|
||||
<li> for playerStat:
|
||||
<br> [player-name]: [formatted-number] [stat-name] [sub-stat-name]
|
||||
<li> for serverStat:
|
||||
<br> [Total on] [server-name]: [formatted-number] [stat-name] [sub-stat-name]
|
||||
<li> for topStat:
|
||||
<br> [PlayerStats] [Top x] [stat-name] [sub-stat-name]
|
||||
<br> [1.] [player-name] [.....] [formatted-number]
|
||||
<br> [2.] [player-name] [.....] [formatted-number]
|
||||
<br> [3.] etc...
|
||||
</ul>
|
||||
|
||||
By default, the resulting message is a {@link TextComponent}, which can be sent directly
|
||||
to a Minecraft client or console with the Adventure library. To send a Component,
|
||||
you need to get a {@link BukkitAudiences} object, and use that to send the desired Component.
|
||||
Normally you would have to add Adventure as a dependency to your project,
|
||||
but since the library is included in PlayerStats, you can access it directly.
|
||||
Information on how to get and use the BukkitAudiences object can be
|
||||
found on <a href="https://docs.adventure.kyori.net/platform/bukkit.html">Adventure's website</a>.
|
||||
|
||||
<p>You can also use the provided {@link #toString()} method to get the same information
|
||||
in String-format. Don't use Adventure's toString methods on the Components
|
||||
- those are for debugging purposes. And finally, if you want the results to be
|
||||
formatted differently, you can get an instance of the {@link Formatter}.
|
||||
*/
|
||||
public interface StatResult<T> {
|
||||
|
||||
/** 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*/
|
||||
T getNumericalValue();
|
||||
|
||||
/** Gets the formatted message for the completed stat-lookup this {@link StatResult} stores.
|
||||
|
||||
@return a {@code TextComponent} message containing the formatted number.
|
||||
This message follows the same style/color/language settings that are specified in the
|
||||
PlayerStats config. See class description for more information. */
|
||||
TextComponent getFormattedTextComponent();
|
||||
|
||||
/** Turns the formatted message for the completed stat-lookup into String.
|
||||
|
||||
@return a String message containing the formatted number. This message follows
|
||||
the same style and color settings that are specified in the PlayerStats config,
|
||||
but it is not translatable (it is always plain English). See class description
|
||||
for more information.*/
|
||||
String toString();
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public record TopStatResult(LinkedHashMap<String, Integer> value, TextComponent formattedValue) implements StatResult<LinkedHashMap<String,Integer>> {
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<String, Integer> getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ComponentUtils.getTranslatableComponentSerializer()
|
||||
.serialize(formattedValue);
|
||||
}
|
||||
}
|
@ -19,54 +19,27 @@ import java.util.stream.Stream;
|
||||
and turn a name into its corresponding enum constant. */
|
||||
public final class EnumHandler {
|
||||
|
||||
private final static List<String> blockNames;
|
||||
private final static List<String> entityNames;
|
||||
private final static List<String> itemNames;
|
||||
private final static List<String> statNames;
|
||||
private final static List<String> subStatNames;
|
||||
private static List<String> blockNames;
|
||||
private static List<String> itemNames;
|
||||
private static List<String> statNames;
|
||||
private static List<String> subStatNames;
|
||||
|
||||
static {
|
||||
blockNames = Arrays.stream(Material.values())
|
||||
.filter(Material::isBlock)
|
||||
.map(Material::toString)
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
entityNames = Arrays.stream(EntityType.values())
|
||||
.map(EntityType::toString)
|
||||
.map(String::toLowerCase)
|
||||
.filter(entityName -> !entityName.equalsIgnoreCase("unknown"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
itemNames = Arrays.stream(Material.values())
|
||||
.filter(Material::isItem)
|
||||
.map(Material::toString)
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
subStatNames = Stream.of(blockNames, entityNames, itemNames)
|
||||
.flatMap(Collection::stream)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
statNames = Arrays.stream(Statistic.values())
|
||||
.map(Statistic::toString)
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toList());
|
||||
public EnumHandler() {
|
||||
prepareLists();
|
||||
}
|
||||
|
||||
/** Returns all block-names in lowercase */
|
||||
public static List<String> getBlockNames() {
|
||||
public List<String> getBlockNames() {
|
||||
return blockNames;
|
||||
}
|
||||
|
||||
/** Returns all item-names in lowercase*/
|
||||
public static List<String> getItemNames() {
|
||||
public List<String> getItemNames() {
|
||||
return itemNames;
|
||||
}
|
||||
|
||||
/** Returns all statistic-names in lowercase */
|
||||
public static List<String> getStatNames() {
|
||||
public List<String> getStatNames() {
|
||||
return statNames;
|
||||
}
|
||||
|
||||
@ -115,31 +88,50 @@ public final class EnumHandler {
|
||||
|
||||
/** Checks if string is a valid statistic
|
||||
@param statName String, case-insensitive */
|
||||
public static boolean isStatistic(@NotNull String statName) {
|
||||
public boolean isStatistic(@NotNull String statName) {
|
||||
return statNames.contains(statName.toLowerCase());
|
||||
}
|
||||
|
||||
/** Checks whether the given String equals the name of an entity-type statistic. */
|
||||
public static boolean isEntityStatistic(String statName) {
|
||||
public boolean isEntityStatistic(String statName) {
|
||||
return statName.equalsIgnoreCase(Statistic.ENTITY_KILLED_BY.toString()) ||
|
||||
statName.equalsIgnoreCase(Statistic.KILL_ENTITY.toString());
|
||||
}
|
||||
|
||||
/** Checks if this statistic is a subStatEntry, meaning it is a block, item or entity
|
||||
@param statName String, case-insensitive*/
|
||||
public static boolean isSubStatEntry(@NotNull String statName) {
|
||||
public boolean isSubStatEntry(@NotNull String statName) {
|
||||
return subStatNames.contains(statName.toLowerCase());
|
||||
}
|
||||
|
||||
/** Returns "block", "entity", "item", or "sub-statistic" if the provided Type is null. */
|
||||
public static String getSubStatTypeName(Statistic.Type statType) {
|
||||
String subStat = "sub-statistic";
|
||||
if (statType == null) return subStat;
|
||||
switch (statType) {
|
||||
case BLOCK -> subStat = "block";
|
||||
case ENTITY -> subStat = "entity";
|
||||
case ITEM -> subStat = "item";
|
||||
}
|
||||
return subStat;
|
||||
private void prepareLists() {
|
||||
List<String> entityNames = Arrays.stream(EntityType.values())
|
||||
.map(EntityType::toString)
|
||||
.map(String::toLowerCase)
|
||||
.filter(entityName -> !entityName.equalsIgnoreCase("unknown"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
blockNames = Arrays.stream(Material.values())
|
||||
.filter(Material::isBlock)
|
||||
.map(Material::toString)
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
itemNames = Arrays.stream(Material.values())
|
||||
.filter(Material::isItem)
|
||||
.map(Material::toString)
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
subStatNames = Stream.of(blockNames, entityNames, itemNames)
|
||||
.flatMap(Collection::stream)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
statNames = Arrays.stream(Statistic.values())
|
||||
.map(Statistic::toString)
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
@ -13,6 +13,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/** The PlayerStats Logger*/
|
||||
public final class MyLogger {
|
||||
|
||||
private static final Logger logger;
|
||||
@ -36,10 +37,10 @@ public final class MyLogger {
|
||||
}
|
||||
|
||||
/** Sets the desired debugging level.
|
||||
<p>1 = low (only show unexpected errors)</p>
|
||||
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
|
||||
<p>3 = high (log all tasks and time taken)</p>
|
||||
<p>Default: 1</p>*/
|
||||
<br>1 = low (only show unexpected errors)</br>
|
||||
<br>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</br>
|
||||
<br>3 = high (log all tasks and time taken)</br>
|
||||
<br>Default: 1</br>*/
|
||||
public static void setDebugLevel(int level) {
|
||||
if (level == 2) {
|
||||
debugLevel = DebugLevel.MEDIUM;
|
||||
@ -80,6 +81,10 @@ public final class MyLogger {
|
||||
}
|
||||
}
|
||||
|
||||
public static void logException(@NotNull Exception exception, String caughtBy) {
|
||||
logException(exception, caughtBy, null);
|
||||
}
|
||||
|
||||
/** Log the encountered exception as a warning to console,
|
||||
with some information about which class/method caught it
|
||||
and with a printStackTrace if DebugLevel is HIGH.
|
||||
@ -96,7 +101,7 @@ public final class MyLogger {
|
||||
}
|
||||
}
|
||||
|
||||
/** If DebugLevel is MEDIUM or HIGH, logs when the while loop in MessageWriter, getLanguageKey is being run. */
|
||||
/** If DebugLevel is MEDIUM or HIGH, logs when the while loop in MessageBuilder, getLanguageKey is being run. */
|
||||
public static void replacingUnderscores() {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
logger.info("Replacing underscores and capitalizing names...");
|
||||
@ -105,14 +110,14 @@ public final class MyLogger {
|
||||
|
||||
/** Output to console that the given thread has been created (but not started yet).*/
|
||||
public static void threadCreated(String threadName) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
logger.info(threadName + " created!");
|
||||
}
|
||||
}
|
||||
|
||||
/** Output to console that the given thread has been started. */
|
||||
public static void threadStart(String threadName) {
|
||||
if (debugLevel == DebugLevel.MEDIUM || debugLevel == DebugLevel.HIGH) {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
logger.info(threadName + " started!");
|
||||
}
|
||||
}
|
||||
@ -190,14 +195,6 @@ public final class MyLogger {
|
||||
}
|
||||
}
|
||||
|
||||
/** Output to console how long a certain task has taken (regardless of DebugLevel).
|
||||
@param className Name of the class executing the task
|
||||
@param methodName Name or description of the task
|
||||
@param startTime Timestamp marking the beginning of the task */
|
||||
public static void logTimeTaken(String className, String methodName, long startTime) {
|
||||
logTimeTaken(className, methodName, startTime, DebugLevel.LOW);
|
||||
}
|
||||
|
||||
/** Output to console how long a certain task has taken if DebugLevel is equal to or higher than the specified threshold.
|
||||
@param className Name of the class executing the task
|
||||
@param methodName Name or description of the task
|
||||
|
@ -7,7 +7,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class OfflinePlayerHandler {
|
||||
/** A utility class that deals with OfflinePlayers. It stores a list of all OfflinePlayer-names
|
||||
that need to be included in statistic calculations, and can retrieve the corresponding OfflinePlayer
|
||||
object for a given player-name.*/
|
||||
public final class OfflinePlayerHandler {
|
||||
|
||||
private static ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||
private static ArrayList<String> playerNames;
|
||||
@ -17,6 +20,16 @@ public class OfflinePlayerHandler {
|
||||
playerNames = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new HashMap that stores the players to include in stat calculations.
|
||||
* This HashMap is stored as a private variable in OfflinePlayerHandler.
|
||||
* @param playerList ConcurrentHashMap with keys: playerNames and values: UUIDs
|
||||
*/
|
||||
public static void updateOfflinePlayerList(ConcurrentHashMap<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 */
|
||||
public boolean isRelevantPlayer(String playerName) {
|
||||
@ -33,16 +46,6 @@ public class OfflinePlayerHandler {
|
||||
return playerNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new HashMap that stores the players to include in stat calculations.
|
||||
* This HashMap is stored as a private variable in OfflinePlayerHandler.
|
||||
* @param playerList ConcurrentHashMap with keys: playerNames and values: UUIDs
|
||||
*/
|
||||
public void updateOfflinePlayerList(ConcurrentHashMap<String, UUID> playerList) {
|
||||
offlinePlayerUUIDs = playerList;
|
||||
playerNames = Collections.list(offlinePlayerUUIDs.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the playerName to get the player's UUID from a private HashMap, and uses the UUID to get the corresponding OfflinePlayer Object.
|
||||
* @param playerName name of the target player
|
||||
@ -53,6 +56,7 @@ public class OfflinePlayerHandler {
|
||||
return Bukkit.getOfflinePlayer(offlinePlayerUUIDs.get(playerName));
|
||||
}
|
||||
else {
|
||||
MyLogger.logMsg("Cannot calculate statistics for player-name: " + playerName, true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
/** A small utility class that calculates with unix time.*/
|
||||
public final class UnixTimeHandler {
|
||||
|
||||
private UnixTimeHandler() {
|
||||
|
@ -4,6 +4,8 @@ version: 1.6.1
|
||||
api-version: 1.13
|
||||
description: adds commands to view player statistics in chat
|
||||
author: Artemis_the_gr8
|
||||
softdepend:
|
||||
- PlaceholderAPI
|
||||
commands:
|
||||
statistic:
|
||||
aliases:
|
||||
|
Loading…
Reference in New Issue
Block a user