mirror of
https://github.com/itHotL/PlayerStats.git
synced 2025-02-12 01:01:21 +01:00
Merge branch 'api'
# Conflicts: # src/main/java/com/artemis/the/gr8/playerstats/enums/Unit.java # src/main/java/com/artemis/the/gr8/playerstats/msg/msgutils/NumberFormatter.java
This commit is contained in:
commit
320e6a6c22
@ -7,7 +7,8 @@
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="JavadocGenerationManager">
|
||||
<option name="OUTPUT_DIRECTORY" value="$PROJECT_DIR$/javadoc" />
|
||||
<option name="OUTPUT_DIRECTORY" value="$PROJECT_DIR$/target/apidocs" />
|
||||
<option name="OPTION_SCOPE" value="public" />
|
||||
</component>
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
|
@ -1,9 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.gmail.artemis.the.gr8</groupId>
|
||||
<groupId>io.github.ithotl</groupId>
|
||||
<artifactId>PlayerStats</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<name>PlayerStats</name>
|
||||
<version>1.7</version>
|
||||
<description>Statistics Plugin</description>
|
||||
<url>https://www.spigotmc.org/resources/playerstats.102347/</url>
|
||||
<developers>
|
||||
<developer>
|
||||
<name>Artemis</name>
|
||||
<email>artemis.the.gr8@gmail.com</email>
|
||||
<url>https://github.com/Artemis-the-gr8</url>
|
||||
</developer>
|
||||
</developers>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>http://www.opensource.org/licenses/mit-license.php</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/itHotL/PlayerStats.git</connection>
|
||||
<developerConnection>scm:git:git://github.com/itHotL/PlayerStats.git</developerConnection>
|
||||
<url>https://github.com/itHotL/PlayerStats/tree/main</url>
|
||||
</scm>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
@ -28,7 +49,7 @@
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer>
|
||||
<mainClass>com.gmail.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
<mainClass>com.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<artifactSet>
|
||||
@ -39,15 +60,15 @@
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.lib.kyori</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.lib.kyori</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.tchristofferson</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.util.tchristofferson</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.util.tchristofferson</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.util.bstats</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.util.bstats</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<filters>
|
||||
@ -92,6 +113,66 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||
<failOnError>false</failOnError>
|
||||
<quiet>true</quiet>
|
||||
<show>protected</show>
|
||||
<groups>
|
||||
<group>
|
||||
<title>API</title>
|
||||
<packages>com.artemis.the.gr8.playerstats.api</packages>
|
||||
</group>
|
||||
</groups>
|
||||
<detectLinks>true</detectLinks>
|
||||
<dependencyLinks>
|
||||
<dependencyLink>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<url>https://hub.spigotmc.org/javadocs/bukkit/</url>
|
||||
</dependencyLink>
|
||||
<dependencyLink>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<url>https://jd.adventure.kyori.net/api/4.11.0/</url>
|
||||
</dependencyLink>
|
||||
<dependencyLink>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-minimessage</artifactId>
|
||||
<url>https://jd.adventure.kyori.net/text-minimessage/4.11.0/</url>
|
||||
</dependencyLink>
|
||||
<dependencyLink>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-platform-bukkit</artifactId>
|
||||
<url>https://jd.adventure.kyori.net/</url>
|
||||
</dependencyLink>
|
||||
</dependencyLinks>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<repositories>
|
||||
|
103
pom.xml
103
pom.xml
@ -4,9 +4,34 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.gmail.artemis.the.gr8</groupId>
|
||||
<groupId>io.github.ithotl</groupId>
|
||||
<artifactId>PlayerStats</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<version>1.7</version>
|
||||
|
||||
<name>PlayerStats</name>
|
||||
<description>Statistics Plugin</description>
|
||||
<url>https://www.spigotmc.org/resources/playerstats.102347/</url>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>http://www.opensource.org/licenses/mit-license.php</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<name>Artemis</name>
|
||||
<email>artemis.the.gr8@gmail.com</email>
|
||||
<url>https://github.com/Artemis-the-gr8</url>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/itHotL/PlayerStats/tree/main</url>
|
||||
<connection>scm:git:git://github.com/itHotL/PlayerStats.git</connection>
|
||||
<developerConnection>scm:git:git://github.com/itHotL/PlayerStats.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@ -14,7 +39,6 @@
|
||||
<maven.compiler.target>16</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigot-repo</id> <!-- Spigot API -->
|
||||
@ -117,7 +141,7 @@
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.gmail.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
<mainClass>com.artemis.the.gr8.playerstats.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<artifactSet>
|
||||
@ -128,15 +152,15 @@
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.lib.kyori</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.lib.kyori</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.tchristofferson</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.util.tchristofferson</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.util.tchristofferson</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>com.gmail.artemis.the.gr8.util.bstats</shadedPattern>
|
||||
<shadedPattern>com.artemis.the.gr8.util.bstats</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<filters>
|
||||
@ -182,6 +206,71 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||
<failOnError>false</failOnError>
|
||||
<quiet>true</quiet>
|
||||
<show>protected</show>
|
||||
<groups>
|
||||
<group>
|
||||
<title>API</title>
|
||||
<packages>com.artemis.the.gr8.playerstats.api</packages>
|
||||
</group>
|
||||
</groups>
|
||||
<detectLinks>true</detectLinks>
|
||||
<dependencyLinks>
|
||||
<dependencyLink>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<url>https://hub.spigotmc.org/javadocs/bukkit/</url>
|
||||
</dependencyLink>
|
||||
|
||||
<dependencyLink>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<url>https://jd.adventure.kyori.net/api/4.11.0/</url>
|
||||
</dependencyLink>
|
||||
|
||||
<dependencyLink>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-minimessage</artifactId>
|
||||
<url>https://jd.adventure.kyori.net/text-minimessage/4.11.0/</url>
|
||||
</dependencyLink>
|
||||
|
||||
<dependencyLink>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-platform-bukkit</artifactId>
|
||||
<url>https://jd.adventure.kyori.net/</url>
|
||||
</dependencyLink>
|
||||
</dependencyLinks>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
189
src/main/java/com/artemis/the/gr8/playerstats/Main.java
Normal file
189
src/main/java/com/artemis/the/gr8/playerstats/Main.java
Normal file
@ -0,0 +1,189 @@
|
||||
package com.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.PlayerStats;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.api.PlayerStatsAPI;
|
||||
import com.artemis.the.gr8.playerstats.commands.ReloadCommand;
|
||||
import com.artemis.the.gr8.playerstats.commands.ShareCommand;
|
||||
import com.artemis.the.gr8.playerstats.commands.StatCommand;
|
||||
import com.artemis.the.gr8.playerstats.commands.TabCompleter;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.listeners.JoinListener;
|
||||
import com.artemis.the.gr8.playerstats.msg.InternalFormatter;
|
||||
import com.artemis.the.gr8.playerstats.msg.MessageBuilder;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* PlayerStats' Main class
|
||||
*/
|
||||
public final class Main extends JavaPlugin {
|
||||
|
||||
private static Main instance;
|
||||
private static BukkitAudiences adventure;
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static LanguageKeyHandler languageKeyHandler;
|
||||
private static OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static EnumHandler enumHandler;
|
||||
|
||||
private static OutputManager outputManager;
|
||||
private static ShareManager shareManager;
|
||||
private static StatCalculator statCalculator;
|
||||
private static ThreadManager threadManager;
|
||||
|
||||
private static PlayerStats playerStatsAPI;
|
||||
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
//initialize all the Managers, singletons, ConfigHandler and the API
|
||||
initializeMainClasses();
|
||||
setupMetrics();
|
||||
|
||||
//register all commands and the tabCompleter
|
||||
PluginCommand statcmd = this.getCommand("statistic");
|
||||
if (statcmd != null) {
|
||||
statcmd.setExecutor(new StatCommand(outputManager, threadManager));
|
||||
statcmd.setTabCompleter(new TabCompleter(enumHandler, offlinePlayerHandler));
|
||||
}
|
||||
PluginCommand reloadcmd = this.getCommand("statisticreload");
|
||||
if (reloadcmd != null) reloadcmd.setExecutor(new ReloadCommand(threadManager));
|
||||
PluginCommand sharecmd = this.getCommand("statisticshare");
|
||||
if (sharecmd != null) sharecmd.setExecutor(new ShareCommand(shareManager, outputManager));
|
||||
|
||||
//register the listener
|
||||
Bukkit.getPluginManager().registerEvents(new JoinListener(threadManager), this);
|
||||
|
||||
//finish up
|
||||
this.getLogger().info("Enabled PlayerStats!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (adventure != null) {
|
||||
adventure.close();
|
||||
adventure = null;
|
||||
}
|
||||
this.getLogger().info("Disabled PlayerStats!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Adventure's BukkitAudiences object
|
||||
* @throws IllegalStateException if PlayerStats is not enabled
|
||||
*/
|
||||
public static @NotNull BukkitAudiences getAdventure() throws IllegalStateException {
|
||||
if (adventure == null) {
|
||||
throw new IllegalStateException("Tried to access Adventure without PlayerStats being enabled!");
|
||||
}
|
||||
return adventure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PlayerStats' ConfigHandler
|
||||
* @throws IllegalStateException if PlayerStats is not enabled
|
||||
*/
|
||||
public static @NotNull ConfigHandler getConfigHandler() throws IllegalStateException {
|
||||
if (config == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public static @NotNull OfflinePlayerHandler getOfflinePlayerHandler() throws IllegalStateException {
|
||||
if (offlinePlayerHandler == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return offlinePlayerHandler;
|
||||
}
|
||||
|
||||
public static @NotNull LanguageKeyHandler getLanguageKeyHandler() {
|
||||
if (languageKeyHandler == null) {
|
||||
languageKeyHandler = new LanguageKeyHandler(instance);
|
||||
}
|
||||
return languageKeyHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the EnumHandler. If there is no EnumHandler, one will be created.
|
||||
* @return PlayerStat's EnumHandler
|
||||
*/
|
||||
public static @NotNull EnumHandler getEnumHandler() {
|
||||
if (enumHandler == null) {
|
||||
enumHandler = new EnumHandler();
|
||||
}
|
||||
return enumHandler;
|
||||
}
|
||||
|
||||
public static @NotNull StatCalculator getStatCalculator() throws IllegalStateException {
|
||||
if (statCalculator == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return statCalculator;
|
||||
}
|
||||
|
||||
public static @NotNull InternalFormatter getStatFormatter() throws IllegalStateException {
|
||||
if (outputManager == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return outputManager;
|
||||
}
|
||||
|
||||
public static @NotNull PlayerStats getPlayerStatsAPI() throws IllegalStateException {
|
||||
if (playerStatsAPI == null) {
|
||||
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
|
||||
}
|
||||
return playerStatsAPI;
|
||||
}
|
||||
|
||||
private void initializeMainClasses() {
|
||||
instance = this;
|
||||
adventure = BukkitAudiences.create(this);
|
||||
|
||||
config = new ConfigHandler(this);
|
||||
enumHandler = new EnumHandler();
|
||||
languageKeyHandler = new LanguageKeyHandler(instance);
|
||||
offlinePlayerHandler = new OfflinePlayerHandler();
|
||||
|
||||
shareManager = new ShareManager(config);
|
||||
statCalculator = new StatCalculator(offlinePlayerHandler);
|
||||
outputManager = new OutputManager(adventure, config, shareManager);
|
||||
threadManager = new ThreadManager(config, statCalculator, outputManager);
|
||||
|
||||
MessageBuilder apiMessageBuilder = MessageBuilder.defaultBuilder(config);
|
||||
playerStatsAPI = new PlayerStatsAPI(apiMessageBuilder, offlinePlayerHandler);
|
||||
}
|
||||
|
||||
private void setupMetrics() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Metrics metrics = new Metrics(instance, 15923);
|
||||
final boolean placeholderExpansionActive;
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
|
||||
PlaceholderExpansion expansion = PlaceholderAPIPlugin
|
||||
.getInstance()
|
||||
.getLocalExpansionManager()
|
||||
.getExpansion("playerstats");
|
||||
placeholderExpansionActive = expansion != null;
|
||||
} else {
|
||||
placeholderExpansionActive = false;
|
||||
}
|
||||
metrics.addCustomChart(new SimplePie("using_placeholder_expansion", () -> placeholderExpansionActive ? "yes" : "no"));
|
||||
}
|
||||
}.runTaskLaterAsynchronously(this, 200);
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats;
|
||||
package com.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.statistic.result.InternalStatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.InternalStatResult;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
@ -19,8 +18,12 @@ 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.*/
|
||||
/**
|
||||
* 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 boolean isEnabled;
|
||||
@ -58,8 +61,8 @@ public final class ShareManager {
|
||||
sharedResults = null;
|
||||
}
|
||||
if (config.allowStatSharing() && !config.useHoverText()) {
|
||||
MyLogger.logMsg("Stat-sharing does not work without hover-text enabled! " +
|
||||
"Enable hover-text, or disable stat-sharing to stop seeing this message.", true);
|
||||
MyLogger.logWarning("Stat-sharing does not work without hover-text enabled! " +
|
||||
"Enable hover-text, or disable stat-sharing to stop seeing this message.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,7 +79,7 @@ public final class ShareManager {
|
||||
InternalStatResult result = new InternalStatResult(playerName, statResult, ID);
|
||||
int shareCode = result.hashCode();
|
||||
statResultQueue.put(shareCode, result);
|
||||
MyLogger.logMsg("Saving statResults with no. " + ID, DebugLevel.MEDIUM);
|
||||
MyLogger.logMediumLevelMsg("Saving statResults with no. " + ID);
|
||||
return shareCode;
|
||||
}
|
||||
|
||||
@ -93,17 +96,20 @@ public final class ShareManager {
|
||||
return sharedResults.contains(shareCode);
|
||||
}
|
||||
|
||||
/** Takes a formattedValue from the internal ConcurrentHashmap,
|
||||
puts the current time in the shareTimeStamp (ConcurrentHashMap),
|
||||
puts the shareCode (int hashCode) in the sharedResults (ArrayBlockingQueue),
|
||||
and returns the formattedValue. If no formattedValue was found, returns null.*/
|
||||
/**
|
||||
* Takes a formattedComponent from the internal ConcurrentHashmap,
|
||||
* puts the current time in the shareTimeStamp (ConcurrentHashMap),
|
||||
* puts the shareCode (int hashCode) in the sharedResults (ArrayBlockingQueue),
|
||||
* and returns the formattedComponent. If no formattedComponent 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);
|
||||
MyLogger.logMediumLevelMsg("500 stat-results have been shared, " +
|
||||
"creating a new internal queue with the most recent 50 share-code-values and discarding the rest...");
|
||||
ArrayBlockingQueue<Integer> newQueue = new ArrayBlockingQueue<>(500);
|
||||
|
||||
synchronized (this) { //put the last 50 values in the new Queue
|
||||
@ -123,8 +129,10 @@ public final class ShareManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** If the given player already has more than x (in this case 25) StatResults saved,
|
||||
remove the oldest one.*/
|
||||
/**
|
||||
* 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<InternalStatResult> alreadySavedResults = statResultQueue.values()
|
||||
.parallelStream()
|
||||
@ -136,7 +144,7 @@ public final class ShareManager {
|
||||
.parallelStream()
|
||||
.min(Comparator.comparing(InternalStatResult::ID))
|
||||
.orElseThrow().hashCode();
|
||||
MyLogger.logMsg("Removing old stat no. " + statResultQueue.get(hashCode).ID() + " for player " + playerName, DebugLevel.MEDIUM);
|
||||
MyLogger.logMediumLevelMsg("Removing old stat no. " + statResultQueue.get(hashCode).ID() + " for player " + playerName);
|
||||
statResultQueue.remove(hashCode);
|
||||
}
|
||||
}
|
105
src/main/java/com/artemis/the/gr8/playerstats/ThreadManager.java
Normal file
105
src/main/java/com/artemis/the/gr8/playerstats/ThreadManager.java
Normal file
@ -0,0 +1,105 @@
|
||||
package com.artemis.the.gr8.playerstats;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* The ThreadManager is in charge of the Threads that PlayerStats
|
||||
* can utilize. It keeps track of past and currently active Threads,
|
||||
* to ensure a Player cannot start multiple Threads at the same time
|
||||
* (thereby limiting them to one stat-lookup at a time). It also
|
||||
* passes appropriate references along to the {@link StatThread}
|
||||
* or {@link ReloadThread}, to ensure those will never run at the
|
||||
* same time.
|
||||
*/
|
||||
public final class ThreadManager {
|
||||
|
||||
private final static int threshold = 10;
|
||||
private int statThreadID;
|
||||
private int reloadThreadID;
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static OutputManager outputManager;
|
||||
private static StatCalculator statCalculator;
|
||||
|
||||
private ReloadThread lastActiveReloadThread;
|
||||
private StatThread lastActiveStatThread;
|
||||
private final HashMap<String, Thread> statThreads;
|
||||
private static long lastRecordedCalcTime;
|
||||
|
||||
public ThreadManager(ConfigHandler config, StatCalculator statCalculator, OutputManager outputManager) {
|
||||
ThreadManager.config = config;
|
||||
ThreadManager.outputManager = outputManager;
|
||||
ThreadManager.statCalculator = statCalculator;
|
||||
|
||||
statThreads = new HashMap<>();
|
||||
statThreadID = 0;
|
||||
reloadThreadID = 0;
|
||||
lastRecordedCalcTime = 0;
|
||||
|
||||
startReloadThread(null);
|
||||
}
|
||||
|
||||
public static int getTaskThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public void startReloadThread(CommandSender sender) {
|
||||
if (lastActiveReloadThread == null || !lastActiveReloadThread.isAlive()) {
|
||||
reloadThreadID += 1;
|
||||
|
||||
lastActiveReloadThread = new ReloadThread(config, outputManager, reloadThreadID, lastActiveStatThread, sender);
|
||||
lastActiveReloadThread.start();
|
||||
}
|
||||
else {
|
||||
MyLogger.logLowLevelMsg("Another reloadThread is already running! (" + lastActiveReloadThread.getName() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
public void startStatThread(RequestSettings requestSettings) {
|
||||
statThreadID += 1;
|
||||
String cmdSender = requestSettings.getCommandSender().getName();
|
||||
|
||||
if (config.limitStatRequests() && statThreads.containsKey(cmdSender)) {
|
||||
Thread runningThread = statThreads.get(cmdSender);
|
||||
if (runningThread.isAlive()) {
|
||||
outputManager.sendFeedbackMsg(requestSettings.getCommandSender(), StandardMessage.REQUEST_ALREADY_RUNNING);
|
||||
} else {
|
||||
startNewStatThread(requestSettings);
|
||||
}
|
||||
} else {
|
||||
startNewStatThread(requestSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the duration in milliseconds of the last top-stat-lookup
|
||||
* (or of loading the offline-player-list if no look-ups have been done yet).
|
||||
*/
|
||||
public static void recordCalcTime(long time) {
|
||||
lastRecordedCalcTime = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration in milliseconds of the last top-stat-lookup
|
||||
* (or of loading the offline-player-list if no look-ups have been done yet).
|
||||
*/
|
||||
public static long getLastRecordedCalcTime() {
|
||||
return lastRecordedCalcTime;
|
||||
}
|
||||
|
||||
private void startNewStatThread(RequestSettings requestSettings) {
|
||||
lastActiveStatThread = new StatThread(outputManager, statCalculator, statThreadID, requestSettings, lastActiveReloadThread);
|
||||
statThreads.put(requestSettings.getCommandSender().getName(), lastActiveStatThread);
|
||||
lastActiveStatThread.start();
|
||||
}
|
||||
}
|
@ -0,0 +1,252 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.NumberFormatter;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.StatResult;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Statistic;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Formats messages meant for usage outside PlayerStats.
|
||||
* <p> For more information about the default formatting
|
||||
* PlayerStats uses, see the class description of
|
||||
* StatResult.
|
||||
|
||||
* @see StatResult
|
||||
*/
|
||||
public interface ApiFormatter {
|
||||
|
||||
/**
|
||||
* Turns a TextComponent into its String representation. This method is equipped
|
||||
* to turn all PlayerStats' formatted statResults into String, using a custom
|
||||
* Serializer.
|
||||
*
|
||||
* @param component the Component to turn 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link NumberFormatter} to format raw numbers into something more readable.
|
||||
*
|
||||
* @return the <code>NumberFormatter</code>
|
||||
*/
|
||||
default NumberFormatter getNumberFormatter() {
|
||||
return new NumberFormatter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default prefix PlayerStats uses.
|
||||
* @return [PlayerStats]
|
||||
*/
|
||||
TextComponent getPluginPrefix();
|
||||
|
||||
/**
|
||||
* Gets the special rainbow version of PlayerStats' prefix.
|
||||
* @return [PlayerStats] in rainbow color
|
||||
* s*/
|
||||
TextComponent getRainbowPluginPrefix();
|
||||
|
||||
/**
|
||||
* Gets the version of the prefix that is surrounded by underscores.
|
||||
* This is meant to be used as a title above a message or statistic display.
|
||||
* @return ________ [PlayerStats] ________
|
||||
*/
|
||||
TextComponent getPluginPrefixAsTitle();
|
||||
|
||||
/**
|
||||
* Gets the special rainbow version of the title-prefix.
|
||||
*
|
||||
* @return ________ [PlayerStats] ________ in rainbow colors
|
||||
*/
|
||||
TextComponent getRainbowPluginPrefixAsTitle();
|
||||
|
||||
/**
|
||||
* Gets a formatted message that displays the name of this Statistic as it is
|
||||
* displayed by PlayerStats. If this Statistic is not of Type.Untyped,
|
||||
* include the name of the relevant sub-statistic (block, item or entity).
|
||||
*
|
||||
* @param statistic the Statistic enum constant to display the name of
|
||||
* @param subStatName where necessary, the name of the Material or EntityType
|
||||
* to include, acquired by doing #toString() on the Material/EntityType in question
|
||||
* @return [stat-name] [sub-stat-name]
|
||||
*/
|
||||
TextComponent getStatTitle(Statistic statistic, @Nullable String subStatName);
|
||||
|
||||
/**
|
||||
* Gets a formatted message that displays the name of this Statistic as it is
|
||||
* displayed by PlayerStats in a top-stat-message. If this Statistic is not
|
||||
* of Type.Untyped, include the name of the relevant sub-statistic
|
||||
* (block, item or entity).
|
||||
*
|
||||
* @param statistic the Statistic enum constant for this message
|
||||
* @param subStatName the name of the Material or EntityType to include,
|
||||
* acquired by doing #toString() on the Material/EntityType in question
|
||||
* @param topStatSize the size of the top-list this title is for
|
||||
* @return Top [topStatSize] [stat-name] [sub-stat-name]
|
||||
*/
|
||||
TextComponent getTopStatTitle(int topStatSize, Statistic statistic, @Nullable String subStatName);
|
||||
|
||||
/**
|
||||
* Formats the input into a single top-statistic line. The stat-number
|
||||
* is formatted into the most suitable {@link Unit} based on the provided
|
||||
* Statistic. For Type.Time, the resulting formatted number will have as
|
||||
* many additional smaller units as are specified in the config,
|
||||
* unless <code>formatTopStatLineForTypeTime()</code> is used.
|
||||
*
|
||||
* @param positionInTopList the rank-number in this list of the Player
|
||||
* @param playerName the name of the Player on this line
|
||||
* @param statNumber the result of Player#getStatistic()
|
||||
* @param statistic the Statistic enum constant for this message
|
||||
* @return a single line from a top-x statistic:
|
||||
* <br> [positionInTopList]. [player-name] ......... [stat-number]
|
||||
*/
|
||||
TextComponent formatTopStatLine(int positionInTopList, String playerName, long statNumber, Statistic statistic);
|
||||
|
||||
/**
|
||||
* Formats the input into a single top-statistic line. The stat-number
|
||||
* is formatted into the provided {@link Unit}. For Type.Time, the
|
||||
* resulting formatted number will have as many additional smaller
|
||||
* units as are specified in the config, unless
|
||||
* <code>formatTopStatLineForTypeTime()</code> is used.
|
||||
*
|
||||
* @param positionInTopList the rank-number in this list of the Player
|
||||
* @param playerName the name of the Player on this line
|
||||
* @param statNumber the result of Player#getStatistic()
|
||||
* @param unit the Unit to format the <code>statNumber</code> with
|
||||
* @return a single line from a top-x statistic:
|
||||
* <br> [positionInTopList]. [player-name] ......... [stat-number]
|
||||
* */
|
||||
TextComponent formatTopStatLine(int positionInTopList, String playerName, long statNumber, Unit unit);
|
||||
|
||||
/**
|
||||
* Formats the input into a single top-statistic line for a time-based
|
||||
* statistic with the Unit-range that is between <code>bigUnit</code>
|
||||
* and <code>smallUnit</code> (both inclusive).
|
||||
*
|
||||
* @param positionInTopList the rank-number in this list of the Player
|
||||
* @param playerName the name of the Player on this line
|
||||
* @param statNumber the result of Player#getStatistic()
|
||||
* @param bigUnit the biggest Unit to use of {@link Unit.Type#TIME}
|
||||
* @param smallUnit the smallest Unit to use of {@link Unit.Type#TIME}
|
||||
* @return a single line from a stop-x statistic:
|
||||
* <br>[positionInTopList]. [player-name] ......... [1D 2H 3M 4S]
|
||||
*/
|
||||
TextComponent formatTopStatLineForTypeTime(int positionInTopList, String playerName, long statNumber, Unit bigUnit, Unit smallUnit);
|
||||
|
||||
/**
|
||||
* Formats the input into a server statistic message. The stat-number
|
||||
* is formatted into the most suitable {@link Unit} based on the provided
|
||||
* Statistic. For Type.Time, the resulting formatted number will have as
|
||||
* many additional smaller units as are specified in the config,
|
||||
* unless <code>formatServerStatForTypeTime()</code> is used.
|
||||
*
|
||||
* @param statNumber the result of all Player#getStatistic() values combined
|
||||
* @param statistic te Statistic enum constant for this message
|
||||
* @return [Total on this server]: [stat-number] [stat-name]
|
||||
*/
|
||||
TextComponent formatServerStat(long statNumber, Statistic statistic);
|
||||
|
||||
/**
|
||||
* Formats the input into a server statistic message for a statistic
|
||||
* that has a sub-statistic (block, item or entity).
|
||||
*
|
||||
* @param statistic the Statistic enum constant for this message
|
||||
* @param statNumber the result of all Player#getStatistic() values combined
|
||||
* @param subStatName the name of the Material or EntityType of this
|
||||
* statistic-lookup, acquired by doing #toString() on the Material/
|
||||
* EntityType in question
|
||||
* @return [Total on this server]: [stat-number] [stat-name] [sub-stat-name]
|
||||
*/
|
||||
TextComponent formatServerStat(long statNumber, Statistic statistic, String subStatName);
|
||||
|
||||
/**
|
||||
* Formats the input into a server statistic message with the specified
|
||||
* {@link Unit}. The stat-number is formatted into the most suitable
|
||||
* {@link Unit} based on the provided Statistic. For Type.Time, the
|
||||
* resulting formatted number will have as many additional smaller
|
||||
* units as are specified in the config, unless
|
||||
* <code>formatServerStatForTypeTime()</code> is used.
|
||||
*
|
||||
* @param statistic the Statistic enum constant for this message
|
||||
* @param statNumber the result of all Player#getStatistic() values combined
|
||||
* @param unit the Unit to use to format te <code>statNumber</code>
|
||||
* @return [Total on this server]: [stat-number] [stat-name] [unit-name]
|
||||
*/
|
||||
TextComponent formatServerStat(long statNumber, Statistic statistic, Unit unit);
|
||||
|
||||
/**
|
||||
* Formats the input into a server statistic message for a time-based
|
||||
* statistic with the Unit-range that is between <code>bigUnit</code>
|
||||
* and <code>smallUnit</code> (both inclusive).
|
||||
*
|
||||
* @param statistic the Statistic enum constant for this message
|
||||
* @param statNumber the result of all Player#getStatistic() values combined
|
||||
* @param bigUnit the biggest Unit to use of {@link Unit.Type#TIME}
|
||||
* @param smallUnit the smallest Unit to use of {@link Unit.Type#TIME}
|
||||
* @return [Total on this server]: [1D 2H 3M 4S] [stat-name]
|
||||
*/
|
||||
TextComponent formatServerStatForTypeTime(long statNumber, Statistic statistic, Unit bigUnit, Unit smallUnit);
|
||||
|
||||
/**
|
||||
* Formats the input into a player statistic message. For Unit.Type.Time,
|
||||
* the resulting formatted number will have as many additional smaller
|
||||
* units as are specified in the config, unless
|
||||
* <code>formatPlayerStatForTypeTime</code> is used.
|
||||
*
|
||||
* @param playerName the name of the Player
|
||||
* @param statistic the Statistic enum constant for this message
|
||||
* @param statNumber the result of Player#getStatistic()
|
||||
* @return [player-name]: [stat-number] [stat-name]
|
||||
*/
|
||||
TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic);
|
||||
|
||||
/**
|
||||
* Formats the input into a player statistic message for a statistic
|
||||
* that has a sub-statistic (block, item or entity).
|
||||
*
|
||||
* @param playerName the name of the Player
|
||||
* @param statistic the Statistic enum constant for this message
|
||||
* @param statNumber the result of Player#getStatistic()
|
||||
* @param subStatName the name of the Material or EntityType of
|
||||
* this statistic-lookup, acquired by doing #toString() on the
|
||||
* Material/EntityType in question
|
||||
* @return [player-name]: [stat-number] [stat-name] [sub-stat-name]
|
||||
*/
|
||||
TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic, String subStatName);
|
||||
|
||||
/**
|
||||
* Formats the input into a player statistic message with the specified
|
||||
* {@link Unit}. For Unit.Type.Time, the resulting formatted number will
|
||||
* have as many additional smaller units as are specified in the config,
|
||||
* unless <code>formatPlayerStatForTypeTime</code> is used.
|
||||
*
|
||||
* @param playerName the name of the Player
|
||||
* @param statistic the Statistic enum constant for this message
|
||||
* @param statNumber the result of Player#getStatistic()
|
||||
* @param unit the Unit to use when formatting the <code>statNumber</code>
|
||||
* @return [player-name]: [stat-number] [stat-name] [stat-unit]
|
||||
*/
|
||||
TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic, Unit unit);
|
||||
|
||||
/**
|
||||
* Formats the input into a player statistic message for a time-based
|
||||
* statistic with the Unit-range that is between <code>bigUnit</code>
|
||||
* and <code>smallUnit</code> (both inclusive).
|
||||
*
|
||||
* @param playerName the name of the Player
|
||||
* @param statNumber the result of Player#getStatistic()
|
||||
* @param statistic the Statistic enum constant for this message
|
||||
* @param bigUnit the biggest Unit to use of {@link Unit.Type#TIME}
|
||||
* @param smallUnit the smallest Unit to use of {@link Unit.Type#TIME}
|
||||
* @return [player-name]: [1D 2H 3M 4S] [stat-name]
|
||||
*/
|
||||
TextComponent formatPlayerStatForTypeTime(String playerName, int statNumber, Statistic statistic, Unit bigUnit, Unit smallUnit);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The outgoing API that represents the core functionality of PlayerStats!
|
||||
*
|
||||
* <p> To work with it, you'll need to call PlayerStats.{@link #getAPI()} and get an instance of
|
||||
* {@link PlayerStatsAPI}. You can then use this object to access any of the further methods.
|
||||
*
|
||||
* <p> Since calculating a top or server statistics can take some time, I strongly
|
||||
* encourage you to call {@link StatRequest#execute()} asynchronously.
|
||||
* Otherwise, the main Thread will have to wait until all calculations are done,
|
||||
* and this can severely impact server performance.
|
||||
*
|
||||
* @see StatManager
|
||||
* @see ApiFormatter
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
StatManager getStatManager();
|
||||
|
||||
ApiFormatter getFormatter();
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.*;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
|
||||
import static org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
/** The implementation of the API Interface */
|
||||
public final class PlayerStatsAPI implements PlayerStats, StatManager {
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static ApiFormatter apiFormatter;
|
||||
|
||||
@Internal
|
||||
public PlayerStatsAPI(ApiFormatter formatter, OfflinePlayerHandler offlinePlayers) {
|
||||
apiFormatter = formatter;
|
||||
offlinePlayerHandler = offlinePlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiFormatter getFormatter() {
|
||||
return apiFormatter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatManager getStatManager() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatRequest playerStatRequest(String playerName) {
|
||||
RequestSettings request = RequestHandler.getBasicPlayerStatRequest(playerName);
|
||||
return new PlayerStatRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest serverStatRequest() {
|
||||
RequestSettings request = RequestHandler.getBasicServerStatRequest();
|
||||
return new ServerStatRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest topStatRequest(int topListSize) {
|
||||
RequestSettings request = RequestHandler.getBasicTopStatRequest(topListSize);
|
||||
return new TopStatRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest totalTopStatRequest() {
|
||||
int playerCount = offlinePlayerHandler.getOfflinePlayerCount();
|
||||
return topStatRequest(playerCount);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Creates an executable {@link StatRequest}. This Request holds all
|
||||
* the information PlayerStats needs to work with, and is used by the {@link StatCalculator}
|
||||
* to get the desired statistic data.
|
||||
*/
|
||||
public interface RequestGenerator<T> {
|
||||
|
||||
/**
|
||||
* Gets an executable Request object 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<T> untyped(@NotNull Statistic statistic) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Gets an executable Request object 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<T> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException;
|
||||
|
||||
/** Gets an executable Request object 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<T> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException;
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* Turns user input into a {@link StatRequest} that can be used to get statistic data
|
||||
*/
|
||||
public interface StatManager {
|
||||
|
||||
/** Gets a RequestGenerator that can be used to create a PlayerStatRequest.
|
||||
* This RequestGenerator will make sure all default settings
|
||||
* for a player-statistic-lookup are configured.
|
||||
*
|
||||
* @param playerName the player whose statistic is being requested
|
||||
* @return the RequestGenerator */
|
||||
RequestGenerator<Integer> playerStatRequest(String playerName);
|
||||
|
||||
/** Gets a RequestGenerator that can be used to create a ServerStatRequest.
|
||||
* This RequestGenerator will make sure all default settings
|
||||
* for a server-statistic-lookup are configured.
|
||||
*
|
||||
* @return the RequestGenerator*/
|
||||
RequestGenerator<Long> serverStatRequest();
|
||||
|
||||
/** Gets a RequestGenerator that can be used to create a TopStatRequest
|
||||
* for a top-list of the specified size. This RequestGenerator will
|
||||
* make sure all default settings for a top-statistic-lookup are configured.
|
||||
*
|
||||
* @param topListSize how big the top-x should be (10 by default)
|
||||
* @return the RequestGenerator*/
|
||||
RequestGenerator<LinkedHashMap<String, Integer>> topStatRequest(int topListSize);
|
||||
|
||||
/** Gets a RequestGenerator that can be used to create a TopStatRequest
|
||||
* for all offline players on the server (those that are included by
|
||||
* PlayerStats' settings). This RequestGenerator will make sure
|
||||
* all default settings for a top-statistic-lookup are configured.
|
||||
*
|
||||
* @return the RequestGenerator*/
|
||||
RequestGenerator<LinkedHashMap<String, Integer>> totalTopStatRequest();
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The PlayerStats API
|
||||
*/
|
||||
package com.artemis.the.gr8.playerstats.api;
|
@ -1,6 +1,6 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.commands;
|
||||
package com.artemis.the.gr8.playerstats.commands;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
@ -1,10 +1,10 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.commands;
|
||||
package com.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.statistic.result.InternalStatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.ShareManager;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.InternalStatResult;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@ -38,7 +38,7 @@ public final class ShareCommand implements CommandExecutor {
|
||||
}
|
||||
else {
|
||||
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
|
||||
if (result == null) { //at this point the only possible cause of formattedComponent being null is the request being older than 25 player-requests ago
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.STAT_RESULTS_TOO_OLD);
|
||||
} else {
|
||||
outputManager.sendToAllPlayers(result.formattedValue());
|
@ -1,11 +1,11 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.commands;
|
||||
package com.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.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
@ -32,10 +32,10 @@ public final class StatCommand implements CommandExecutor {
|
||||
outputManager.sendExamples(sender);
|
||||
}
|
||||
else {
|
||||
StatRequest baseRequest = StatRequestHandler.getBasicInternalStatRequest(sender);
|
||||
StatRequestHandler statRequestHandler = new StatRequestHandler(baseRequest);
|
||||
RequestSettings baseRequest = RequestHandler.getBasicInternalStatRequest(sender);
|
||||
RequestHandler requestHandler = new RequestHandler(baseRequest);
|
||||
|
||||
StatRequest completedRequest = statRequestHandler.getRequestFromArgs(args);
|
||||
RequestSettings completedRequest = requestHandler.getRequestFromArgs(args);
|
||||
if (completedRequest.isValid()) {
|
||||
threadManager.startStatThread(completedRequest);
|
||||
} else {
|
||||
@ -46,31 +46,33 @@ public final class StatCommand implements CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 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
|
||||
/**
|
||||
* If a given {@link RequestSettings} object does not result in a valid
|
||||
* statistic look-up, this will send a feedback message to the CommandSender
|
||||
* that made the request. The following is checked:
|
||||
* <ul>
|
||||
* <li>Is a <code>statistic</code> set?
|
||||
* <li>Is a <code>subStatEntry</code> needed, and if so, is a corresponding Material/EntityType present?
|
||||
* <li>If the <code>target</code> is Player, is a valid <code>playerName</code> provided?
|
||||
* </ul>
|
||||
*
|
||||
* @param requestSettings the RequestSettings to give feedback on
|
||||
*/
|
||||
private void sendFeedback(StatRequest statRequest) {
|
||||
CommandSender sender = statRequest.getCommandSender();
|
||||
private void sendFeedback(RequestSettings requestSettings) {
|
||||
CommandSender sender = requestSettings.getCommandSender();
|
||||
|
||||
if (statRequest.getStatistic() == null) {
|
||||
if (requestSettings.getStatistic() == null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_STAT_NAME);
|
||||
}
|
||||
else if (statRequest.getTarget() == Target.PLAYER && statRequest.getPlayerName() == null) {
|
||||
else if (requestSettings.getTarget() == Target.PLAYER && requestSettings.getPlayerName() == null) {
|
||||
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_PLAYER_NAME);
|
||||
}
|
||||
else {
|
||||
Statistic.Type type = statRequest.getStatistic().getType();
|
||||
if (type != Statistic.Type.UNTYPED && statRequest.getSubStatEntryName() == null) {
|
||||
Statistic.Type type = requestSettings.getStatistic().getType();
|
||||
if (type != Statistic.Type.UNTYPED && requestSettings.getSubStatEntryName() == null) {
|
||||
outputManager.sendFeedbackMsgMissingSubStat(sender, type);
|
||||
} else {
|
||||
outputManager.sendFeedbackMsgWrongSubStat(sender, type, statRequest.getSubStatEntryName());
|
||||
outputManager.sendFeedbackMsgWrongSubStat(sender, type, requestSettings.getSubStatEntryName());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.commands;
|
||||
package com.artemis.the.gr8.playerstats.commands;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.commands.cmdutils.TabCompleteHelper;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.commands.cmdutils.TabCompleteHelper;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
@ -1,6 +1,6 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.commands.cmdutils;
|
||||
package com.artemis.the.gr8.playerstats.commands.cmdutils;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
@ -0,0 +1,586 @@
|
||||
package com.artemis.the.gr8.playerstats.config;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/** Handles all PlayerStats' config-settings. */
|
||||
public final class ConfigHandler {
|
||||
|
||||
private static Main plugin;
|
||||
private static int configVersion;
|
||||
|
||||
private File configFile;
|
||||
private FileConfiguration config;
|
||||
|
||||
public ConfigHandler(Main plugin) {
|
||||
ConfigHandler.plugin = plugin;
|
||||
configVersion = 6;
|
||||
|
||||
saveDefaultConfig();
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
checkConfigVersion();
|
||||
|
||||
MyLogger.setDebugLevel(getDebugLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>PlayerStats 1.1: "config-version" doesn't exist.
|
||||
* <br>PlayerStats 1.2: "config-version" is 2.
|
||||
* <br>PlayerStats 1.3: "config-version" is 3.
|
||||
* <br>PlayerStats 1.4: "config-version" is 4.
|
||||
* <br>PlayerStats 1.5: "config-version" is 5.
|
||||
* <br>PlayerStats 1.6 and up: "config-version" is 6.
|
||||
*/
|
||||
private void checkConfigVersion() {
|
||||
if (!config.contains("config-version") || config.getInt("config-version") != configVersion) {
|
||||
new ConfigUpdateHandler(plugin, configFile, configVersion);
|
||||
reloadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a config file if none exists yet
|
||||
* (from the config.yml in the plugin's resources).
|
||||
*/
|
||||
private void saveDefaultConfig() {
|
||||
config = plugin.getConfig();
|
||||
plugin.saveDefaultConfig();
|
||||
configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the config from file, or creates a new file with default values
|
||||
* if there is none. Also reads the value for debug-level and passes it
|
||||
* on to {@link MyLogger}.
|
||||
*
|
||||
* @return true if the config has been reloaded from disk, false if it failed
|
||||
*/
|
||||
public boolean reloadConfig() {
|
||||
if (!configFile.exists()) {
|
||||
saveDefaultConfig();
|
||||
}
|
||||
try {
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
return true;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
MyLogger.logException(e, "ConfigHandler", "reloadConfig");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the desired debugging level.
|
||||
*
|
||||
* <br> 1 = low (only show unexpected errors)
|
||||
* <br> 2 = medium (detail all encountered exceptions, log main tasks and show time taken)
|
||||
* <br> 3 = high (log all tasks and time taken)
|
||||
*
|
||||
* @return the DebugLevel (default: 1)
|
||||
*/
|
||||
public int getDebugLevel() {
|
||||
return config.getInt("debug-level", 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether command-senders should be limited to one stat-request at a time.
|
||||
* @return the config setting (default: true)
|
||||
*/
|
||||
public boolean limitStatRequests() {
|
||||
return config.getBoolean("only-allow-one-lookup-at-a-time-per-player", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether stat-sharing is allowed.
|
||||
* @return the config setting (default: true)
|
||||
*/
|
||||
public boolean allowStatSharing() {
|
||||
return config.getBoolean("enable-stat-sharing", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of minutes a player has to wait before being able to
|
||||
* share another stat-result.
|
||||
* @return the number (default: 0)
|
||||
*/
|
||||
public int getStatShareWaitingTime() {
|
||||
return config.getInt("waiting-time-before-sharing-again", 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to limit stat-calculations to whitelisted players only.
|
||||
* @return the config setting (default: true)
|
||||
*/
|
||||
public boolean whitelistOnly() {
|
||||
return config.getBoolean("include-whitelist-only", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to exclude banned players from stat-calculations.
|
||||
* @return the config setting for exclude-banned-players (default: false)
|
||||
*/
|
||||
public boolean excludeBanned() {
|
||||
return config.getBoolean("exclude-banned-players", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of maximum days since a player has last been online.
|
||||
* @return the number (default: 0 - which signals not to use this limit)
|
||||
*/
|
||||
public int getLastPlayedLimit() {
|
||||
return config.getInt("number-of-days-since-last-joined", 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use TranslatableComponents wherever possible.
|
||||
*
|
||||
* @return the config setting (default: true)
|
||||
* @implNote Currently supported: statistic, block, item and entity names.
|
||||
*/
|
||||
public boolean useTranslatableComponents() {
|
||||
return config.getBoolean("translate-to-client-language", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use HoverComponents for additional information
|
||||
* @return the config setting (default: true)
|
||||
*/
|
||||
public boolean useHoverText() {
|
||||
return config.getBoolean("enable-hover-text", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use festive formatting, such as pride colors
|
||||
* @return the config setting (default: true)
|
||||
*/
|
||||
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
|
||||
* @return the config setting (default: false)
|
||||
*/
|
||||
public boolean useRainbowMode() {
|
||||
return config.getBoolean("rainbow-mode", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use enters before the statistic output in chat
|
||||
*
|
||||
* @param selection the Target (Player, Server or Top)
|
||||
* @return the config setting (default: true for non-shared top
|
||||
* statistics, false for everything else)
|
||||
*/
|
||||
public boolean useEnters(Target selection, boolean getSharedSetting) {
|
||||
ConfigurationSection section = config.getConfigurationSection("use-enters");
|
||||
boolean def = selection == Target.TOP && !getSharedSetting;
|
||||
if (section != null) {
|
||||
String path = switch (selection) {
|
||||
case TOP -> getSharedSetting ? "top-stats-shared" : "top-stats";
|
||||
case PLAYER -> getSharedSetting ? "player-stats-shared" : "player-stats";
|
||||
case SERVER -> getSharedSetting ? "server-stats-shared" : "server-stats";
|
||||
};
|
||||
return section.getBoolean(path, def);
|
||||
}
|
||||
MyLogger.logWarning("Config settings for use-enters could not be retrieved! " +
|
||||
"Please check your file if you want to use custom settings. " +
|
||||
"Using default values...");
|
||||
return def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether dots should be used to align the numbers in a top-stat-result.
|
||||
* @return the config setting (default: true)
|
||||
*/
|
||||
public boolean useDots() {
|
||||
return config.getBoolean("use-dots", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum size for the top-stat-list.
|
||||
* @return the config setting (default: 10)
|
||||
*/
|
||||
public int getTopListMaxSize() {
|
||||
return config.getInt("top-list-max-size", 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* The title that a top-statistic should start with.
|
||||
* @return a String that represents the title for a top statistic
|
||||
* (default: "Top")
|
||||
*/
|
||||
public String getTopStatsTitle() {
|
||||
return config.getString("top-list-title", "Top");
|
||||
}
|
||||
|
||||
/**
|
||||
* The title that a server statistic should start with.
|
||||
* @return the title (default: "Total on")
|
||||
*/
|
||||
public String getServerTitle() {
|
||||
return config.getString("total-server-stat-title", "Total on");
|
||||
}
|
||||
|
||||
/**
|
||||
* The specified server name for a server stat title.
|
||||
* @return the title (default: "this server")
|
||||
*/
|
||||
public String getServerName() {
|
||||
return config.getString("your-server-name", "this server");
|
||||
}
|
||||
|
||||
/**
|
||||
* The unit that should be used for distance-related statistics.
|
||||
*
|
||||
* @param isUnitForHoverText whether the number formatted with this
|
||||
* Unit is inside a HoverComponent
|
||||
* @return the Unit (default: Blocks for plain text, km for hover-text)
|
||||
*/
|
||||
public String getDistanceUnit(boolean isUnitForHoverText) {
|
||||
return getUnitString(isUnitForHoverText, "blocks", "km", "distance-unit");
|
||||
}
|
||||
|
||||
/**
|
||||
* The unit that should be used for damage-based statistics.
|
||||
*
|
||||
* @param isUnitForHoverText whether the number formatted with this
|
||||
* Unit is inside a HoverComponent
|
||||
* @return the Unit (default: Hearts for plain text, HP for hover-text)
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param isUnitForHoverText whether the number formatted with this
|
||||
* Unit is inside a HoverComponent
|
||||
* @return the config setting (default: true)
|
||||
*/
|
||||
public boolean autoDetectTimeUnit(boolean isUnitForHoverText) {
|
||||
String path = "auto-detect-biggest-time-unit";
|
||||
if (isUnitForHoverText) {
|
||||
path = path + "-for-hover-text";
|
||||
}
|
||||
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
|
||||
*
|
||||
* @param isUnitForHoverText whether the number formatted with this
|
||||
* Unit is inside a HoverComponent
|
||||
* @return the config setting (default: 1 for plain text,
|
||||
* 0 for hover-text)
|
||||
*/
|
||||
public int getNumberOfExtraTimeUnits(boolean isUnitForHoverText) {
|
||||
String path = "number-of-extra-units";
|
||||
if (isUnitForHoverText) {
|
||||
path = path + "-for-hover-text";
|
||||
}
|
||||
int defaultValue = isUnitForHoverText ? 0 : 1;
|
||||
return config.getInt(path, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The largest unit that should be used for time-based statistics.
|
||||
*
|
||||
* @param isUnitForHoverText whether the number formatted with this
|
||||
* Unit is inside a HoverComponent
|
||||
* @return a String representation of the largest time-unit
|
||||
* (default: days for plain text, hours for hover-text)
|
||||
*/
|
||||
public String getTimeUnit(boolean isUnitForHoverText) {
|
||||
return getTimeUnit(isUnitForHoverText, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 biggest).
|
||||
*
|
||||
* @param isUnitForHoverText whether the number formatted with this
|
||||
* Unit is inside a HoverComponent
|
||||
* @param smallUnit if this is true, get the minimum time-unit
|
||||
* @return the Unit (default: hours for plain text, seconds for hover-text)
|
||||
*/
|
||||
public String getTimeUnit(boolean isUnitForHoverText, boolean smallUnit) {
|
||||
if (smallUnit) {
|
||||
return getUnitString(isUnitForHoverText, "hours", "seconds", "smallest-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.
|
||||
* @return an {@code int} that represents a percentage (default: 20)
|
||||
*/
|
||||
public int getHoverTextAmountLighter() {
|
||||
return config.getInt("hover-text-amount-lighter", 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code,
|
||||
* or a Style.
|
||||
*
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "italic"
|
||||
* <br>Color: "gray"
|
||||
*/
|
||||
public String getSharedByTextDecoration(boolean getStyleSetting) {
|
||||
String def = getStyleSetting ? "italic" : "gray";
|
||||
return getDecorationString(null, getStyleSetting, def, "shared-by");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code, or a Style.
|
||||
*
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color: "#845EC2"
|
||||
*/
|
||||
public String getSharerNameDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(null, getStyleSetting, "#845EC2", "player-name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code, or a Style.
|
||||
*
|
||||
* @param selection the Target (Player, Server or Top)
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color Top: "green"
|
||||
* <br>Color Individual/Server: "gold"
|
||||
*/
|
||||
public String getPlayerNameDecoration(Target selection, boolean getStyleSetting) {
|
||||
String def;
|
||||
if (selection == Target.TOP) {
|
||||
def = "green";
|
||||
}
|
||||
else {
|
||||
def = "gold";
|
||||
}
|
||||
return getDecorationString(selection, getStyleSetting, def, "player-names");
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the playerNames Style is "bold" for a top-stat.
|
||||
* @return the config setting (default: false)
|
||||
*/
|
||||
public boolean playerNameIsBold() {
|
||||
ConfigurationSection style = getRelevantSection(Target.TOP);
|
||||
|
||||
if (style != null) {
|
||||
String styleString = style.getString("player-names");
|
||||
return styleString != null && styleString.equalsIgnoreCase("bold");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code,
|
||||
* or a Style.
|
||||
*
|
||||
* @param selection the Target (Player, Server or Top)
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color: "yellow"
|
||||
*/
|
||||
public String getStatNameDecoration(Target selection, boolean getStyleSetting) {
|
||||
return getDecorationString(selection, getStyleSetting, "yellow", "stat-names");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code,
|
||||
* or a Style.
|
||||
*
|
||||
* @param selection the Target (Player, Server or Top)
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color: "#FFD52B"
|
||||
*/
|
||||
public String getSubStatNameDecoration(Target selection, boolean getStyleSetting) {
|
||||
return getDecorationString(selection, getStyleSetting, "#FFD52B", "sub-stat-names");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code,
|
||||
* or Style.
|
||||
*
|
||||
* @param selection the Target (Player, Server or Top)
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color Top: "#55AAFF"
|
||||
* <br>Color Individual/Server: "#ADE7FF"
|
||||
*/
|
||||
public String getStatNumberDecoration(Target selection, boolean getStyleSetting) {
|
||||
String def;
|
||||
if (selection == Target.TOP) {
|
||||
def = "#55AAFF";
|
||||
}
|
||||
else {
|
||||
def = "#ADE7FF";
|
||||
}
|
||||
return getDecorationString(selection, getStyleSetting, def,"stat-numbers");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code,
|
||||
* or Style.
|
||||
*
|
||||
* @param selection the Target (Player, Server or Top)
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color Top: "yellow"
|
||||
* <br>Color Server: "gold"
|
||||
*/
|
||||
public String getTitleDecoration(Target selection, boolean getStyleSetting) {
|
||||
String def;
|
||||
if (selection == Target.TOP) {
|
||||
def = "yellow";
|
||||
}
|
||||
else {
|
||||
def = "gold";
|
||||
}
|
||||
return getDecorationString(selection, getStyleSetting, def, "title");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code,
|
||||
* or Style.
|
||||
*
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color: "gold"
|
||||
*/
|
||||
public String getTitleNumberDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(Target.TOP, getStyleSetting, "gold", "title-number");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code,
|
||||
* or Style.
|
||||
*
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color: "#FFB80E"
|
||||
*/
|
||||
public String getServerNameDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(Target.SERVER, getStyleSetting, "#FFB80E", "server-name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code,
|
||||
* or Style.
|
||||
*
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color: "gold"
|
||||
*/
|
||||
public String getRankNumberDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(Target.TOP, getStyleSetting, "gold", "rank-numbers");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String that represents either a Chat Color, hex color code,
|
||||
* or Style.
|
||||
*
|
||||
* @param getStyleSetting if true, returns a Style instead of a Color
|
||||
* @return the config setting. Default:
|
||||
* <br>Style: "none"
|
||||
* <br>Color: "dark_gray"
|
||||
*/
|
||||
public String getDotsDecoration(boolean getStyleSetting) {
|
||||
return getDecorationString(Target.TOP, getStyleSetting, "dark_gray", "dots");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a String representing a {@link Unit}.
|
||||
*
|
||||
* @return 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
|
||||
* @param defaultValue the default unit for plain text
|
||||
* @param defaultHoverValue the default unit for hovering text
|
||||
* @param pathName the config path to retrieve the value from
|
||||
*/
|
||||
private String getUnitString(boolean isHoverText, String defaultValue, String defaultHoverValue, String pathName) {
|
||||
String path = isHoverText ? pathName + "-for-hover-text" : pathName;
|
||||
String def = defaultValue;
|
||||
if (isHoverText && defaultHoverValue != null) {
|
||||
def = defaultHoverValue;
|
||||
}
|
||||
return config.getString(path, def);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the config value for a color or style option in string-format,
|
||||
* the supplied default value, or null if no configSection was found.
|
||||
* @param selection the Target this decoration is meant for (Player, Server or Top)
|
||||
* @param getStyleSetting if true, the result will be a style String,
|
||||
* otherwise a color String
|
||||
* @param defaultColor the default color to return if the config value cannot be found
|
||||
* (for style, the default is always "none")
|
||||
* @param pathName the config path to retrieve the value from
|
||||
*/
|
||||
private @Nullable String getDecorationString(Target selection, boolean getStyleSetting, String defaultColor, String pathName){
|
||||
String path = getStyleSetting ? pathName + "-style" : pathName;
|
||||
String defaultValue = getStyleSetting ? "none" : defaultColor;
|
||||
|
||||
ConfigurationSection section = getRelevantSection(selection);
|
||||
return section != null ? section.getString(path, defaultValue) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the config section that contains the relevant color or style option.
|
||||
*/
|
||||
private @Nullable ConfigurationSection getRelevantSection(Target selection) {
|
||||
if (selection == null) { //rather than rework the whole Target enum, I have added shared-stats as the null-option for now
|
||||
return config.getConfigurationSection("shared-stats");
|
||||
}
|
||||
switch (selection) {
|
||||
case TOP -> {
|
||||
return config.getConfigurationSection("top-list");
|
||||
}
|
||||
case PLAYER -> {
|
||||
return config.getConfigurationSection("individual-statistics");
|
||||
}
|
||||
case SERVER -> {
|
||||
return config.getConfigurationSection("total-server");
|
||||
}
|
||||
default -> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.config;
|
||||
package com.artemis.the.gr8.playerstats.config;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
@ -11,7 +11,10 @@ import com.tchristofferson.configupdater.ConfigUpdater;
|
||||
|
||||
public final class ConfigUpdateHandler {
|
||||
|
||||
/** Add new key-value pairs to the config without losing comments, using <a href="https://github.com/tchristofferson/Config-Updater">tchristofferson's Config-Updater</a> */
|
||||
/**
|
||||
* Add new key-value pairs to the config without losing comments,
|
||||
* using <a href="https://github.com/tchristofferson/Config-Updater">tchristofferson's Config-Updater</a>
|
||||
*/
|
||||
public ConfigUpdateHandler(Main plugin, File configFile, int configVersion) {
|
||||
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configFile);
|
||||
updateTopListDefault(configuration);
|
||||
@ -20,14 +23,17 @@ public final class ConfigUpdateHandler {
|
||||
try {
|
||||
configuration.save(configFile);
|
||||
ConfigUpdater.update(plugin, configFile.getName(), configFile);
|
||||
MyLogger.logMsg("Your config has been updated to version " + configVersion +
|
||||
MyLogger.logLowLevelMsg("Your config has been updated to version " + configVersion +
|
||||
", but all of your custom settings should still be there!");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** Adjusts the value for "top-list" to migrate the config file from versions 1 or 2 to version 3 and above.*/
|
||||
/**
|
||||
* Adjusts the value for "top-list" to migrate the config file from
|
||||
* versions 1 or 2 to version 3 and above.
|
||||
*/
|
||||
private void updateTopListDefault(YamlConfiguration configuration) {
|
||||
String oldTitle = configuration.getString("top-list-title");
|
||||
if (oldTitle != null && oldTitle.equalsIgnoreCase("Top [x]")) {
|
||||
@ -35,7 +41,10 @@ public final class ConfigUpdateHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/** Adjusts some of the default colors to migrate from versions 2 or 3 to version 4 and above.*/
|
||||
/**
|
||||
* Adjusts some of the default colors to migrate from versions 2
|
||||
* or 3 to version 4 and above.
|
||||
*/
|
||||
private void updateDefaultColors(YamlConfiguration configuration) {
|
||||
updateColor(configuration, "top-list.title", "yellow", "#FFD52B");
|
||||
updateColor(configuration, "top-list.title", "#FFEA40", "#FFD52B");
|
@ -0,0 +1,12 @@
|
||||
package com.artemis.the.gr8.playerstats.enums;
|
||||
|
||||
/**
|
||||
* Represents the debugging level that PlayerStats can use.
|
||||
* <br>
|
||||
* <br>1 = low (only show unexpected errors)
|
||||
* <br>2 = medium (detail all encountered exceptions, log main tasks and show time taken)
|
||||
* <br>3 = high (log all tasks and time taken)
|
||||
*/
|
||||
public enum DebugLevel {
|
||||
LOW, MEDIUM, HIGH
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
package com.artemis.the.gr8.playerstats.enums;
|
||||
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This enum represents the colorscheme PlayerStats uses in its output messages.
|
||||
* The first set of colors is used throughout the plugin, while the set of NAME-colors
|
||||
* represents the colors that player-names can be in the "shared by player-name"
|
||||
* section of shared statistics
|
||||
*/
|
||||
public enum PluginColor {
|
||||
/**
|
||||
* ChatColor Gray (#AAAAAA)
|
||||
*/
|
||||
GRAY (NamedTextColor.GRAY),
|
||||
|
||||
/**
|
||||
* A Dark Purple that is mainly used for title-underscores (#6E3485).
|
||||
*/
|
||||
DARK_PURPLE (TextColor.fromHexString("#6E3485")),
|
||||
|
||||
/**
|
||||
* A Light Purple that is meant to simulate the color of a clicked link.
|
||||
* Used for the "Hover Here" part of shared statistics (#845EC2)
|
||||
* */
|
||||
LIGHT_PURPLE (TextColor.fromHexString("#845EC2")),
|
||||
|
||||
/**
|
||||
* ChatColor Blue (#5555FF)
|
||||
*/
|
||||
BLUE (NamedTextColor.BLUE),
|
||||
|
||||
/**
|
||||
* A Medium Blue that is used for default feedback and error messages (#55AAFF).
|
||||
*/
|
||||
MEDIUM_BLUE (TextColor.fromHexString("#55AAFF")),
|
||||
|
||||
/**
|
||||
* A Light Blue that is used for hover-messages and the share-button (#55C6FF).
|
||||
*/
|
||||
LIGHT_BLUE (TextColor.fromHexString("#55C6FF")),
|
||||
|
||||
/**
|
||||
* ChatColor Gold (#FFAA00)
|
||||
*/
|
||||
GOLD (NamedTextColor.GOLD),
|
||||
|
||||
/**
|
||||
* A Medium Gold that is used for the example message and for hover-text accents (#FFD52B).
|
||||
*/
|
||||
MEDIUM_GOLD (TextColor.fromHexString("#FFD52B")),
|
||||
|
||||
/**
|
||||
* A Light Gold that is used for the example message and for hover-text accents (#FFEA40).
|
||||
*/
|
||||
LIGHT_GOLD (TextColor.fromHexString("#FFEA40")),
|
||||
|
||||
/**
|
||||
* A Light Yellow that is used for final accents in the example message (#FFFF8E).
|
||||
*/
|
||||
LIGHT_YELLOW (TextColor.fromHexString("#FFFF8E")),
|
||||
|
||||
/**
|
||||
* The color of vanilla Minecraft hearts (#FF1313).
|
||||
*/
|
||||
RED (TextColor.fromHexString("#FF1313")),
|
||||
|
||||
/**
|
||||
* ChatColor Blue (#5555FF)
|
||||
*/
|
||||
NAME_1 (NamedTextColor.BLUE), //#5555FF - blue
|
||||
|
||||
/**
|
||||
* A shade of blue between Blue and Medium Blue (#4287F5)
|
||||
*/
|
||||
NAME_2 (TextColor.fromHexString("#4287F5")),
|
||||
|
||||
/**
|
||||
* Medium Blue (#55AAFF)
|
||||
*/
|
||||
NAME_3 (TextColor.fromHexString("#55AAFF")),
|
||||
|
||||
/**
|
||||
* A shade of magenta/purple (#D65DB1)
|
||||
*/
|
||||
NAME_4 (TextColor.fromHexString("#D65DB1")),
|
||||
|
||||
/**
|
||||
* A dark shade of orange (#EE8A19)
|
||||
*/
|
||||
NAME_5 (TextColor.fromHexString("#EE8A19")),
|
||||
|
||||
/**
|
||||
* A shade of green/aqua/cyan-ish (#01C1A7)
|
||||
*/
|
||||
NAME_6 (TextColor.fromHexString("#01C1A7")),
|
||||
|
||||
/**
|
||||
* A light shade of green (#46D858)
|
||||
*/
|
||||
NAME_7 (TextColor.fromHexString("#46D858"));
|
||||
|
||||
|
||||
private final TextColor color;
|
||||
|
||||
PluginColor(TextColor color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TextColor value belonging to the corresponding enum constant.
|
||||
*/
|
||||
public TextColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest NamedTextColor for the corresponding enum constant.
|
||||
*/
|
||||
public TextColor getConsoleColor() {
|
||||
return NamedTextColor.nearestTo(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly selects one of the 7 different NAME-colors.
|
||||
*/
|
||||
public static TextColor getRandomNameColor() {
|
||||
return getRandomNameColor(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly selects one of the 7 different NAME-colors, and if isConsole is true,
|
||||
* returns the closest NamedTextColor
|
||||
*/
|
||||
public static TextColor getRandomNameColor(boolean isConsole) {
|
||||
Random randomizer = new Random();
|
||||
PluginColor color = switch (randomizer.nextInt(7)) {
|
||||
case 0 -> NAME_1;
|
||||
case 2 -> NAME_3;
|
||||
case 3 -> NAME_4;
|
||||
case 4 -> NAME_5;
|
||||
case 5 -> NAME_6;
|
||||
case 6 -> NAME_7;
|
||||
default -> NAME_2;
|
||||
};
|
||||
return getCorrespondingColor(color, isConsole);
|
||||
}
|
||||
|
||||
private static TextColor getCorrespondingColor(PluginColor nameColor, boolean isConsole) {
|
||||
return isConsole ? nameColor.getConsoleColor() : nameColor.getColor();
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.enums;
|
||||
package com.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.*/
|
||||
/**
|
||||
* 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,
|
@ -0,0 +1,9 @@
|
||||
package com.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
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.enums;
|
||||
package com.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.*/
|
||||
/**
|
||||
* All the units PlayerStats can display statistics in, separated
|
||||
* by {@link Unit.Type}.
|
||||
*/
|
||||
public enum Unit {
|
||||
NUMBER (Type.UNTYPED, "Times"),
|
||||
KM (Type.DISTANCE, "km"),
|
||||
@ -26,21 +29,33 @@ public enum Unit {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/** Returns a pretty name belonging to this enum constant. If the Unit is
|
||||
NUMBER, it will return null. */
|
||||
/**
|
||||
* Gets the pretty name belonging to this enum constant.
|
||||
*
|
||||
* @return the label
|
||||
*/
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
/** Returns the Type this enum constant belongs to.*/
|
||||
/**
|
||||
* Gets the Type this enum constant belongs to.
|
||||
*
|
||||
* @return the Type
|
||||
*/
|
||||
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*/
|
||||
/**
|
||||
* 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
|
||||
* @return the smaller Unit
|
||||
*/
|
||||
public Unit getSmallerUnit(int stepsSmaller) {
|
||||
switch (this) {
|
||||
case DAY -> {
|
||||
@ -99,7 +114,12 @@ public enum Unit {
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts the current Unit into seconds (and returns -1 if the current Unit is not of Type TIME)*/
|
||||
/**
|
||||
* Converts the current Unit into seconds (and returns
|
||||
* -1 if the current Unit is not of {@link Unit.Type} TIME).
|
||||
*
|
||||
* @return this Unit in seconds
|
||||
*/
|
||||
public double getSeconds() {
|
||||
return switch (this) {
|
||||
case DAY -> 86400;
|
||||
@ -111,6 +131,15 @@ public enum Unit {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Unit corresponding to the given String. This String
|
||||
* does not need to match exactly (it can be "day" or "days",
|
||||
* for example), and is case-insensitive.
|
||||
*
|
||||
* @param unitName the name belonging to the desired Unit,
|
||||
* case-insensitive
|
||||
* @return the Unit
|
||||
*/
|
||||
/** Converts the current Unit into a short label (and returns a '?' if the current Unit is not of Type TIME)*/
|
||||
public char getShortLabel(){
|
||||
return switch (this) {
|
||||
@ -118,7 +147,6 @@ public enum Unit {
|
||||
case HOUR -> 'h';
|
||||
case MINUTE -> 'm';
|
||||
case SECOND -> 's';
|
||||
case TICK -> 't';
|
||||
default -> '?';
|
||||
};
|
||||
}
|
||||
@ -143,8 +171,13 @@ public enum Unit {
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the Unit.Type of this Statistic, which can be Untyped, Distance, Damage, or Time.
|
||||
@param statistic the Statistic enum constant*/
|
||||
/**
|
||||
* Gets the Unit.Type of this Statistic, which can be Untyped,
|
||||
* Distance, Damage, or Time.
|
||||
*
|
||||
* @param statistic the Statistic enum constant
|
||||
* @return the Type of this Unit
|
||||
*/
|
||||
public static @NotNull Type getTypeFromStatistic(Statistic statistic) {
|
||||
String name = statistic.toString().toLowerCase();
|
||||
if (name.contains("one_cm")) {
|
||||
@ -158,9 +191,13 @@ public enum Unit {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the most suitable timeUnit for this number.
|
||||
@param type the Unit.Type of the statistic this number belongs to
|
||||
@param number the statistic number as returned by Player.getStatistic()*/
|
||||
/**
|
||||
* Gets the most suitable Unit for this number.
|
||||
*
|
||||
* @param type the Unit.Type of the statistic this number belongs to
|
||||
* @param number the statistic number as returned by Player.getStatistic()
|
||||
* @return the Unit
|
||||
*/
|
||||
public static Unit getMostSuitableUnit(Unit.Type type, long number) {
|
||||
switch (type) {
|
||||
case TIME -> {
|
@ -1,11 +1,16 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.listeners;
|
||||
package com.artemis.the.gr8.playerstats.listeners;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/** Listens for new Players that join, and reloads PlayerStats if someone joins that hasn't joined before.*/
|
||||
/**
|
||||
* Listens for new Players that join, and reloads PlayerStats
|
||||
* if someone joins that hasn't joined before.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public class JoinListener implements Listener {
|
||||
|
||||
private static ThreadManager threadManager;
|
@ -0,0 +1,36 @@
|
||||
package com.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import net.kyori.adventure.text.*;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/** The {@link InternalFormatter} formats raw numbers into pretty messages.
|
||||
* This Formatter takes a {@link RequestSettings} object and combines it
|
||||
* with the raw data returned by the {@link StatCalculator}, and transforms
|
||||
* those into a pretty message with all the relevant information in it.
|
||||
* @see MessageBuilder
|
||||
*/
|
||||
@Internal
|
||||
public interface InternalFormatter {
|
||||
|
||||
/** @return a TextComponent with the following parts:
|
||||
* <br>[player-name]: [number] [stat-name] {sub-stat-name}
|
||||
*/
|
||||
TextComponent formatAndSavePlayerStat(RequestSettings requestSettings, int playerStat);
|
||||
|
||||
/** @return a TextComponent with the following parts:
|
||||
* <br>[Total on] [server-name]: [number] [stat-name] [sub-stat-name]
|
||||
*/
|
||||
TextComponent formatAndSaveServerStat(RequestSettings requestSettings, long serverStat);
|
||||
|
||||
/** @return a TextComponent with the following parts:
|
||||
* <br>[PlayerStats] [Top 10] [stat-name] [sub-stat-name]
|
||||
* <br> [1.] [player-name] [number]
|
||||
* <br> [2.] [player-name] [number]
|
||||
* <br> [3.] etc...
|
||||
*/
|
||||
TextComponent formatAndSaveTopStat(RequestSettings requestSettings, LinkedHashMap<String, Integer> topStats);
|
||||
}
|
@ -0,0 +1,736 @@
|
||||
package com.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.api.ApiFormatter;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentFactory;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ExampleMessage;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.HelpMessage;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.BukkitConsoleComponentFactory;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.PrideComponentFactory;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.*;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
|
||||
/**
|
||||
* Composes messages to send to a Player or Console. This class is responsible
|
||||
* for constructing a final {@link TextComponent} with the text content of the
|
||||
* desired message. The component parts (with appropriate formatting) are supplied
|
||||
* by a {@link ComponentFactory}. By default, this class works with the standard
|
||||
* ComponentFactory, but you can give it a different ComponentFactory upon creation.
|
||||
*
|
||||
* @see PrideComponentFactory
|
||||
* @see BukkitConsoleComponentFactory
|
||||
*/
|
||||
public final class MessageBuilder implements ApiFormatter {
|
||||
|
||||
private static ConfigHandler config;
|
||||
private boolean useHoverText;
|
||||
private boolean isConsoleBuilder;
|
||||
|
||||
private final ComponentFactory componentFactory;
|
||||
private final LanguageKeyHandler languageKeyHandler;
|
||||
private final NumberFormatter formatter;
|
||||
|
||||
private MessageBuilder(ConfigHandler config) {
|
||||
this (config, new ComponentFactory(config));
|
||||
}
|
||||
|
||||
private MessageBuilder(ConfigHandler configHandler, ComponentFactory factory) {
|
||||
config = configHandler;
|
||||
useHoverText = config.useHoverText();
|
||||
componentFactory = factory;
|
||||
|
||||
formatter = new NumberFormatter();
|
||||
languageKeyHandler = Main.getLanguageKeyHandler();
|
||||
}
|
||||
|
||||
public static MessageBuilder defaultBuilder(ConfigHandler config) {
|
||||
return new MessageBuilder(config);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getPluginPrefix() {
|
||||
return componentFactory.pluginPrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getRainbowPluginPrefix() {
|
||||
PrideComponentFactory pride = new PrideComponentFactory(config);
|
||||
return pride.rainbowPrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getPluginPrefixAsTitle() {
|
||||
return componentFactory.pluginPrefixAsTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getRainbowPluginPrefixAsTitle() {
|
||||
PrideComponentFactory pride = new PrideComponentFactory(config);
|
||||
return pride.pluginPrefixAsTitle();
|
||||
}
|
||||
|
||||
public TextComponent reloadedConfig() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content("Config reloaded!"));
|
||||
}
|
||||
|
||||
public TextComponent stillReloading() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"The plugin is (re)loading, your request will be processed when it is done!"));
|
||||
}
|
||||
|
||||
public TextComponent waitAMoment(boolean longWait) {
|
||||
String msg = longWait ? "Calculating statistics, this may take a minute..." :
|
||||
"Calculating statistics, this may take a few moments...";
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(msg));
|
||||
}
|
||||
|
||||
public TextComponent missingStatName() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please provide a valid statistic name!"));
|
||||
}
|
||||
|
||||
public TextComponent missingSubStatName(Statistic.Type statType) {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please add a valid " + EnumHandler.getSubStatTypeName(statType) + " to look up this statistic!"));
|
||||
}
|
||||
|
||||
public TextComponent missingPlayerName() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please specify a valid player-name!"));
|
||||
}
|
||||
|
||||
public TextComponent wrongSubStatType(Statistic.Type statType, String subStatName) {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.messageAccent().content("\"" + subStatName + "\""))
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"is not a valid " + EnumHandler.getSubStatTypeName(statType) + "!"));
|
||||
}
|
||||
|
||||
public TextComponent requestAlreadyRunning() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please wait for your previous lookup to finish!"));
|
||||
}
|
||||
|
||||
public TextComponent stillOnShareCoolDown() {
|
||||
int waitTime = config.getStatShareWaitingTime();
|
||||
String minutes = waitTime == 1 ? " minute" : " minutes";
|
||||
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content("You need to wait")
|
||||
.append(space())
|
||||
.append(componentFactory.messageAccent()
|
||||
.content(waitTime + minutes))
|
||||
.append(space())
|
||||
.append(text("between sharing!")));
|
||||
}
|
||||
|
||||
public TextComponent resultsAlreadyShared() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content("You already shared these results!"));
|
||||
}
|
||||
|
||||
public TextComponent statResultsTooOld() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"It has been too long since you looked up this statistic, please repeat the original command!"));
|
||||
}
|
||||
|
||||
public TextComponent unknownError() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Something went wrong with your request, " +
|
||||
"please try again or see /statistic for a usage explanation!"));
|
||||
}
|
||||
|
||||
public TextComponent usageExamples() {
|
||||
return ExampleMessage.construct(componentFactory);
|
||||
}
|
||||
|
||||
public TextComponent helpMsg() {
|
||||
int listSize = config.getTopListMaxSize();
|
||||
if (!isConsoleBuilder && useHoverText) {
|
||||
return HelpMessage.constructHoverMsg(componentFactory, listSize);
|
||||
} else {
|
||||
return HelpMessage.constructPlainMsg(componentFactory, listSize);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getStatTitle(Statistic statistic, @Nullable String subStatName) {
|
||||
return getTopStatTitleComponent(0, statistic, subStatName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getTopStatTitle(int topListSize, Statistic statistic, @Nullable String subStatName) {
|
||||
return getTopStatTitleComponent(topListSize, statistic, subStatName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatTopStatLine(int positionInTopList, String playerName, long statNumber, Statistic statistic) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.TOP, statistic);
|
||||
return getTopStatLineComponent(positionInTopList, playerName, statNumberComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatTopStatLine(int positionInTopList, String playerName, long statNumber, Unit unit) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.TOP, unit);
|
||||
return getTopStatLineComponent(positionInTopList, playerName, statNumberComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time-number does not hover
|
||||
*/
|
||||
@Override
|
||||
public TextComponent formatTopStatLineForTypeTime(int positionInTopList, String playerName, long statNumber, Unit bigUnit, Unit smallUnit) {
|
||||
TextComponent statNumberComponent = getBasicTimeNumberComponent(statNumber, Target.TOP, bigUnit, smallUnit);
|
||||
return getTopStatLineComponent(positionInTopList, playerName, statNumberComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStat(long statNumber, Statistic statistic) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.SERVER, statistic);
|
||||
return getServerStatComponent(statNumberComponent, statistic, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStat(long statNumber, Statistic statistic, String subStatName) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.SERVER, statistic);
|
||||
return getServerStatComponent(statNumberComponent, statistic, subStatName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStat(long statNumber, Statistic statistic, Unit unit) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.SERVER, unit);
|
||||
return getServerStatComponent(statNumberComponent, statistic, null, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStatForTypeTime(long statNumber, Statistic statistic, Unit bigUnit, Unit smallUnit) {
|
||||
TextComponent statNumberComponent = getBasicTimeNumberComponent(statNumber, Target.SERVER, bigUnit, smallUnit);
|
||||
return getServerStatComponent(statNumberComponent, statistic, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.PLAYER, statistic);
|
||||
return getPlayerStatComponent(playerName, statNumberComponent, statistic, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic, Unit unit) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.PLAYER, unit);
|
||||
return getPlayerStatComponent(playerName, statNumberComponent, statistic, null, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatPlayerStat(String playerName, int statNumber, Statistic statistic, String subStatName) {
|
||||
TextComponent statNumberComponent = getStatNumberComponent(statNumber, Target.PLAYER, statistic);
|
||||
return getPlayerStatComponent(playerName, statNumberComponent, statistic, subStatName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatPlayerStatForTypeTime(String playerName, int statNumber, Statistic statistic, Unit bigUnit, Unit smallUnit) {
|
||||
TextComponent statNumberComponent = getBasicTimeNumberComponent(statNumber, Target.PLAYER, bigUnit, smallUnit);
|
||||
return getPlayerStatComponent(playerName, statNumberComponent, statistic, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BiFunction for a player statistic. This BiFunction will return
|
||||
* a formattedComponent, 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>- If both parameters are null, the formattedComponent will be returned
|
||||
* as is.
|
||||
*/
|
||||
public BiFunction<Integer, CommandSender, TextComponent> formattedPlayerStatFunction(int stat, @NotNull RequestSettings request) {
|
||||
TextComponent playerStat = formatPlayerStat(request.getPlayerName(), stat, request.getStatistic(), request.getSubStatEntryName());
|
||||
return getFormattingFunction(playerStat, Target.PLAYER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BiFunction for a server statistic. This BiFunction will return
|
||||
* a formattedComponent, 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>- If both parameters are null, the formattedComponent will be returned
|
||||
* as is.
|
||||
*/
|
||||
public BiFunction<Integer, CommandSender, TextComponent> formattedServerStatFunction(long stat, @NotNull RequestSettings request) {
|
||||
TextComponent serverStat = formatServerStat(stat, request.getStatistic(), request.getSubStatEntryName());
|
||||
return getFormattingFunction(serverStat, Target.SERVER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BiFunction for a top statistic. This BiFunction will return
|
||||
* a formattedComponent, 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>- If both parameters are null, the formattedComponent will be returned
|
||||
* as is.
|
||||
*/
|
||||
public BiFunction<Integer, CommandSender, TextComponent> formattedTopStatFunction(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull RequestSettings request) {
|
||||
final TextComponent title = getTopStatTitle(topStats.size(), request.getStatistic(), request.getSubStatEntryName());
|
||||
final TextComponent list = getTopStatListComponent(topStats, request.getStatistic());
|
||||
final boolean useEnters = config.useEnters(Target.TOP, false);
|
||||
final boolean useEntersForShared = config.useEnters(Target.TOP, true);
|
||||
|
||||
return (shareCode, sender) -> {
|
||||
TextComponent.Builder topBuilder = text();
|
||||
|
||||
//if we're adding a share-button
|
||||
if (shareCode != null) {
|
||||
if (useEnters) {
|
||||
topBuilder.append(newline());
|
||||
}
|
||||
topBuilder.append(componentFactory.pluginPrefix())
|
||||
.append(space())
|
||||
.append(title)
|
||||
.append(space())
|
||||
.append(componentFactory.shareButton(shareCode))
|
||||
.append(list);
|
||||
}
|
||||
//if we're adding a "shared by" component
|
||||
else if (sender != null) {
|
||||
if (useEntersForShared) {
|
||||
topBuilder.append(newline());
|
||||
}
|
||||
topBuilder.append(title)
|
||||
.append(space())
|
||||
.append(componentFactory.statResultInHoverText(text()
|
||||
.append(componentFactory.pluginPrefix())
|
||||
.append(space())
|
||||
.append(title)
|
||||
.append(list)
|
||||
.build()))
|
||||
.append(newline())
|
||||
.append(componentFactory.sharedByMessage(
|
||||
getSharerNameComponent(sender)));
|
||||
}
|
||||
//if we're not adding a share-button or a "shared by" component
|
||||
else {
|
||||
if (useEnters) {
|
||||
topBuilder.append(newline());
|
||||
}
|
||||
topBuilder.append(componentFactory.pluginPrefix())
|
||||
.append(space())
|
||||
.append(title)
|
||||
.append(list);
|
||||
}
|
||||
return topBuilder.build();
|
||||
};
|
||||
}
|
||||
|
||||
private TextComponent getPlayerStatComponent(String playerName, TextComponent statNumberComponent, Statistic statistic, @Nullable String subStatName, @Nullable Unit unit) {
|
||||
TextComponent statUnit = (unit == null) ?
|
||||
getStatUnitComponent(statistic, Target.PLAYER) :
|
||||
getStatUnitComponent(unit, Target.PLAYER);
|
||||
|
||||
return Component.text()
|
||||
.append(componentFactory.playerName(playerName, Target.PLAYER)
|
||||
.append(text(":"))
|
||||
.append(space()))
|
||||
.append(statNumberComponent)
|
||||
.append(space())
|
||||
.append(getStatAndSubStatNameComponent(statistic, subStatName, Target.PLAYER))
|
||||
.append(statUnit) //space is provided by statUnitComponent
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent getServerStatComponent(TextComponent statNumber, Statistic statistic, @Nullable String subStatName, @Nullable Unit unit) {
|
||||
String serverTitle = config.getServerTitle();
|
||||
String serverName = config.getServerName();
|
||||
TextComponent statUnit = (unit == null) ?
|
||||
getStatUnitComponent(statistic, Target.SERVER) :
|
||||
getStatUnitComponent(unit, Target.SERVER);
|
||||
|
||||
return Component.text()
|
||||
.append(componentFactory.title(serverTitle, Target.SERVER))
|
||||
.append(space())
|
||||
.append(componentFactory.serverName(serverName))
|
||||
.append(space())
|
||||
.append(statNumber)
|
||||
.append(space())
|
||||
.append(getStatAndSubStatNameComponent(statistic, subStatName, Target.SERVER))
|
||||
.append(statUnit) //space is provided by statUnit
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent getTopStatTitleComponent(int topListSize, Statistic statistic, @Nullable String subStatName, @Nullable Unit unit) {
|
||||
TextComponent statUnit = (unit == null) ?
|
||||
getStatUnitComponent(statistic, Target.TOP) :
|
||||
getStatUnitComponent(unit, Target.TOP);
|
||||
|
||||
if (topListSize == 0) {
|
||||
return Component.text()
|
||||
.append(getStatAndSubStatNameComponent(statistic, subStatName, Target.TOP))
|
||||
.append(statUnit) //space is provided by statUnitComponent
|
||||
.build();
|
||||
} else {
|
||||
return Component.text()
|
||||
.append(componentFactory.title(config.getTopStatsTitle(), Target.TOP))
|
||||
.append(space())
|
||||
.append(componentFactory.titleNumber(topListSize))
|
||||
.append(space())
|
||||
.append(getStatAndSubStatNameComponent(statistic, subStatName, Target.TOP))
|
||||
.append(statUnit) //space is provided by statUnitComponent
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private TextComponent getTopStatListComponent(LinkedHashMap<String, Integer> topStats, Statistic statistic) {
|
||||
TextComponent.Builder topList = Component.text();
|
||||
Set<String> playerNames = topStats.keySet();
|
||||
boolean useDots = config.useDots();
|
||||
|
||||
int count = 0;
|
||||
for (String playerName : playerNames) {
|
||||
topList.append(newline());
|
||||
if (useDots) {
|
||||
topList.append(getTopStatLineComponent(
|
||||
++count, playerName, getStatNumberComponent(topStats.get(playerName), Target.TOP, statistic)));
|
||||
} else {
|
||||
topList.append(space())
|
||||
.append(componentFactory.rankNumber(++count))
|
||||
.append(space())
|
||||
.append(componentFactory.playerName(playerName + ":", Target.TOP))
|
||||
.append(space()).append(getStatNumberComponent(topStats.get(playerName), Target.TOP, statistic));
|
||||
}
|
||||
}
|
||||
return topList.build();
|
||||
}
|
||||
|
||||
private TextComponent getTopStatLineComponent(int positionInTopList, String playerName, TextComponent statNumberComponent) {
|
||||
TextComponent.Builder topStatLineBuilder = Component.text()
|
||||
.append(space())
|
||||
.append(componentFactory.rankNumber(positionInTopList))
|
||||
.append(space())
|
||||
.append(componentFactory.playerName(playerName, Target.TOP))
|
||||
.append(space());
|
||||
|
||||
int dots = getNumberOfDotsToAlign(positionInTopList + ". " + playerName);
|
||||
if (dots >= 1) {
|
||||
topStatLineBuilder.append(componentFactory.dots(".".repeat(dots)));
|
||||
}
|
||||
|
||||
return topStatLineBuilder
|
||||
.append(space())
|
||||
.append(statNumberComponent)
|
||||
.build();
|
||||
}
|
||||
|
||||
private TextComponent getStatAndSubStatNameComponent(Statistic statistic, @Nullable String subStatName, Target target) {
|
||||
if (config.useTranslatableComponents()) {
|
||||
String statKey = languageKeyHandler.getStatKey(statistic);
|
||||
String subStatKey = switch (statistic.getType()) {
|
||||
case UNTYPED -> null;
|
||||
case ENTITY -> languageKeyHandler.getEntityKey(EnumHandler.getEntityEnum(subStatName));
|
||||
case BLOCK -> languageKeyHandler.getBlockKey(EnumHandler.getBlockEnum(subStatName));
|
||||
case ITEM -> languageKeyHandler.getItemKey(EnumHandler.getItemEnum(subStatName));
|
||||
};
|
||||
if (subStatKey == null) {
|
||||
subStatKey = StringUtils.prettify(subStatName);
|
||||
}
|
||||
return componentFactory.statAndSubStatNameTranslatable(statKey, subStatKey, target);
|
||||
}
|
||||
|
||||
String prettyStatName = StringUtils.prettify(statistic.toString());
|
||||
String prettySubStatName = StringUtils.prettify(subStatName);
|
||||
return componentFactory.statAndSubStatName(prettyStatName, prettySubStatName, target);
|
||||
}
|
||||
|
||||
private TextComponent getStatNumberComponent(long statNumber, Target target, Unit unit) {
|
||||
return switch (unit.getType()) {
|
||||
case TIME -> getBasicTimeNumberComponent(statNumber, target, unit, null);
|
||||
case DAMAGE -> getDamageNumberComponent(statNumber, target, unit);
|
||||
case DISTANCE -> getDistanceNumberComponent(statNumber, target, unit);
|
||||
default -> getDefaultNumberComponent(statNumber, target);
|
||||
};
|
||||
}
|
||||
|
||||
private TextComponent getStatNumberComponent(long statNumber, Target target, Statistic statistic) {
|
||||
Unit.Type unitType = Unit.getTypeFromStatistic(statistic);
|
||||
return switch (unitType) {
|
||||
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));
|
||||
return getDistanceNumberComponent(statNumber, target, statUnit);
|
||||
}
|
||||
|
||||
private TextComponent getDistanceNumberComponent(long statNumber, Target target, Unit unit) {
|
||||
String prettyNumber = formatter.formatDistanceNumber(statNumber, unit);
|
||||
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));
|
||||
return getDamageNumberComponent(statNumber, target, statUnit);
|
||||
}
|
||||
|
||||
private TextComponent getDamageNumberComponent(long statNumber, Target target, Unit unit) {
|
||||
String prettyNumber = formatter.formatDamageNumber(statNumber, unit);
|
||||
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.logWarning("There is something wrong with the time-units you specified, please check your config!");
|
||||
return componentFactory.timeNumber(formatter.formatNumber(statNumber), target);
|
||||
}
|
||||
else {
|
||||
String mainNumber = formatter.formatTimeNumber(statNumber, unitRange.get(0), unitRange.get(1));
|
||||
if (!useHoverText) {
|
||||
return componentFactory.timeNumber(mainNumber, target);
|
||||
} else {
|
||||
String hoverNumber = formatter.formatTimeNumber(statNumber, unitRange.get(2), unitRange.get(3));
|
||||
MyLogger.logHighLevelMsg("mainNumber: " + mainNumber + ", hoverNumber: " + hoverNumber);
|
||||
return componentFactory.timeNumberWithHoverText(mainNumber, hoverNumber, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TextComponent getBasicTimeNumberComponent(long statNumber, Target target, Unit bigUnit, @Nullable Unit smallUnit) {
|
||||
if (smallUnit == null) {
|
||||
smallUnit = bigUnit.getSmallerUnit(1);
|
||||
}
|
||||
return componentFactory.timeNumber(formatter.formatTimeNumber(statNumber, bigUnit, smallUnit), target);
|
||||
}
|
||||
|
||||
private TextComponent getDefaultNumberComponent(long statNumber, Target target) {
|
||||
return componentFactory.statNumber(formatter.formatNumber(statNumber), target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides its own space in front of it!
|
||||
*/
|
||||
private TextComponent getStatUnitComponent(Statistic statistic, Target target) {
|
||||
Unit unit = switch (Unit.getTypeFromStatistic(statistic)) {
|
||||
case DAMAGE -> Unit.fromString(config.getDamageUnit(false));
|
||||
case DISTANCE -> Unit.fromString(config.getDistanceUnit(false));
|
||||
default -> Unit.NUMBER;
|
||||
};
|
||||
return getStatUnitComponent(unit, target);
|
||||
}
|
||||
|
||||
private TextComponent getStatUnitComponent(Unit unit, Target target) {
|
||||
return switch (unit.getType()) {
|
||||
case DAMAGE -> getDamageUnitComponent(unit, target);
|
||||
case DISTANCE -> getDistanceUnitComponent(unit, target);
|
||||
default -> Component.empty();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides its own space in front of it!
|
||||
*/
|
||||
private TextComponent getDistanceUnitComponent(Unit unit, Target target) {
|
||||
if (config.useTranslatableComponents()) {
|
||||
String unitKey = languageKeyHandler.getUnitKey(unit);
|
||||
if (unitKey != null) {
|
||||
return Component.space()
|
||||
.append(componentFactory.statUnitTranslatable(unitKey, target));
|
||||
}
|
||||
}
|
||||
return Component.space()
|
||||
.append(componentFactory.statUnit(unit.getLabel(), target));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides its own space in front of it!
|
||||
*/
|
||||
private TextComponent getDamageUnitComponent(Unit unit, Target target) {
|
||||
if (unit == Unit.HEART) {
|
||||
TextComponent heartUnit;
|
||||
if (isConsoleBuilder) {
|
||||
heartUnit = componentFactory.consoleHeart();
|
||||
} else if (useHoverText) {
|
||||
heartUnit = componentFactory.clientHeartWithHoverText();
|
||||
} else {
|
||||
heartUnit = componentFactory.clientHeart(false);
|
||||
}
|
||||
return Component.space()
|
||||
.append(heartUnit);
|
||||
}
|
||||
return Component.space()
|
||||
.append(componentFactory.statUnit(unit.getLabel(), target));
|
||||
}
|
||||
|
||||
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 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();
|
||||
|
||||
//if we're adding a share-button
|
||||
if (shareCode != null) {
|
||||
if (useEnters) {
|
||||
statBuilder.append(newline());
|
||||
}
|
||||
statBuilder.append(statResult)
|
||||
.append(space())
|
||||
.append(componentFactory.shareButton(shareCode));
|
||||
}
|
||||
//if we're adding a "shared by" component
|
||||
else if (sender != null) {
|
||||
if (useEntersForShared) {
|
||||
statBuilder.append(newline());
|
||||
}
|
||||
statBuilder.append(statResult)
|
||||
.append(newline())
|
||||
.append(componentFactory.sharedByMessage(
|
||||
getSharerNameComponent(sender)));
|
||||
}
|
||||
//if we're not adding a share-button or a "shared by" component
|
||||
else {
|
||||
if (useEnters) {
|
||||
statBuilder.append(newline());
|
||||
}
|
||||
statBuilder.append(statResult);
|
||||
}
|
||||
return statBuilder.build();
|
||||
};
|
||||
}
|
||||
|
||||
private int getNumberOfDotsToAlign(String displayText) {
|
||||
if (isConsoleBuilder) {
|
||||
return FontUtils.getNumberOfDotsToAlignForConsole(displayText);
|
||||
} else if (config.playerNameIsBold()) {
|
||||
return FontUtils.getNumberOfDotsToAlignForBoldText(displayText);
|
||||
} else {
|
||||
return FontUtils.getNumberOfDotsToAlign(displayText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ArrayList consisting of 2 or 4 timeUnits. The order of items is:
|
||||
* <p>0. maxUnit</p>
|
||||
* <p>1. minUnit</p>
|
||||
* <p>2. maxHoverUnit</p>
|
||||
* <p>3. minHoverUnit</p>
|
||||
*/
|
||||
private ArrayList<Unit> getTimeUnitRange(long statNumber) {
|
||||
ArrayList<Unit> unitRange = new ArrayList<>();
|
||||
if (!config.autoDetectTimeUnit(false)) {
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(false)));
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(false, true)));
|
||||
}
|
||||
else {
|
||||
Unit bigUnit = Unit.getMostSuitableUnit(Unit.Type.TIME, statNumber);
|
||||
unitRange.add(bigUnit);
|
||||
unitRange.add(bigUnit.getSmallerUnit(config.getNumberOfExtraTimeUnits(false)));
|
||||
}
|
||||
if (useHoverText) {
|
||||
if (!config.autoDetectTimeUnit(true)) {
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(true)));
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(true, true)));
|
||||
}
|
||||
else {
|
||||
Unit bigHoverUnit = Unit.getMostSuitableUnit(Unit.Type.TIME, statNumber);
|
||||
unitRange.add(bigHoverUnit);
|
||||
unitRange.add(bigHoverUnit.getSmallerUnit(config.getNumberOfExtraTimeUnits(true)));
|
||||
}
|
||||
}
|
||||
return unitRange;
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg;
|
||||
package com.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
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.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 com.artemis.the.gr8.playerstats.ShareManager;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.BukkitConsoleComponentFactory;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.PrideComponentFactory;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
@ -24,12 +22,16 @@ import java.util.LinkedHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage.*;
|
||||
import static com.artemis.the.gr8.playerstats.enums.StandardMessage.*;
|
||||
|
||||
/** This class manages all PlayerStats output. It is the only place where messages are sent.
|
||||
It gets 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 {
|
||||
/**
|
||||
* This class manages all PlayerStats output. It is the only
|
||||
* place where messages are sent. It gets its messages from a
|
||||
* {@link MessageBuilder} configured for either a Console or
|
||||
* for Players (mainly to deal with the lack of hover-text,
|
||||
* and for Bukkit consoles to make up for the lack of hex-colors).
|
||||
*/
|
||||
public final class OutputManager implements InternalFormatter {
|
||||
|
||||
private static BukkitAudiences adventure;
|
||||
private static ConfigHandler config;
|
||||
@ -53,56 +55,27 @@ public final class OutputManager implements StatFormatter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getPluginPrefix() {
|
||||
ComponentFactory factory = new ComponentFactory(config);
|
||||
return factory.pluginPrefix();
|
||||
}
|
||||
|
||||
@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) {
|
||||
public TextComponent formatAndSavePlayerStat(@NotNull RequestSettings requestSettings, int playerStat) {
|
||||
BiFunction<Integer, CommandSender, TextComponent> playerStatFunction =
|
||||
getMessageBuilder(statRequest).formattedPlayerStatFunction(playerStat, statRequest);
|
||||
getMessageBuilder(requestSettings).formattedPlayerStatFunction(playerStat, requestSettings);
|
||||
|
||||
return processFunction(statRequest.getCommandSender(), playerStatFunction);
|
||||
return processFunction(requestSettings.getCommandSender(), playerStatFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent formatServerStat(@NotNull StatRequest statRequest, long serverStat) {
|
||||
public TextComponent formatAndSaveServerStat(@NotNull RequestSettings requestSettings, long serverStat) {
|
||||
BiFunction<Integer, CommandSender, TextComponent> serverStatFunction =
|
||||
getMessageBuilder(statRequest).formattedServerStatFunction(serverStat, statRequest);
|
||||
getMessageBuilder(requestSettings).formattedServerStatFunction(serverStat, requestSettings);
|
||||
|
||||
return processFunction(statRequest.getCommandSender(), serverStatFunction);
|
||||
return processFunction(requestSettings.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) {
|
||||
public TextComponent formatAndSaveTopStat(@NotNull RequestSettings requestSettings, @NotNull LinkedHashMap<String, Integer> topStats) {
|
||||
BiFunction<Integer, CommandSender, TextComponent> topStatFunction =
|
||||
getMessageBuilder(statRequest).formattedTopStatFunction(topStats, statRequest);
|
||||
getMessageBuilder(requestSettings).formattedTopStatFunction(topStats, requestSettings);
|
||||
|
||||
return processFunction(statRequest.getCommandSender(), topStatFunction);
|
||||
return processFunction(requestSettings.getCommandSender(), topStatFunction);
|
||||
}
|
||||
|
||||
public void sendFeedbackMsg(@NotNull CommandSender sender, StandardMessage message) {
|
||||
@ -168,8 +141,8 @@ public final class OutputManager implements StatFormatter {
|
||||
return sender instanceof ConsoleCommandSender ? consoleMessageBuilder : messageBuilder;
|
||||
}
|
||||
|
||||
private MessageBuilder getMessageBuilder(StatRequest statRequest) {
|
||||
if (statRequest.isAPIRequest() || !statRequest.isConsoleSender()) {
|
||||
private MessageBuilder getMessageBuilder(RequestSettings requestSettings) {
|
||||
if (!requestSettings.isConsoleSender()) {
|
||||
return messageBuilder;
|
||||
} else {
|
||||
return consoleMessageBuilder;
|
@ -1,7 +1,7 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
@ -11,8 +11,11 @@ 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.*/
|
||||
/**
|
||||
* 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) {
|
@ -1,11 +1,11 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
|
||||
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 com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.artemis.the.gr8.playerstats.msg.MessageBuilder;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
@ -22,9 +22,14 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
|
||||
/** 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.*/
|
||||
/** 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 (as specified by the {@link ConfigHandler}).
|
||||
*
|
||||
* @see PluginColor
|
||||
*/
|
||||
public class ComponentFactory {
|
||||
|
||||
private static ConfigHandler config;
|
||||
@ -76,8 +81,9 @@ public class ComponentFactory {
|
||||
return getColorFromString(config.getSharerNameDecoration(false));
|
||||
}
|
||||
|
||||
|
||||
/** Returns [PlayerStats]. */
|
||||
/**
|
||||
* Returns [PlayerStats].
|
||||
*/
|
||||
public TextComponent pluginPrefix() {
|
||||
return text("[")
|
||||
.color(BRACKETS)
|
||||
@ -85,7 +91,9 @@ public class ComponentFactory {
|
||||
.append(text("]"));
|
||||
}
|
||||
|
||||
/** Returns [PlayerStats] surrounded by underscores on both sides. */
|
||||
/**
|
||||
* Returns [PlayerStats] surrounded by underscores on both sides.
|
||||
*/
|
||||
public TextComponent pluginPrefixAsTitle() {
|
||||
//12 underscores for both console and in-game
|
||||
return text("____________").color(UNDERSCORE)
|
||||
@ -95,12 +103,18 @@ public class ComponentFactory {
|
||||
.append(text("____________"));
|
||||
}
|
||||
|
||||
/** Returns a TextComponent with the input String as content, with color Gray and decoration Italic.*/
|
||||
/**
|
||||
* Returns a TextComponent with the input String as content,
|
||||
* with color Gray and decoration Italic.
|
||||
*/
|
||||
public TextComponent subTitle(String content) {
|
||||
return text(content).color(BRACKETS).decorate(TextDecoration.ITALIC);
|
||||
}
|
||||
|
||||
/** Returns a TextComponents in the style of a default plugin message, with color Medium_Blue. */
|
||||
/**
|
||||
* Returns a TextComponents in the style of a default plugin message,
|
||||
* with color Medium_Blue.
|
||||
*/
|
||||
public TextComponent message() {
|
||||
return text().color(MSG_MAIN).build();
|
||||
}
|
||||
@ -127,8 +141,8 @@ public class ComponentFactory {
|
||||
getStyleFromString(config.getRankNumberDecoration(true)));
|
||||
}
|
||||
|
||||
public TextComponent dots() {
|
||||
return getComponent(null,
|
||||
public TextComponent dots(String dots) {
|
||||
return getComponent(dots,
|
||||
getColorFromString(config.getDotsDecoration(false)),
|
||||
getStyleFromString(config.getDotsDecoration(true)));
|
||||
}
|
||||
@ -182,8 +196,12 @@ public class ComponentFactory {
|
||||
.build());
|
||||
}
|
||||
|
||||
/** @param prettyStatName a statName with underscores removed and each word capitalized
|
||||
@param prettySubStatName if present, a subStatName with underscores removed and each word capitalized*/
|
||||
/**
|
||||
* @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 target) {
|
||||
TextComponent.Builder totalStatNameBuilder = getComponentBuilder(prettyStatName,
|
||||
getColorFromString(config.getStatNameDecoration(target, false)),
|
||||
@ -198,8 +216,10 @@ public class ComponentFactory {
|
||||
return totalStatNameBuilder.build();
|
||||
}
|
||||
|
||||
/** Returns a TextComponent with TranslatableComponent as a child.*/
|
||||
public TextComponent statAndSubStatNameTranslatable(String statKey, String subStatKey, Target target) {
|
||||
/**
|
||||
* Returns a TextComponent with TranslatableComponent as a child.
|
||||
* */
|
||||
public TextComponent statAndSubStatNameTranslatable(String statKey, @Nullable String subStatKey, Target target) {
|
||||
TextComponent.Builder totalStatNameBuilder = getComponentBuilder(null,
|
||||
getColorFromString(config.getStatNameDecoration(target, false)),
|
||||
getStyleFromString(config.getStatNameDecoration(target, true)));
|
||||
@ -228,13 +248,18 @@ public class ComponentFactory {
|
||||
getStyleFromString(config.getStatNumberDecoration(target, true)));
|
||||
}
|
||||
|
||||
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 timeNumber(String prettyNumber, Target target) {
|
||||
return statNumber(prettyNumber, target);
|
||||
}
|
||||
|
||||
public TextComponent timeNumberWithHoverText(String mainNumber, String hoverNumber, Target target) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, null, null, null, target);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@ -248,11 +273,11 @@ public class ComponentFactory {
|
||||
}
|
||||
|
||||
public TextComponent distanceNumberWithHoverText(String mainNumber, String hoverNumber, String hoverUnitName, Target target) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, null, target);
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, hoverUnitName, null, null, target);
|
||||
}
|
||||
|
||||
public TextComponent distanceNumberWithTranslatableHoverText(String mainNumber, String hoverNumber, String hoverUnitKey, Target target) {
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, null, hoverUnitKey, target);
|
||||
return statNumberWithHoverText(mainNumber, hoverNumber, null, hoverUnitKey, null, target);
|
||||
}
|
||||
|
||||
public TextComponent statUnit(String unitName, Target target) {
|
||||
@ -303,7 +328,9 @@ public class ComponentFactory {
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Returns a TextComponent for the subStatName, or an empty component.*/
|
||||
/**
|
||||
* Returns a TextComponent for the subStatName, or an empty component.
|
||||
*/
|
||||
private TextComponent subStatName(@Nullable String prettySubStatName, Target target) {
|
||||
if (prettySubStatName == null) {
|
||||
return Component.empty();
|
||||
@ -318,8 +345,10 @@ public class ComponentFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a TranslatableComponent for the subStatName, or an empty component.*/
|
||||
private TextComponent subStatNameTranslatable(String subStatKey, Target target) {
|
||||
/**
|
||||
* Returns a TranslatableComponent for the subStatName, or an empty component.
|
||||
*/
|
||||
private TextComponent subStatNameTranslatable(@Nullable String subStatKey, Target target) {
|
||||
if (subStatKey != null) {
|
||||
return getComponentBuilder(null,
|
||||
getColorFromString(config.getSubStatNameDecoration(target, false)),
|
||||
@ -333,18 +362,26 @@ public class ComponentFactory {
|
||||
return Component.empty();
|
||||
}
|
||||
|
||||
/** Construct a custom translation for kill_entity with the language key for commands.kill.success.single ("Killed %s").
|
||||
@return a TranslatableComponent Builder with the subStat Component as args.*/
|
||||
/**
|
||||
* Construct a custom translation for kill_entity with the language key
|
||||
* for commands.kill.success.single ("Killed %s").
|
||||
*
|
||||
* @return a TranslatableComponent Builder with the subStat Component as args.
|
||||
*/
|
||||
private TranslatableComponent.Builder killEntityBuilder(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key(LanguageKeyHandler.getAlternativeKeyForKillEntity()) //"Killed %s"
|
||||
.args(subStat);
|
||||
}
|
||||
|
||||
/** Construct a custom translation for entity_killed_by with the language keys for stat.minecraft.deaths
|
||||
("Number of Deaths") and book.byAuthor ("by %s").
|
||||
@return a TranslatableComponent Builder with stat.minecraft.deaths as key, with a ChildComponent
|
||||
with book.byAuthor as key and the subStat Component as args.*/
|
||||
/**
|
||||
* Construct a custom translation for entity_killed_by with the language
|
||||
* keys for stat.minecraft.deaths ("Number of Deaths") and book.byAuthor
|
||||
* ("by %s").
|
||||
*
|
||||
* @return a TranslatableComponent Builder with stat.minecraft.deaths as key,
|
||||
* with a ChildComponent with book.byAuthor as key and the subStat Component as args.
|
||||
*/
|
||||
private TranslatableComponent.Builder entityKilledByBuilder(@NotNull TextComponent subStat) {
|
||||
return translatable()
|
||||
.key(LanguageKeyHandler.getAlternativeKeyForEntityKilledBy()) //"Number of Deaths"
|
||||
@ -354,7 +391,11 @@ public class ComponentFactory {
|
||||
.args(subStat));
|
||||
}
|
||||
|
||||
private TextComponent statNumberWithHoverText(String mainNumber, String hoverNumber, @Nullable String hoverUnitName, @Nullable String hoverUnitKey, @Nullable TextComponent heartComponent, Target target) {
|
||||
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));
|
||||
|
@ -1,19 +1,26 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.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 com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.StringUtils;
|
||||
import net.kyori.adventure.text.*;
|
||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
/** A utility class for handling Adventure's Components.
|
||||
Its main function is currently to help serialize Components into String.*/
|
||||
/**
|
||||
* A small utility class for turning PlayerStats' custom 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. */
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the Serializer
|
||||
* @see LanguageKeyHandler
|
||||
*/
|
||||
public static LegacyComponentSerializer getTranslatableComponentSerializer() {
|
||||
LegacyComponentSerializer serializer = getTextComponentSerializer();
|
||||
|
||||
@ -29,6 +36,7 @@ public final class ComponentUtils {
|
||||
TextComponent.Builder temp = Component.text();
|
||||
trans.iterator(ComponentIteratorType.DEPTH_FIRST, ComponentIteratorFlag.INCLUDE_TRANSLATABLE_COMPONENT_ARGUMENTS)
|
||||
.forEachRemaining(component -> {
|
||||
//copy the style to the temp builder, because the translatable component that follows it has no style itself
|
||||
if (component instanceof TextComponent text) {
|
||||
if (!text.children().isEmpty()) {
|
||||
text.iterator(ComponentIteratorType.DEPTH_FIRST).forEachRemaining(component1 -> {
|
||||
@ -37,27 +45,31 @@ public final class ComponentUtils {
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (component instanceof TranslatableComponent translatable) {
|
||||
if (translatable.key().contains("entity")) {
|
||||
}
|
||||
//isolate the translatable component with the entity inside
|
||||
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(
|
||||
StringUtils.prettify(LanguageKeyHandler.convertToName(translatable.key()))))
|
||||
.append(Component.text(")")));
|
||||
totalPrettyName.append(
|
||||
serializer.serialize(temp.build()));
|
||||
} else if (!LanguageKeyHandler.isKeyForEntityKilledByArg(translatable.key())) {
|
||||
}
|
||||
else if (!LanguageKeyHandler.isKeyForEntityKilledByArg(translatable.key())) {
|
||||
totalPrettyName.append(
|
||||
StringUtils.prettify(
|
||||
LanguageKeyHandler.convertToName(
|
||||
translatable.key())));
|
||||
LanguageKeyHandler.getStatKeyTranslation(
|
||||
translatable.key()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (trans.key().startsWith("stat")) {
|
||||
return LanguageKeyHandler.getStatKeyTranslation(trans.key());
|
||||
}
|
||||
else {
|
||||
return StringUtils.prettify(
|
||||
LanguageKeyHandler.convertToName(
|
||||
trans.key()));
|
||||
return StringUtils.prettify(LanguageKeyHandler.convertToName(trans.key()));
|
||||
}
|
||||
return totalPrettyName.toString();
|
||||
})
|
@ -1,4 +1,4 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
@ -11,7 +11,9 @@ import java.util.List;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
/** A fully constructed message with examples on how to use PlayerStats.*/
|
||||
/**
|
||||
* A fully constructed message with examples on how to use PlayerStats.
|
||||
*/
|
||||
public final class ExampleMessage implements TextComponent {
|
||||
|
||||
private final TextComponent exampleMessage;
|
@ -1,4 +1,4 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
@ -13,7 +13,9 @@ import java.util.List;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
/** The help message that explains how to use PlayerStats.*/
|
||||
/**
|
||||
* The help message that explains how to use PlayerStats.
|
||||
*/
|
||||
public final class HelpMessage implements TextComponent {
|
||||
|
||||
private final TextComponent helpMessage;
|
@ -1,8 +1,8 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.components;
|
||||
package com.artemis.the.gr8.playerstats.msg.components;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import com.artemis.the.gr8.playerstats.enums.PluginColor;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
@ -11,7 +11,9 @@ import java.util.Random;
|
||||
|
||||
import static net.kyori.adventure.text.Component.*;
|
||||
|
||||
/** A festive version of the {@link ComponentFactory}*/
|
||||
/**
|
||||
* A festive version of the {@link ComponentFactory}
|
||||
*/
|
||||
public class PrideComponentFactory extends ComponentFactory {
|
||||
|
||||
public PrideComponentFactory(ConfigHandler c) {
|
||||
@ -61,6 +63,10 @@ public class PrideComponentFactory extends ComponentFactory {
|
||||
if (randomizer.nextBoolean()) {
|
||||
return backwardsPluginPrefixComponent();
|
||||
}
|
||||
return rainbowPrefix();
|
||||
}
|
||||
|
||||
public TextComponent rainbowPrefix() {
|
||||
return text()
|
||||
.append(MiniMessage.miniMessage()
|
||||
.deserialize("<#f74040>[</#f74040>" +
|
||||
@ -79,7 +85,7 @@ public class PrideComponentFactory extends ComponentFactory {
|
||||
.build();
|
||||
}
|
||||
|
||||
public TextComponent backwardsPluginPrefixComponent() {
|
||||
private TextComponent backwardsPluginPrefixComponent() {
|
||||
return text()
|
||||
.append(MiniMessage.miniMessage()
|
||||
.deserialize("<#631ae6>[</#631ae6>" +
|
@ -1,4 +1,4 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
package com.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import net.kyori.adventure.text.Component;
|
||||
@ -12,8 +12,11 @@ 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.
|
||||
It does not impact the rest of the plugin, and will only be used for the players mentioned in here.*/
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private static boolean isEnabled;
|
||||
@ -58,7 +61,7 @@ public final class EasterEggProvider {
|
||||
}
|
||||
}
|
||||
case "0dc5336b-acd2-4dc3-a5e9-0aa9b8f113f7" -> {
|
||||
if (sillyNumberIsBetween(sillyNumber, 0, 20)) {
|
||||
if (sillyNumberIsBetween(sillyNumber, 0, 100)) {
|
||||
playerName = "<gradient:#f73bdb:#fc8bec:#f73bdb>an UwU sister</gradient>";
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.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() {
|
||||
}
|
||||
|
||||
public static int getNumberOfDotsToAlign(String displayText) {
|
||||
return (int) Math.round((130.0 - MinecraftFont.Font.getWidth(displayText))/2);
|
||||
}
|
||||
|
||||
public static int getNumberOfDotsToAlignForConsole(String displayText) {
|
||||
return (int) Math.round((130.0 - MinecraftFont.Font.getWidth(displayText))/6) + 7;
|
||||
}
|
||||
|
||||
public static int getNumberOfDotsToAlignForBoldText(String displayText) {
|
||||
return (int) Math.round((130.0 - (MinecraftFont.Font.getWidth(displayText) * 1.5))/2);
|
||||
}
|
||||
}
|
@ -1,65 +1,133 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
package com.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
/** A utility class that provides language keys to be put in a TranslatableComponent.*/
|
||||
/**
|
||||
*
|
||||
* A utility class that provides language keys to be
|
||||
* put in a TranslatableComponent.
|
||||
*/
|
||||
public final class LanguageKeyHandler {
|
||||
|
||||
private static Main plugin;
|
||||
private static HashMap<Statistic, String> statNameKeys;
|
||||
private static File languageKeyFile;
|
||||
private static FileConfiguration languageKeys;
|
||||
|
||||
public LanguageKeyHandler() {
|
||||
/**
|
||||
* Since this class uses a file to get the English translations
|
||||
* of languageKeys, it needs an instance of the PlayerStats
|
||||
* plugin to get access to this file.
|
||||
*
|
||||
* @param plugin an instance of PlayerStats' Main class
|
||||
*/
|
||||
public LanguageKeyHandler(Main plugin) {
|
||||
LanguageKeyHandler.plugin = plugin;
|
||||
statNameKeys = generateStatNameKeys();
|
||||
loadFile();
|
||||
}
|
||||
|
||||
/** Checks if a given Key is the language key "stat_type.minecraft.killed"
|
||||
or "commands.kill.success.single" (which results in "Killed %s").*/
|
||||
private static void loadFile() {
|
||||
languageKeyFile = new File(plugin.getDataFolder(), "language.yml");
|
||||
if (!languageKeyFile.exists()) {
|
||||
plugin.saveResource("language.yml", false);
|
||||
}
|
||||
languageKeys = YamlConfiguration.loadConfiguration(languageKeyFile);
|
||||
}
|
||||
|
||||
@Internal
|
||||
public static void reloadFile() {
|
||||
if (!languageKeyFile.exists()) {
|
||||
loadFile();
|
||||
} else {
|
||||
languageKeys = YamlConfiguration.loadConfiguration(languageKeyFile);
|
||||
MyLogger.logLowLevelMsg("Language file reloaded!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given Key is the language key "stat_type.minecraft.killed"
|
||||
* or "commands.kill.success.single" (which results in "Killed %s").
|
||||
*
|
||||
* @param statKey the Key to check
|
||||
* @return true if this Key is key for kill-entity
|
||||
*/
|
||||
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" */
|
||||
/**
|
||||
* 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").*/
|
||||
/**
|
||||
* Checks if a given Key is the language key "stat_type.minecraft.killed_by"
|
||||
* or "stat.minecraft.deaths" (which results in "Number of Deaths").
|
||||
*
|
||||
* @param statKey the Key to check
|
||||
* @return true if this Key is a key for entity-killed-by
|
||||
*/
|
||||
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()})*/
|
||||
/**
|
||||
* 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"). */
|
||||
/**
|
||||
* Checks if a given Key is the language key "book.byAuthor"
|
||||
* (which results in "by %s").
|
||||
*
|
||||
* @param statKey the Key to Check
|
||||
* @return true if this Key is the key for book.byAuthor
|
||||
*/
|
||||
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"*/
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key the String to turn into a normal name
|
||||
* @return a pretty name
|
||||
*/
|
||||
public static String convertToName(String key) {
|
||||
if (key.equalsIgnoreCase("soundCategory.block")) {
|
||||
return Unit.BLOCK.getLabel();
|
||||
@ -88,6 +156,31 @@ public final class LanguageKeyHandler {
|
||||
return key.replace(toReplace, "");
|
||||
}
|
||||
|
||||
private static @Nullable String convertToNormalStatKey(String statKey) {
|
||||
if (isKeyForKillEntity(statKey)) {
|
||||
return "stat_type.minecraft.killed";
|
||||
} else if (isKeyForEntityKilledBy(statKey)) {
|
||||
return "stat_type.minecraft.killed_by";
|
||||
} else if (isKeyForEntityKilledByArg(statKey)) {
|
||||
return null;
|
||||
} else {
|
||||
return statKey;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getStatKeyTranslation(String statKey) {
|
||||
String realKey = convertToNormalStatKey(statKey);
|
||||
if (realKey == null) {
|
||||
return "";
|
||||
}
|
||||
return languageKeys.getString(realKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param statistic the Statistic to get the Key for
|
||||
* @return the official Key from the NameSpacedKey for this Statistic,
|
||||
* or return null if no enum constant can be retrieved.
|
||||
*/
|
||||
public String getStatKey(@NotNull Statistic statistic) {
|
||||
if (statistic.getType() == Statistic.Type.UNTYPED) {
|
||||
return "stat.minecraft." + statNameKeys.get(statistic);
|
||||
@ -97,8 +190,11 @@ public final class LanguageKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the official Key from the NameSpacedKey for this entityType,
|
||||
or return null if no enum constant can be retrieved or entityType is UNKNOWN.*/
|
||||
/**
|
||||
* @param entity the EntityType to get the Key for
|
||||
* @return the official Key from the NameSpacedKey for this EntityType,
|
||||
* or return null if no enum constant can be retrieved or EntityType is UNKNOWN.
|
||||
*/
|
||||
public @Nullable String getEntityKey(EntityType entity) {
|
||||
if (entity == null || entity == EntityType.UNKNOWN) return null;
|
||||
else {
|
||||
@ -106,8 +202,11 @@ public final class LanguageKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the official Key from the NameSpacedKey for this item Material,
|
||||
or return null if no enum constant can be retrieved.*/
|
||||
/**
|
||||
* @param item the Material to get the Key for
|
||||
* @return the official Key from the NameSpacedKey for this item Material,
|
||||
* or return null if no enum constant can be retrieved.
|
||||
*/
|
||||
public @Nullable String getItemKey(Material item) {
|
||||
if (item == null) return null;
|
||||
else if (item.isBlock()) {
|
||||
@ -118,8 +217,11 @@ public final class LanguageKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the official Key from the NameSpacedKey for the block Material provided,
|
||||
or return null if no enum constant can be retrieved.*/
|
||||
/**
|
||||
* @param block the Material to get the Key for
|
||||
* @return the official Key from the NameSpacedKey for the block Material provided,
|
||||
* or return null if no enum constant can be retrieved.
|
||||
*/
|
||||
public @Nullable String getBlockKey(Material block) {
|
||||
if (block == null) return null;
|
||||
else if (block.toString().toLowerCase().contains("wall_banner")) { //replace wall_banner with regular banner, since there is no key for wall banners
|
||||
@ -132,6 +234,10 @@ public final class LanguageKeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param unit the Unit to get the Key for
|
||||
* @return "soundCategory.block" for Unit.Block, null otherwise
|
||||
*/
|
||||
public @Nullable String getUnitKey(Unit unit) {
|
||||
if (unit == Unit.BLOCK) {
|
||||
return "soundCategory.block";
|
@ -1,12 +1,15 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
package com.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Unit;
|
||||
import com.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.*/
|
||||
/**
|
||||
* A utility class that formats statistic numbers into something more readable.
|
||||
* It transforms numbers of {@link Unit.Type} Time, Damage, and Distance into numbers
|
||||
* that are easier to understand (for example: from ticks to hours) and adds commas
|
||||
* to break up large numbers.
|
||||
*/
|
||||
public final class NumberFormatter {
|
||||
|
||||
private final DecimalFormat format;
|
||||
@ -17,15 +20,19 @@ public final class NumberFormatter {
|
||||
format.setGroupingSize(3);
|
||||
}
|
||||
|
||||
/** Turns the input number into a more readable format depending on its type
|
||||
(number-of-times, time-, damage- or distance-based) according to the
|
||||
corresponding config settings, and adds commas in groups of 3.*/
|
||||
/**
|
||||
* 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 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. */
|
||||
/**
|
||||
* The unit of damage-based statistics is half a heart by default.
|
||||
* This method turns the number into hearts.
|
||||
*/
|
||||
public String formatDamageNumber(long number, Unit statUnit) { //7 statistics
|
||||
if (statUnit == Unit.HEART) {
|
||||
return format.format(Math.round(number / 2.0));
|
||||
@ -34,8 +41,12 @@ 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. */
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public String formatDistanceNumber(long number, Unit statUnit) { //15 statistics
|
||||
switch (statUnit) {
|
||||
case CM -> {
|
||||
@ -53,7 +64,11 @@ public final class NumberFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
/** The unit of time-based statistics is ticks by default.*/
|
||||
/** The unit of time-based statistics is ticks by default.
|
||||
*
|
||||
* @return a String with the form "1D 2H 3M 4S"
|
||||
* (depending on the Unit range selected)
|
||||
*/
|
||||
public String formatTimeNumber(long number, Unit biggestUnit, Unit smallestUnit) { //5 statistics
|
||||
if (number <= 0) {
|
||||
return "-";
|
@ -1,21 +1,27 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
package com.artemis.the.gr8.playerstats.msg.msgutils;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
|
||||
/** A small utility class that helps make enum constant names prettier for output in stat-messages.*/
|
||||
/**
|
||||
* A small utility class that helps make enum constant
|
||||
* names prettier for output in stat-messages.
|
||||
*/
|
||||
public final class StringUtils {
|
||||
|
||||
private StringUtils() {
|
||||
}
|
||||
|
||||
/** Replace "_" with " " and capitalize each first letter of the input.
|
||||
@param input String to prettify, case-insensitive*/
|
||||
/**
|
||||
* Replace "_" with " " and capitalize each first letter of the input.
|
||||
*
|
||||
* @param input String to prettify, case-insensitive
|
||||
*/
|
||||
public static String prettify(String input) {
|
||||
if (input == null) return null;
|
||||
StringBuilder capitals = new StringBuilder(input.toLowerCase());
|
||||
capitals.setCharAt(0, Character.toUpperCase(capitals.charAt(0)));
|
||||
while (capitals.indexOf("_") != -1) {
|
||||
MyLogger.replacingUnderscores();
|
||||
MyLogger.logHighLevelMsg("Replacing underscores and capitalizing names...");
|
||||
|
||||
int index = capitals.indexOf("_");
|
||||
capitals.setCharAt(index + 1, Character.toUpperCase(capitals.charAt(index + 1)));
|
@ -1,15 +1,18 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.reload;
|
||||
package com.artemis.the.gr8.playerstats.reload;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.UnixTimeHandler;
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.UnixTimeHandler;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RecursiveAction;
|
||||
|
||||
/** The action that is executed when a reload-command is triggered. */
|
||||
/**
|
||||
* The action that is executed when a reload-command is triggered.
|
||||
*/
|
||||
final class ReloadAction extends RecursiveAction {
|
||||
|
||||
private static int threshold;
|
||||
@ -21,10 +24,14 @@ final class ReloadAction extends RecursiveAction {
|
||||
private final int lastPlayedLimit;
|
||||
private final ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||
|
||||
/** Fills a ConcurrentHashMap with PlayerNames and UUIDs for all OfflinePlayers that should be included in statistic calculations.
|
||||
/**
|
||||
* Fills a ConcurrentHashMap with PlayerNames and UUIDs for all OfflinePlayers
|
||||
* that should be included in statistic calculations.
|
||||
*
|
||||
* @param players array of all OfflinePlayers (straight from Bukkit)
|
||||
* @param lastPlayedLimit whether to set a limit based on last-played-date
|
||||
* @param offlinePlayerUUIDs the ConcurrentHashMap to put resulting playerNames and UUIDs on
|
||||
* @param offlinePlayerUUIDs the ConcurrentHashMap to put playerNames and UUIDs in
|
||||
* @see OfflinePlayerHandler
|
||||
*/
|
||||
public ReloadAction(OfflinePlayer[] players,
|
||||
int lastPlayedLimit, ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
|
||||
@ -68,7 +75,7 @@ final class ReloadAction extends RecursiveAction {
|
||||
for (int i = start; i < end; i++) {
|
||||
OfflinePlayer player = players[i];
|
||||
String playerName = player.getName();
|
||||
MyLogger.actionRunning(Thread.currentThread().getName(), playerName, 1);
|
||||
MyLogger.actionRunning(Thread.currentThread().getName());
|
||||
if (playerName != null &&
|
||||
(lastPlayedLimit == 0 || UnixTimeHandler.hasPlayedSince(lastPlayedLimit, player.getLastPlayed()))) {
|
||||
offlinePlayerUUIDs.put(playerName, player.getUniqueId());
|
@ -1,15 +1,16 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.reload;
|
||||
package com.artemis.the.gr8.playerstats.reload;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ShareManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import com.gmail.artemis.the.gr8.playerstats.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.StatRetriever;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.ShareManager;
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatCalculator;
|
||||
import com.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@ -41,22 +42,26 @@ public final class ReloadThread extends Thread {
|
||||
sender = se;
|
||||
|
||||
this.setName("ReloadThread-" + reloadThreadID);
|
||||
MyLogger.threadCreated(this.getName());
|
||||
MyLogger.logHighLevelMsg(this.getName() + " created!");
|
||||
}
|
||||
|
||||
/** This method will perform a series of tasks. If a {@link StatThread} is still running,
|
||||
it will join the statThread and wait for it to finish. Then, it will reload the config,
|
||||
update the offlinePlayerList in the {@link OfflinePlayerHandler}, update the {@link DebugLevel},
|
||||
update the share-settings in {@link ShareManager} and topListSize-settings in {@link StatRetriever},
|
||||
and update the MessageBuilders in the {@link OutputManager}.*/
|
||||
/**
|
||||
* This method will perform a series of tasks. If a {@link StatThread}
|
||||
* is still running, it will join the statThread and wait for it to finish.
|
||||
* Then, it will reload the config, update the offlinePlayerList in the
|
||||
* {@link OfflinePlayerHandler}, update the {@link DebugLevel}, update
|
||||
* the share-settings in {@link ShareManager} and topListSize-settings
|
||||
* in {@link StatCalculator}, and update the MessageBuilders in the
|
||||
* {@link OutputManager}.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
long time = System.currentTimeMillis();
|
||||
MyLogger.threadStart(this.getName());
|
||||
MyLogger.logHighLevelMsg(this.getName() + " started!");
|
||||
|
||||
if (statThread != null && statThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.waitingForOtherThread(this.getName(), statThread.getName());
|
||||
MyLogger.logLowLevelMsg(this.getName() + ": Waiting for " + statThread.getName() + " to finish up...");
|
||||
statThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
MyLogger.logException(e, "ReloadThread", "run(), trying to join " + statThread.getName());
|
||||
@ -65,7 +70,7 @@ public final class ReloadThread extends Thread {
|
||||
}
|
||||
|
||||
if (reloadThreadID != 1 && config.reloadConfig()) { //during a reload
|
||||
MyLogger.logMsg("Reloading!", false);
|
||||
MyLogger.logLowLevelMsg("Reloading!");
|
||||
reloadEverything();
|
||||
|
||||
if (sender != null) {
|
||||
@ -81,6 +86,7 @@ public final class ReloadThread extends Thread {
|
||||
|
||||
private void reloadEverything() {
|
||||
MyLogger.setDebugLevel(config.getDebugLevel());
|
||||
LanguageKeyHandler.reloadFile();
|
||||
OutputManager.updateMessageBuilders();
|
||||
OfflinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
|
||||
ShareManager.updateSettings(config);
|
||||
@ -92,8 +98,8 @@ public final class ReloadThread extends Thread {
|
||||
OfflinePlayer[] offlinePlayers;
|
||||
if (config.whitelistOnly()) {
|
||||
offlinePlayers = Bukkit.getWhitelistedPlayers().toArray(OfflinePlayer[]::new);
|
||||
MyLogger.logTimeTaken("ReloadThread",
|
||||
"retrieved whitelist", time, DebugLevel.MEDIUM);
|
||||
MyLogger.logMediumLevelTask("ReloadThread",
|
||||
"retrieved whitelist", time);
|
||||
}
|
||||
else if (config.excludeBanned()) {
|
||||
if (Bukkit.getPluginManager().getPlugin("LiteBans") != null) {
|
||||
@ -107,13 +113,13 @@ public final class ReloadThread extends Thread {
|
||||
.parallel()
|
||||
.filter(offlinePlayer -> !bannedPlayers.contains(offlinePlayer)).toArray(OfflinePlayer[]::new);
|
||||
}
|
||||
MyLogger.logTimeTaken("ReloadThread",
|
||||
"retrieved banlist", time, DebugLevel.MEDIUM);
|
||||
MyLogger.logMediumLevelTask("ReloadThread",
|
||||
"retrieved banlist", time);
|
||||
}
|
||||
else {
|
||||
offlinePlayers = Bukkit.getOfflinePlayers();
|
||||
MyLogger.logTimeTaken("ReloadThread",
|
||||
"retrieved list of Offline Players", time, DebugLevel.MEDIUM);
|
||||
MyLogger.logMediumLevelTask("ReloadThread",
|
||||
"retrieved list of Offline Players", time);
|
||||
}
|
||||
|
||||
int size = offlinePlayers != null ? offlinePlayers.length : 16;
|
||||
@ -122,10 +128,10 @@ public final class ReloadThread extends Thread {
|
||||
ReloadAction task = new ReloadAction(offlinePlayers, config.getLastPlayedLimit(), playerMap);
|
||||
MyLogger.actionCreated((offlinePlayers != null) ? offlinePlayers.length : 0);
|
||||
ForkJoinPool.commonPool().invoke(task);
|
||||
MyLogger.actionFinished(1);
|
||||
MyLogger.actionFinished();
|
||||
|
||||
MyLogger.logTimeTaken("ReloadThread",
|
||||
("loaded " + playerMap.size() + " offline players"), time, DebugLevel.LOW);
|
||||
MyLogger.logLowLevelTask("ReloadThread",
|
||||
("loaded " + playerMap.size() + " offline players"), time);
|
||||
return playerMap;
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
package com.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
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.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
@ -11,30 +11,35 @@ import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RecursiveTask;
|
||||
|
||||
/** The action that is executed when a stat-command is triggered. */
|
||||
/**
|
||||
* 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 statRequest;
|
||||
private final RequestSettings requestSettings;
|
||||
private final ConcurrentHashMap<String, Integer> allStats;
|
||||
|
||||
/**
|
||||
* Gets the statistic numbers for all players whose name is on the list, puts them in a ConcurrentHashMap
|
||||
* using the default ForkJoinPool, and returns the ConcurrentHashMap when everything is done
|
||||
* Gets the statistic numbers for all players whose name is on
|
||||
* the list, puts them in a ConcurrentHashMap using the default
|
||||
* ForkJoinPool, and returns the ConcurrentHashMap when
|
||||
* everything is done.
|
||||
*
|
||||
* @param offlinePlayerHandler the OfflinePlayerHandler to convert playerNames into Players
|
||||
* @param playerNames ImmutableList of playerNames for players that should be included in stat calculations
|
||||
* @param statRequest a validated statRequest
|
||||
* @param requestSettings a validated requestSettings object
|
||||
* @param allStats the ConcurrentHashMap to put the results on
|
||||
*/
|
||||
public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList<String> playerNames, StatRequest statRequest, ConcurrentHashMap<String, Integer> allStats) {
|
||||
public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList<String> playerNames, RequestSettings requestSettings, ConcurrentHashMap<String, Integer> allStats) {
|
||||
threshold = ThreadManager.getTaskThreshold();
|
||||
|
||||
this.offlinePlayerHandler = offlinePlayerHandler;
|
||||
this.playerNames = playerNames;
|
||||
this.statRequest = statRequest;
|
||||
this.requestSettings = requestSettings;
|
||||
this.allStats = allStats;
|
||||
|
||||
MyLogger.subActionCreated(Thread.currentThread().getName());
|
||||
@ -46,8 +51,8 @@ final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>>
|
||||
return getStatsDirectly();
|
||||
}
|
||||
else {
|
||||
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);
|
||||
final StatAction subTask1 = new StatAction(offlinePlayerHandler, playerNames.subList(0, playerNames.size()/2), requestSettings, allStats);
|
||||
final StatAction subTask2 = new StatAction(offlinePlayerHandler, playerNames.subList(playerNames.size()/2, playerNames.size()), requestSettings, allStats);
|
||||
|
||||
//queue and compute all subtasks in the right order
|
||||
subTask1.fork();
|
||||
@ -61,19 +66,17 @@ final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>>
|
||||
if (iterator.hasNext()) {
|
||||
do {
|
||||
String playerName = iterator.next();
|
||||
MyLogger.actionRunning(Thread.currentThread().getName(), playerName, 2);
|
||||
MyLogger.actionRunning(Thread.currentThread().getName());
|
||||
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(playerName);
|
||||
if (player != null) {
|
||||
int statistic = 0;
|
||||
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) {
|
||||
allStats.put(playerName, statistic);
|
||||
}
|
||||
int statistic = 0;
|
||||
switch (requestSettings.getStatistic().getType()) {
|
||||
case UNTYPED -> statistic = player.getStatistic(requestSettings.getStatistic());
|
||||
case ENTITY -> statistic = player.getStatistic(requestSettings.getStatistic(), requestSettings.getEntity());
|
||||
case BLOCK -> statistic = player.getStatistic(requestSettings.getStatistic(), requestSettings.getBlock());
|
||||
case ITEM -> statistic = player.getStatistic(requestSettings.getStatistic(), requestSettings.getItem());
|
||||
}
|
||||
if (statistic > 0) {
|
||||
allStats.put(playerName, statistic);
|
||||
}
|
||||
} while (iterator.hasNext());
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class StatCalculator {
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
public StatCalculator(OfflinePlayerHandler offlinePlayerHandler) {
|
||||
this.offlinePlayerHandler = offlinePlayerHandler;
|
||||
}
|
||||
|
||||
public int getPlayerStat(RequestSettings requestSettings) {
|
||||
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(requestSettings.getPlayerName());
|
||||
return switch (requestSettings.getStatistic().getType()) {
|
||||
case UNTYPED -> player.getStatistic(requestSettings.getStatistic());
|
||||
case ENTITY -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getEntity());
|
||||
case BLOCK -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getBlock());
|
||||
case ITEM -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getItem());
|
||||
};
|
||||
}
|
||||
|
||||
public LinkedHashMap<String, Integer> getTopStats(RequestSettings requestSettings) {
|
||||
return getAllStatsAsync(requestSettings).entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||
.limit(requestSettings.getTopListSize())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
public long getServerStat(RequestSettings requestSettings) {
|
||||
List<Integer> numbers = getAllStatsAsync(requestSettings)
|
||||
.values()
|
||||
.parallelStream()
|
||||
.toList();
|
||||
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a bunch of worker pool threads to get the statistics for
|
||||
* all players that are stored in the {@link OfflinePlayerHandler}).
|
||||
*/
|
||||
private @NotNull ConcurrentHashMap<String, Integer> getAllStatsAsync(RequestSettings requestSettings) {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
ForkJoinPool commonPool = ForkJoinPool.commonPool();
|
||||
ConcurrentHashMap<String, Integer> allStats;
|
||||
|
||||
try {
|
||||
allStats = commonPool.invoke(getStatTask(requestSettings));
|
||||
} catch (ConcurrentModificationException e) {
|
||||
MyLogger.logWarning("The requestSettings could not be executed due to a ConcurrentModificationException. " +
|
||||
"This likely happened because Bukkit hasn't fully initialized all player-data yet. " +
|
||||
"Try again and it should be fine!");
|
||||
throw new ConcurrentModificationException(e.toString());
|
||||
}
|
||||
|
||||
MyLogger.actionFinished();
|
||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||
MyLogger.logMediumLevelTask("StatThread", "calculated all stats", time);
|
||||
|
||||
return allStats;
|
||||
}
|
||||
|
||||
private StatAction getStatTask(RequestSettings requestSettings) {
|
||||
int size = offlinePlayerHandler.getOfflinePlayerCount() != 0 ? offlinePlayerHandler.getOfflinePlayerCount() : 16;
|
||||
ConcurrentHashMap<String, Integer> allStats = new ConcurrentHashMap<>(size);
|
||||
ImmutableList<String> playerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
|
||||
|
||||
StatAction task = new StatAction(offlinePlayerHandler, playerNames, requestSettings, allStats);
|
||||
MyLogger.actionCreated(playerNames.size());
|
||||
|
||||
return task;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.ThreadManager;
|
||||
import com.artemis.the.gr8.playerstats.msg.OutputManager;
|
||||
import com.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.artemis.the.gr8.playerstats.statistic.request.RequestSettings;
|
||||
import com.artemis.the.gr8.playerstats.reload.ReloadThread;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Thread that is in charge of getting and calculating statistics.
|
||||
*/
|
||||
public final class StatThread extends Thread {
|
||||
|
||||
private static OutputManager outputManager;
|
||||
private static StatCalculator statCalculator;
|
||||
|
||||
private final ReloadThread reloadThread;
|
||||
private final RequestSettings requestSettings;
|
||||
|
||||
public StatThread(OutputManager m, StatCalculator t, int ID, RequestSettings s, @Nullable ReloadThread r) {
|
||||
outputManager = m;
|
||||
statCalculator = t;
|
||||
|
||||
reloadThread = r;
|
||||
requestSettings = s;
|
||||
|
||||
this.setName("StatThread-" + requestSettings.getCommandSender().getName() + "-" + ID);
|
||||
MyLogger.logHighLevelMsg(this.getName() + " created!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws IllegalStateException, NullPointerException {
|
||||
MyLogger.logHighLevelMsg(this.getName() + " started!");
|
||||
|
||||
if (requestSettings == null) {
|
||||
throw new NullPointerException("No statistic requestSettings was found!");
|
||||
}
|
||||
if (reloadThread != null && reloadThread.isAlive()) {
|
||||
try {
|
||||
MyLogger.logLowLevelMsg(this.getName() + ": Waiting for " + reloadThread.getName() + " to finish up...");
|
||||
outputManager.sendFeedbackMsg(requestSettings.getCommandSender(), StandardMessage.STILL_RELOADING);
|
||||
reloadThread.join();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
MyLogger.logException(e, "StatThread", "Trying to join " + reloadThread.getName());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
long lastCalc = ThreadManager.getLastRecordedCalcTime();
|
||||
if (lastCalc > 2000) {
|
||||
outputManager.sendFeedbackMsgWaitAMoment(requestSettings.getCommandSender(), lastCalc > 20000);
|
||||
}
|
||||
|
||||
Target selection = requestSettings.getTarget();
|
||||
try {
|
||||
TextComponent statResult = switch (selection) {
|
||||
case PLAYER -> outputManager.formatAndSavePlayerStat(requestSettings, statCalculator.getPlayerStat(requestSettings));
|
||||
case TOP -> outputManager.formatAndSaveTopStat(requestSettings, statCalculator.getTopStats(requestSettings));
|
||||
case SERVER -> outputManager.formatAndSaveServerStat(requestSettings, statCalculator.getServerStat(requestSettings));
|
||||
};
|
||||
outputManager.sendToCommandSender(requestSettings.getCommandSender(), statResult);
|
||||
}
|
||||
catch (ConcurrentModificationException e) {
|
||||
if (!requestSettings.isConsoleSender()) {
|
||||
outputManager.sendFeedbackMsg(requestSettings.getCommandSender(), StandardMessage.UNKNOWN_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.PlayerStatResult;
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class PlayerStatRequest extends StatRequest<Integer> implements RequestGenerator<Integer> {
|
||||
|
||||
private final RequestHandler requestHandler;
|
||||
|
||||
public PlayerStatRequest(RequestSettings request) {
|
||||
super(request);
|
||||
requestHandler = new RequestHandler(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatRequest untyped(@NotNull Statistic statistic) {
|
||||
RequestSettings completedRequest = requestHandler.untyped(statistic);
|
||||
return new PlayerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
RequestSettings completedRequest = requestHandler.blockOrItemType(statistic, material);
|
||||
return new PlayerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
RequestSettings completedRequest = requestHandler.entityType(statistic, entityType);
|
||||
return new PlayerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStatResult execute() {
|
||||
return getStatResult(super.requestSettings);
|
||||
}
|
||||
|
||||
private PlayerStatResult getStatResult(RequestSettings completedRequest) {
|
||||
int stat = Main
|
||||
.getStatCalculator()
|
||||
.getPlayerStat(completedRequest);
|
||||
|
||||
TextComponent prettyComponent = Main
|
||||
.getStatFormatter()
|
||||
.formatAndSavePlayerStat(completedRequest, stat);
|
||||
|
||||
String prettyString = ComponentUtils
|
||||
.getTranslatableComponentSerializer()
|
||||
.serialize(prettyComponent);
|
||||
|
||||
return new PlayerStatResult(stat, prettyComponent, prettyString);
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
|
||||
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class RequestHandler {
|
||||
|
||||
private final RequestSettings requestSettings;
|
||||
|
||||
public RequestHandler(RequestSettings request) {
|
||||
requestSettings = request;
|
||||
}
|
||||
|
||||
public static RequestSettings getBasicPlayerStatRequest(String playerName) {
|
||||
RequestSettings request = RequestSettings.getBasicAPIRequest();
|
||||
request.setTarget(Target.PLAYER);
|
||||
request.setPlayerName(playerName);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static RequestSettings getBasicServerStatRequest() {
|
||||
RequestSettings request = RequestSettings.getBasicAPIRequest();
|
||||
request.setTarget(Target.SERVER);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static RequestSettings getBasicTopStatRequest(int topListSize) {
|
||||
RequestSettings request = RequestSettings.getBasicAPIRequest();
|
||||
request.setTarget(Target.TOP);
|
||||
request.setTopListSize(topListSize != 0 ? topListSize : Main.getConfigHandler().getTopListMaxSize());
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sender the CommandSender that requested this specific statistic
|
||||
*/
|
||||
public static RequestSettings getBasicInternalStatRequest(CommandSender sender) {
|
||||
RequestSettings request = RequestSettings.getBasicRequest(sender);
|
||||
request.setTopListSize(Main.getConfigHandler().getTopListMaxSize());
|
||||
return request;
|
||||
}
|
||||
|
||||
public RequestSettings untyped(@NotNull Statistic statistic) throws IllegalArgumentException {
|
||||
if (statistic.getType() == Statistic.Type.UNTYPED) {
|
||||
requestSettings.setStatistic(statistic);
|
||||
return requestSettings;
|
||||
}
|
||||
throw new IllegalArgumentException("This statistic is not of Type.Untyped");
|
||||
}
|
||||
|
||||
public RequestSettings blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
|
||||
Statistic.Type type = statistic.getType();
|
||||
if (type == Statistic.Type.BLOCK && material.isBlock()) {
|
||||
requestSettings.setBlock(material);
|
||||
}
|
||||
else if (type == Statistic.Type.ITEM && material.isItem()){
|
||||
requestSettings.setItem(material);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Either this statistic is not of Type.Block or Type.Item, or no valid block or item has been provided");
|
||||
}
|
||||
requestSettings.setStatistic(statistic);
|
||||
requestSettings.setSubStatEntryName(material.toString());
|
||||
return requestSettings;
|
||||
}
|
||||
|
||||
public RequestSettings entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
|
||||
if (statistic.getType() == Statistic.Type.ENTITY) {
|
||||
requestSettings.setStatistic(statistic);
|
||||
requestSettings.setSubStatEntryName(entityType.toString());
|
||||
requestSettings.setEntity(entityType);
|
||||
return requestSettings;
|
||||
}
|
||||
throw new IllegalArgumentException("This statistic is not of Type.Entity");
|
||||
}
|
||||
|
||||
/**
|
||||
* This will create a {@link RequestSettings} object from the provided args,
|
||||
* with the requesting Player (or Console) as CommandSender. This CommandSender
|
||||
* will receive feedback messages if the RequestSettings could not be created.
|
||||
*
|
||||
* @param args an Array of args such as a CommandSender would put in Minecraft chat:
|
||||
* <ul>
|
||||
* <li> a <code>statName</code> (example: "mine_block")
|
||||
* <li> if applicable, a <code>subStatEntryName</code> (example: diorite)
|
||||
* <li> a <code>target</code> for this lookup: can be "top", "server", "player"
|
||||
* (or "me" to indicate the current CommandSender)
|
||||
* <li> if "player" was chosen, include a <code>playerName</code>
|
||||
* </ul>
|
||||
* @return the generated RequestSettings
|
||||
*/
|
||||
public RequestSettings getRequestFromArgs(String[] args) {
|
||||
EnumHandler enumHandler = Main.getEnumHandler();
|
||||
OfflinePlayerHandler offlinePlayerHandler = Main.getOfflinePlayerHandler();
|
||||
CommandSender sender = requestSettings.getCommandSender();
|
||||
|
||||
for (String arg : args) {
|
||||
//check for statName
|
||||
if (enumHandler.isStatistic(arg) && requestSettings.getStatistic() == null) {
|
||||
requestSettings.setStatistic(EnumHandler.getStatEnum(arg));
|
||||
}
|
||||
//check for subStatEntry and playerFlag
|
||||
else if (enumHandler.isSubStatEntry(arg)) {
|
||||
if (arg.equalsIgnoreCase("player") && !requestSettings.getPlayerFlag()) {
|
||||
requestSettings.setPlayerFlag(true);
|
||||
} else {
|
||||
if (requestSettings.getSubStatEntryName() == null) requestSettings.setSubStatEntryName(arg);
|
||||
}
|
||||
}
|
||||
//check for selection
|
||||
else if (arg.equalsIgnoreCase("top")) {
|
||||
requestSettings.setTarget(Target.TOP);
|
||||
} else if (arg.equalsIgnoreCase("server")) {
|
||||
requestSettings.setTarget(Target.SERVER);
|
||||
} else if (arg.equalsIgnoreCase("me")) {
|
||||
if (sender instanceof Player) {
|
||||
requestSettings.setPlayerName(sender.getName());
|
||||
requestSettings.setTarget(Target.PLAYER);
|
||||
} else if (sender instanceof ConsoleCommandSender) {
|
||||
requestSettings.setTarget(Target.SERVER);
|
||||
}
|
||||
} else if (offlinePlayerHandler.isRelevantPlayer(arg) && requestSettings.getPlayerName() == null) {
|
||||
requestSettings.setPlayerName(arg);
|
||||
requestSettings.setTarget(Target.PLAYER);
|
||||
}
|
||||
}
|
||||
patchRequest(requestSettings);
|
||||
return requestSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the RequestSettings object if needed: unpack the playerFlag
|
||||
* into a subStatEntry, try to retrieve the corresponding Enum Constant
|
||||
* for any relevant block/entity/item, and remove any unnecessary
|
||||
* subStatEntries.
|
||||
*/
|
||||
private void patchRequest(RequestSettings requestSettings) {
|
||||
if (requestSettings.getStatistic() != null) {
|
||||
Statistic.Type type = requestSettings.getStatistic().getType();
|
||||
|
||||
if (requestSettings.getPlayerFlag()) { //unpack the playerFlag
|
||||
if (type == Statistic.Type.ENTITY && requestSettings.getSubStatEntryName() == null) {
|
||||
requestSettings.setSubStatEntryName("player");
|
||||
} else {
|
||||
requestSettings.setTarget(Target.PLAYER);
|
||||
}
|
||||
}
|
||||
|
||||
String subStatEntry = requestSettings.getSubStatEntryName();
|
||||
switch (type) { //attempt to convert relevant subStatEntries into their corresponding Enum Constant
|
||||
case BLOCK -> {
|
||||
Material block = EnumHandler.getBlockEnum(subStatEntry);
|
||||
if (block != null) requestSettings.setBlock(block);
|
||||
}
|
||||
case ENTITY -> {
|
||||
EntityType entity = EnumHandler.getEntityEnum(subStatEntry);
|
||||
if (entity != null) requestSettings.setEntity(entity);
|
||||
}
|
||||
case ITEM -> {
|
||||
Material item = EnumHandler.getItemEnum(subStatEntry);
|
||||
if (item != null) requestSettings.setItem(item);
|
||||
}
|
||||
case UNTYPED -> { //remove unnecessary subStatEntries
|
||||
if (subStatEntry != null) requestSettings.setSubStatEntryName(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic.request;
|
||||
package com.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 com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
@ -10,26 +10,33 @@ 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 {
|
||||
/**
|
||||
* The object PlayerStats uses to calculate and format the requested
|
||||
* statistic. The settings in this RequestSettings object can be
|
||||
* configured from two different sources:
|
||||
* <br>- Internally: by PlayerStats itself when /stat is called,
|
||||
* using the args provided by the CommandSender.
|
||||
* <br>- Externally: through the API methods provided by the
|
||||
* {@link RequestGenerator} interface.
|
||||
* <br>
|
||||
* <br>For this RequestSettings object to be valid, the following
|
||||
* values need to be set:
|
||||
* <ul>
|
||||
* <li> a {@link Statistic} <code>statistic</code> </li>
|
||||
* <li> if this Statistic is not of {@link Statistic.Type} Untyped,
|
||||
* a <code>subStatEntryName</code> needs to be set, together with one
|
||||
* of the following values:
|
||||
* <br>- for Type.Block: a {@link Material} <code>blockMaterial</code>
|
||||
* <br>- for Type.Item: a {@link Material} <code>itemMaterial</code>
|
||||
* <br>- for Type.Entity: an {@link EntityType} <code>entityType</code>
|
||||
* <li> a {@link Target} <code>target</code> (defaults to Top)
|
||||
* <li> if the <code>target</code> is Target.Player, a
|
||||
* <code>playerName</code> needs to be added
|
||||
* </ul>
|
||||
*/
|
||||
public final class RequestSettings {
|
||||
|
||||
private final CommandSender sender;
|
||||
private boolean isAPIRequest;
|
||||
private Statistic statistic;
|
||||
private String playerName;
|
||||
private Target target;
|
||||
@ -41,37 +48,28 @@ public final class StatRequest {
|
||||
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
|
||||
/**
|
||||
* Create a new {@link RequestSettings} with default values:
|
||||
* <br>- CommandSender sender (provided)
|
||||
* <br>- Target target = {@link Target#TOP}
|
||||
* <br>- int topListSize = 10
|
||||
* <br>- boolean playerFlag = false
|
||||
* <br>- boolean isAPIRequest
|
||||
*
|
||||
* @param sender the CommandSender who prompted this RequestGenerator
|
||||
*/
|
||||
private StatRequest(@NotNull CommandSender sender, boolean isAPIRequest) {
|
||||
private RequestSettings(@NotNull CommandSender sender) {
|
||||
this.sender = sender;
|
||||
this.isAPIRequest = isAPIRequest;
|
||||
target = Target.TOP;
|
||||
playerFlag = false;
|
||||
}
|
||||
|
||||
public static StatRequest getBasicRequest(CommandSender sender) {
|
||||
return new StatRequest(sender, false);
|
||||
public static RequestSettings getBasicRequest(CommandSender sender) {
|
||||
return new RequestSettings(sender);
|
||||
}
|
||||
|
||||
public static StatRequest getBasicAPIRequest() {
|
||||
return new StatRequest(Bukkit.getConsoleSender(), true);
|
||||
}
|
||||
|
||||
public void setAPIRequest() {
|
||||
this.isAPIRequest = true;
|
||||
}
|
||||
|
||||
public boolean isAPIRequest() {
|
||||
return isAPIRequest;
|
||||
public static RequestSettings getBasicAPIRequest() {
|
||||
return new RequestSettings(Bukkit.getConsoleSender());
|
||||
}
|
||||
|
||||
public @NotNull CommandSender getCommandSender() {
|
@ -0,0 +1,60 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.ServerStatResult;
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ServerStatRequest extends StatRequest<Long> implements RequestGenerator<Long> {
|
||||
|
||||
private final RequestHandler requestHandler;
|
||||
|
||||
public ServerStatRequest(RequestSettings request) {
|
||||
super(request);
|
||||
requestHandler = new RequestHandler(requestSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest untyped(@NotNull Statistic statistic) {
|
||||
RequestSettings completedRequest = requestHandler.untyped(statistic);
|
||||
return new ServerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
RequestSettings completedRequest = requestHandler.blockOrItemType(statistic, material);
|
||||
return new ServerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
RequestSettings completedRequest = requestHandler.entityType(statistic, entityType);
|
||||
return new ServerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatResult execute() {
|
||||
return getStatResult(requestSettings);
|
||||
}
|
||||
|
||||
private ServerStatResult getStatResult(RequestSettings completedRequest) {
|
||||
long stat = Main
|
||||
.getStatCalculator()
|
||||
.getServerStat(completedRequest);
|
||||
|
||||
TextComponent prettyComponent = Main
|
||||
.getStatFormatter()
|
||||
.formatAndSaveServerStat(completedRequest, stat);
|
||||
|
||||
String prettyString = ComponentUtils
|
||||
.getTranslatableComponentSerializer()
|
||||
.serialize(prettyComponent);
|
||||
|
||||
return new ServerStatResult(stat, prettyComponent, prettyString);
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.PlayerStats;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.StatResult;
|
||||
import com.artemis.the.gr8.playerstats.enums.Target;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Holds all the information PlayerStats needs to perform
|
||||
* a lookup, and can be executed to get the results. Calling
|
||||
* {@link #execute()} on a Top- or ServerRequest can take some
|
||||
* time (especially if there is a substantial amount of
|
||||
* OfflinePlayers on this particular server), so I strongly
|
||||
* advice you to call this asynchronously!
|
||||
*/
|
||||
public abstract class StatRequest<T> {
|
||||
|
||||
protected final RequestSettings requestSettings;
|
||||
|
||||
protected StatRequest(RequestSettings request) {
|
||||
requestSettings = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes this StatRequest. For a Top- or ServerRequest, this can
|
||||
* take some time!
|
||||
*
|
||||
* @return a StatResult containing the value of this lookup, both as
|
||||
* numerical value and as formatted message
|
||||
* @see PlayerStats
|
||||
* @see StatResult
|
||||
*/
|
||||
public abstract StatResult<T> execute();
|
||||
|
||||
/**
|
||||
* Gets the Statistic that calling {@link #execute()} will calculate
|
||||
* the data for.
|
||||
* @return the Statistic
|
||||
*/
|
||||
public Statistic getStatisticSetting() {
|
||||
return requestSettings.getStatistic();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Statistic setting for this StatRequest is of Type.Block,
|
||||
* this will get the Material that was set.
|
||||
*
|
||||
* @return a Material for which #isBlock is true, or null if no
|
||||
* Material was set
|
||||
*/
|
||||
public @Nullable Material getBlockSetting() {
|
||||
return requestSettings.getBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Statistic setting for this StatRequest is of Type.Item,
|
||||
* this will get the Material that was set.
|
||||
*
|
||||
* @return a Material for which #isItem is true, or null if no
|
||||
* Material was set
|
||||
*/
|
||||
public @Nullable Material getItemSetting() {
|
||||
return requestSettings.getItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Statistic setting for this StatRequest is of Type.Entity,
|
||||
* this will get the EntityType that was set.
|
||||
*
|
||||
* @return an EntityType, or null if no EntityType was set
|
||||
*/
|
||||
public @Nullable EntityType getEntitySetting() {
|
||||
return requestSettings.getEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Target that will be used when calling {@link #execute()}.
|
||||
*
|
||||
* @return the Target for this lookup (either Player, Server or Top)
|
||||
*/
|
||||
public Target getTargetSetting() {
|
||||
return requestSettings.getTarget();
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.request;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.Main;
|
||||
import com.artemis.the.gr8.playerstats.statistic.result.TopStatResult;
|
||||
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public final class TopStatRequest extends StatRequest<LinkedHashMap<String, Integer>> implements RequestGenerator<LinkedHashMap<String, Integer>> {
|
||||
|
||||
private final RequestHandler requestHandler;
|
||||
|
||||
public TopStatRequest(RequestSettings request) {
|
||||
super(request);
|
||||
requestHandler = new RequestHandler(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest untyped(@NotNull Statistic statistic) {
|
||||
RequestSettings completedRequest = requestHandler.untyped(statistic);
|
||||
return new TopStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
RequestSettings completedRequest = requestHandler.blockOrItemType(statistic, material);
|
||||
return new TopStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
RequestSettings completedRequest = requestHandler.entityType(statistic, entityType);
|
||||
return new TopStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatResult execute() {
|
||||
return getStatResult(super.requestSettings);
|
||||
}
|
||||
|
||||
private TopStatResult getStatResult(RequestSettings completedRequest) {
|
||||
LinkedHashMap<String, Integer> stat = Main
|
||||
.getStatCalculator()
|
||||
.getTopStats(completedRequest);
|
||||
|
||||
TextComponent prettyComponent = Main
|
||||
.getStatFormatter()
|
||||
.formatAndSaveTopStat(completedRequest, stat);
|
||||
|
||||
String prettyString = ComponentUtils
|
||||
.getTranslatableComponentSerializer()
|
||||
.serialize(prettyComponent);
|
||||
|
||||
return new TopStatResult(stat, prettyComponent, prettyString);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.msg.components.ComponentUtils;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
/**
|
||||
* This Record is used to store stat-results internally,
|
||||
* so Players can share them by clicking a share-button.
|
||||
*/
|
||||
public record InternalStatResult(String executorName, TextComponent formattedValue, int ID) implements StatResult<Integer> {
|
||||
|
||||
/**
|
||||
* Gets the ID number for this StatResult. Unlike for the
|
||||
* other {@link StatResult} implementations, this one does
|
||||
* not return the actual statistic data, because this
|
||||
* implementation is meant for internal saving-and-sharing only.
|
||||
* This method is only for Interface-consistency,
|
||||
* InternalStatResult#ID is better.
|
||||
*
|
||||
@return Integer that represents this StatResult's ID number
|
||||
*/
|
||||
@Override
|
||||
public Integer getNumericalValue() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedString() {
|
||||
return ComponentUtils.getTranslatableComponentSerializer()
|
||||
.serialize(formattedValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public record PlayerStatResult(int value, TextComponent formattedComponent, String formattedString) implements StatResult<Integer> {
|
||||
|
||||
@Override
|
||||
public Integer getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedString() {
|
||||
return formattedString;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public record ServerStatResult(long value, TextComponent formattedComponent, String formattedString) implements StatResult<Long> {
|
||||
|
||||
@Override
|
||||
public Long getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedString() {
|
||||
return formattedString;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.api.ApiFormatter;
|
||||
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 through the PlayerStatsAPI. Information
|
||||
* on how to get and use the BukkitAudiences object can be found on
|
||||
* <a href="https://docs.adventure.kyori.net/platform/bukkit.html">Adventure's website</a>.
|
||||
*
|
||||
* <p>You can also use the provided {@link #getFormattedString()} method to get the
|
||||
* same information in String-format. Don't use Adventure's <code>#content()</code>
|
||||
* or <code>#toString()</code> methods on the Components - those won't get the actual
|
||||
* message. And finally, if you want the results to be formatted differently,
|
||||
* you can get an instance of the {@link ApiFormatter}.
|
||||
*/
|
||||
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
|
||||
* StatResult stores.
|
||||
|
||||
* @return a {@code TextComponent} message containing the formatted number.
|
||||
* This message follows the same style/color/language settings that are
|
||||
* specified in the PlayerStats config. See class description for more
|
||||
* information.
|
||||
* @see StatResult
|
||||
*/
|
||||
TextComponent getFormattedTextComponent();
|
||||
|
||||
/**
|
||||
* Gets the formatted message for the completed stat-lookup this
|
||||
* StatResult stores.
|
||||
|
||||
* @return a String message containing the formatted number. This message
|
||||
* follows the same style and color settings that are specified in the
|
||||
* PlayerStats config, but it is not translatable (it is always plain English).
|
||||
* See class description for more information.
|
||||
* @see StatResult
|
||||
*/
|
||||
String getFormattedString();
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.artemis.the.gr8.playerstats.statistic.result;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public record TopStatResult(LinkedHashMap<String, Integer> value, TextComponent formattedComponent, String formattedString) implements StatResult<LinkedHashMap<String,Integer>> {
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<String, Integer> getNumericalValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextComponent getFormattedTextComponent() {
|
||||
return formattedComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedString() {
|
||||
return formattedString;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.utils;
|
||||
package com.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
@ -12,11 +12,13 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/** This class deals with Bukkit Enumerators. It holds private lists of all
|
||||
block-, item-, entity- and statistic-names, and has one big list of all
|
||||
possible sub-statistic-entries (block/item/entity). It can give the names
|
||||
of all aforementioned enums, check if something is a valid enum constant,
|
||||
and turn a name into its corresponding enum constant. */
|
||||
/**
|
||||
* This class deals with Bukkit Enumerators. It holds private lists of all
|
||||
* block-, item-, entity- and statistic-names, and has one big list of all
|
||||
* possible sub-statistic-entries (block/item/entity). It can give the names
|
||||
* of all aforementioned enums, check if something is a valid enum constant,
|
||||
* and turn a name into its corresponding enum constant.
|
||||
*/
|
||||
public final class EnumHandler {
|
||||
|
||||
private static List<String> blockNames;
|
||||
@ -28,24 +30,40 @@ public final class EnumHandler {
|
||||
prepareLists();
|
||||
}
|
||||
|
||||
/** Returns all block-names in lowercase */
|
||||
/**
|
||||
* Returns all block-names in lowercase.
|
||||
*
|
||||
* @return the List
|
||||
*/
|
||||
public List<String> getBlockNames() {
|
||||
return blockNames;
|
||||
}
|
||||
|
||||
/** Returns all item-names in lowercase*/
|
||||
/**
|
||||
* Returns all item-names in lowercase.
|
||||
*
|
||||
* @return the List
|
||||
*/
|
||||
public List<String> getItemNames() {
|
||||
return itemNames;
|
||||
}
|
||||
|
||||
/** Returns all statistic-names in lowercase */
|
||||
/**
|
||||
* Returns all statistic-names in lowercase.
|
||||
*
|
||||
* @return the List
|
||||
*/
|
||||
public List<String> getStatNames() {
|
||||
return statNames;
|
||||
}
|
||||
|
||||
/** Returns the corresponding Material enum constant for an itemName
|
||||
@param itemName String, case-insensitive
|
||||
@return Material enum constant, uppercase */
|
||||
/**
|
||||
* Returns the corresponding Material enum constant for an itemName.
|
||||
*
|
||||
* @param itemName String (case-insensitive)
|
||||
* @return Material enum constant (uppercase), or null if none
|
||||
* can be found
|
||||
*/
|
||||
public static @Nullable Material getItemEnum(String itemName) {
|
||||
if (itemName == null) return null;
|
||||
|
||||
@ -53,9 +71,13 @@ public final class EnumHandler {
|
||||
return (item != null && item.isItem()) ? item : null;
|
||||
}
|
||||
|
||||
/** Returns the corresponding EntityType enum constant for an entityName
|
||||
@param entityName String, case-insensitive
|
||||
@return EntityType enum constant, uppercase */
|
||||
/**
|
||||
* Returns the corresponding EntityType enum constant for an entityName.
|
||||
*
|
||||
* @param entityName String (case-insensitive)
|
||||
* @return EntityType enum constant (uppercase), or null if none
|
||||
* can be found
|
||||
*/
|
||||
public static @Nullable EntityType getEntityEnum(String entityName) {
|
||||
try {
|
||||
return EntityType.valueOf(entityName.toUpperCase());
|
||||
@ -65,9 +87,13 @@ public final class EnumHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the corresponding Material enum constant for a materialName
|
||||
@param materialName String, case-insensitive
|
||||
@return Material enum constant, uppercase */
|
||||
/**
|
||||
* Returns the corresponding Material enum constant for a materialName.
|
||||
*
|
||||
* @param materialName String (case-insensitive)
|
||||
* @return Material enum constant (uppercase), or null if none
|
||||
* can be found
|
||||
*/
|
||||
public static @Nullable Material getBlockEnum(String materialName) {
|
||||
if (materialName == null) return null;
|
||||
|
||||
@ -75,8 +101,12 @@ public final class EnumHandler {
|
||||
return (block != null && block.isBlock()) ? block : null;
|
||||
}
|
||||
|
||||
/** Returns the statistic enum constant, or null if that failed.
|
||||
@param statName String, case-insensitive */
|
||||
/**
|
||||
* Returns the statistic enum constant, or null if that failed.
|
||||
*
|
||||
* @param statName String (case-insensitive)
|
||||
* @return the Statistic enum constant, or null
|
||||
*/
|
||||
public static @Nullable Statistic getStatEnum(@NotNull String statName) {
|
||||
try {
|
||||
return Statistic.valueOf(statName.toUpperCase());
|
||||
@ -86,24 +116,58 @@ public final class EnumHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if string is a valid statistic
|
||||
@param statName String, case-insensitive */
|
||||
/**
|
||||
* Checks if string is a valid {@link Statistic}.
|
||||
*
|
||||
* @param statName the String to check (case-insensitive)
|
||||
* @return true if this String is a valid Statistic
|
||||
*/
|
||||
public boolean isStatistic(@NotNull String statName) {
|
||||
return statNames.contains(statName.toLowerCase());
|
||||
}
|
||||
|
||||
/** Checks whether the given String equals the name of an entity-type statistic. */
|
||||
/**
|
||||
* Checks whether the given String equals the name of a
|
||||
* {@link Statistic} of Type.Entity.
|
||||
*
|
||||
* @param statName the String to check (case-insensitive)
|
||||
* @return true if this String is a Statistic of Type.Entity
|
||||
*/
|
||||
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*/
|
||||
/**
|
||||
* Checks if this statistic is a subStatEntry, meaning it is a block,
|
||||
* item or entity.
|
||||
*
|
||||
* @param statName the String to check (case-insensitive)
|
||||
* @return true if this String is a Statistic that is not
|
||||
* of Type.Untyped
|
||||
*/
|
||||
public boolean isSubStatEntry(@NotNull String statName) {
|
||||
return subStatNames.contains(statName.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the given Statistic.Type
|
||||
*
|
||||
* @param statType the Type of the Statistic to check
|
||||
* @return "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)
|
@ -0,0 +1,169 @@
|
||||
package com.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
import com.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* The PlayerStats Logger
|
||||
*/
|
||||
public final class MyLogger {
|
||||
|
||||
private static final Logger logger;
|
||||
private static DebugLevel debugLevel;
|
||||
|
||||
private static ConcurrentHashMap<String, Integer> threadNames;
|
||||
|
||||
static {
|
||||
Plugin plugin = Bukkit.getPluginManager().getPlugin("PlayerStats");
|
||||
logger = (plugin != null) ? plugin.getLogger() : Bukkit.getLogger();
|
||||
debugLevel = DebugLevel.LOW;
|
||||
threadNames = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
private MyLogger() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired debugging level.
|
||||
* <br>1 = low (only show unexpected errors)
|
||||
* <br>2 = medium (detail all encountered exceptions, log main tasks and show time taken)
|
||||
* <br>3 = high (log all tasks and time taken)
|
||||
* <br>Default: 1
|
||||
*/
|
||||
public static void setDebugLevel(int level) {
|
||||
if (level == 2) {
|
||||
debugLevel = DebugLevel.MEDIUM;
|
||||
}
|
||||
else if (level == 3) {
|
||||
debugLevel = DebugLevel.HIGH;
|
||||
}
|
||||
else {
|
||||
debugLevel = DebugLevel.LOW;
|
||||
}
|
||||
}
|
||||
|
||||
public static void logLowLevelMsg(String content) {
|
||||
logger.info(content);
|
||||
}
|
||||
|
||||
public static void logMediumLevelMsg(String content) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
logger.info(content);
|
||||
}
|
||||
}
|
||||
|
||||
public static void logHighLevelMsg(String content) {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
logger.info(content);
|
||||
}
|
||||
}
|
||||
|
||||
public static void logWarning(String content) {
|
||||
logger.warning(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param exception The encountered exception
|
||||
* @param caughtBy The name of the class that caught the exception
|
||||
* @param additionalInfo e.g. the method-name or line where the
|
||||
* exception is caught
|
||||
*/
|
||||
public static void logException(@NotNull Exception exception, String caughtBy, @Nullable String additionalInfo) {
|
||||
String extraInfo = (additionalInfo != null) ? " [" + additionalInfo + "]" : "";
|
||||
String info = " (" + caughtBy + extraInfo + ")";
|
||||
|
||||
logger.warning(exception + info);
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If DebugLevel is MEDIUM or HIGH, output to console that an
|
||||
* action has started.
|
||||
*
|
||||
* @param taskLength Length of the action (in terms of
|
||||
* units-to-process)
|
||||
*/
|
||||
public static void actionCreated(int taskLength) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
threadNames = new ConcurrentHashMap<>();
|
||||
logger.info("Initial Action created for " + taskLength + " Players. Processing...");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally save the name of the executing thread for later
|
||||
* logging of this action. The list of names is reset upon the
|
||||
* start of every new action.
|
||||
*
|
||||
* @param threadName Name of the executing thread
|
||||
*/
|
||||
public static void subActionCreated(String threadName) {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
if (!threadNames.containsKey(threadName)) {
|
||||
threadNames.put(threadName, threadNames.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally save the name of the executing thread for logging.
|
||||
*
|
||||
* @param threadName Name of the executing thread
|
||||
*/
|
||||
public static void actionRunning(String threadName) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
if (!threadNames.containsKey(threadName)) {
|
||||
threadNames.put(threadName, threadNames.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output to console that an action has finished if DebugLevel is
|
||||
* MEDIUM or higher. If DebugLevel is HIGH, also output the names
|
||||
* of the threads that were used.
|
||||
*/
|
||||
public static void actionFinished() {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
logger.info("Finished Recursive Action! In total " +
|
||||
threadNames.size() + " Threads were used");
|
||||
}
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
logger.info(Collections.list(threadNames.keys()).toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void logMediumLevelTask(String className, String methodName, long startTime) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
printTime(className, methodName, startTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static void logLowLevelTask(String className, String methodName, long startTime) {
|
||||
printTime(className, methodName, startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output to console how long a certain task has taken.
|
||||
*
|
||||
* @param className Name of the class executing the task
|
||||
* @param methodName Name or description of the task
|
||||
* @param startTime Timestamp marking the beginning of the task
|
||||
*/
|
||||
private static void printTime(String className, String methodName, long startTime) {
|
||||
logger.info(className + " " + methodName + ": " + (System.currentTimeMillis() - startTime) + "ms");
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A utility class that deals with OfflinePlayers. It stores a list
|
||||
* of all OfflinePlayer-names that need to be included in statistic
|
||||
* calculations, and can retrieve the corresponding OfflinePlayer
|
||||
* object for a given player-name.
|
||||
*/
|
||||
public final class OfflinePlayerHandler {
|
||||
|
||||
private static ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||
private static ArrayList<String> playerNames;
|
||||
|
||||
public OfflinePlayerHandler() {
|
||||
offlinePlayerUUIDs = new ConcurrentHashMap<>();
|
||||
playerNames = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new HashMap that stores the players to include in stat calculations.
|
||||
* This HashMap is stored as a private variable in OfflinePlayerHandler.
|
||||
*
|
||||
* @param playerList ConcurrentHashMap with keys: playerNames and values: UUIDs
|
||||
*/
|
||||
public static void updateOfflinePlayerList(ConcurrentHashMap<String, UUID> playerList) {
|
||||
offlinePlayerUUIDs = playerList;
|
||||
playerNames = Collections.list(offlinePlayerUUIDs.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given playerName is on the private HashMap of players
|
||||
* that should be included in statistic calculations.
|
||||
*
|
||||
* @param playerName String (case-sensitive)
|
||||
* @return true if this Player should be included in calculations
|
||||
*/
|
||||
public boolean isRelevantPlayer(String playerName) {
|
||||
return offlinePlayerUUIDs.containsKey(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of OfflinePlayers that are included in
|
||||
* statistic calculations.
|
||||
*
|
||||
* @return the number of included OfflinePlayers
|
||||
*/
|
||||
public int getOfflinePlayerCount() {
|
||||
return offlinePlayerUUIDs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an ArrayList of names from all OfflinePlayers that should
|
||||
* be included in statistic calculations.
|
||||
*
|
||||
* @return the ArrayList
|
||||
*/
|
||||
public ArrayList<String> getOfflinePlayerNames() {
|
||||
return playerNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the playerName to get the player's UUID from a private HashMap,
|
||||
* and uses the UUID to get the corresponding OfflinePlayer Object.
|
||||
*
|
||||
* @param playerName name of the target player (case-sensitive)
|
||||
* @return OfflinePlayer
|
||||
* @throws IllegalArgumentException if this player is not on the list
|
||||
* of players that should be included in statistic calculations
|
||||
*/
|
||||
public OfflinePlayer getOfflinePlayer(String playerName) throws IllegalArgumentException {
|
||||
if (offlinePlayerUUIDs.get(playerName) != null) {
|
||||
return Bukkit.getOfflinePlayer(offlinePlayerUUIDs.get(playerName));
|
||||
}
|
||||
else {
|
||||
MyLogger.logWarning("Cannot calculate statistics for player-name: " + playerName +
|
||||
"! Double-check if the name is spelled correctly (including capital letters), " +
|
||||
"or if any of your config settings exclude them");
|
||||
throw new IllegalArgumentException("Cannot convert this player-name into a valid Player to calculate statistics for");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
/**
|
||||
* A small utility class that calculates with unix time.
|
||||
*/
|
||||
public final class UnixTimeHandler {
|
||||
|
||||
private UnixTimeHandler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates whether a player has played recently enough
|
||||
* to fall within the lastPlayedLimit. If lastPlayedLimit == 0,
|
||||
* this always returns true (since there is no limit).
|
||||
*
|
||||
* @param lastPlayed a long that represents the amount of
|
||||
* milliseconds between the unix start point
|
||||
* and the time this player last joined
|
||||
* @param lastPlayedLimit a long that represents the maximum-
|
||||
* number-of-days-since-last-joined
|
||||
*/
|
||||
public static boolean hasPlayedSince(long lastPlayedLimit, long lastPlayed) {
|
||||
long maxLastPlayed = System.currentTimeMillis() - lastPlayedLimit * 24 * 60 * 60 * 1000;
|
||||
return lastPlayedLimit == 0 || lastPlayed >= maxLastPlayed;
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
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;
|
||||
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.StatRetriever;
|
||||
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 final class Main extends JavaPlugin {
|
||||
|
||||
private static BukkitAudiences 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() {
|
||||
new Metrics(this, 15923);
|
||||
|
||||
//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));
|
||||
statcmd.setTabCompleter(new TabCompleter(enumHandler, offlinePlayerHandler));
|
||||
}
|
||||
PluginCommand reloadcmd = this.getCommand("statisticreload");
|
||||
if (reloadcmd != null) reloadcmd.setExecutor(new ReloadCommand(threadManager));
|
||||
PluginCommand sharecmd = this.getCommand("statisticshare");
|
||||
if (sharecmd != null) sharecmd.setExecutor(new ShareCommand(shareManager, outputManager));
|
||||
|
||||
//register the listener
|
||||
Bukkit.getPluginManager().registerEvents(new JoinListener(threadManager), this);
|
||||
|
||||
//finish up
|
||||
this.getLogger().info("Enabled PlayerStats!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (adventure != null) {
|
||||
adventure.close();
|
||||
adventure = null;
|
||||
}
|
||||
this.getLogger().info("Disabled PlayerStats!");
|
||||
}
|
||||
|
||||
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);
|
||||
StatRetriever statRetriever = new StatRetriever(offlinePlayerHandler);
|
||||
threadManager = new ThreadManager(config, statRetriever, outputManager);
|
||||
|
||||
playerStatsAPI = new PlayerStatsAPI(statRetriever, outputManager, offlinePlayerHandler);
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
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.statistic.StatRetriever;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/** The ThreadManager is in charge of the Threads that PlayerStats can utilize.
|
||||
It keeps track of past and currently active Threads, to ensure a Player cannot
|
||||
start multiple Threads at the same time (thereby limiting them to one stat-lookup at a time).
|
||||
It also passes appropriate references along to the {@link StatThread} or {@link ReloadThread},
|
||||
to ensure those will never run at the same time. */
|
||||
public final class ThreadManager {
|
||||
|
||||
private final static int threshold = 10;
|
||||
private int statThreadID;
|
||||
private int reloadThreadID;
|
||||
|
||||
private static ConfigHandler config;
|
||||
private static OutputManager outputManager;
|
||||
private static StatRetriever statRetriever;
|
||||
|
||||
private ReloadThread lastActiveReloadThread;
|
||||
private StatThread lastActiveStatThread;
|
||||
private final HashMap<String, Thread> statThreads;
|
||||
private static long lastRecordedCalcTime;
|
||||
|
||||
public ThreadManager(ConfigHandler config, StatRetriever statRetriever, OutputManager outputManager) {
|
||||
ThreadManager.config = config;
|
||||
ThreadManager.outputManager = outputManager;
|
||||
ThreadManager.statRetriever = statRetriever;
|
||||
|
||||
statThreads = new HashMap<>();
|
||||
statThreadID = 0;
|
||||
reloadThreadID = 0;
|
||||
lastRecordedCalcTime = 0;
|
||||
|
||||
startReloadThread(null);
|
||||
}
|
||||
|
||||
public static int getTaskThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public void startReloadThread(CommandSender sender) {
|
||||
if (lastActiveReloadThread == null || !lastActiveReloadThread.isAlive()) {
|
||||
reloadThreadID += 1;
|
||||
|
||||
lastActiveReloadThread = new ReloadThread(config, outputManager, reloadThreadID, lastActiveStatThread, sender);
|
||||
lastActiveReloadThread.start();
|
||||
}
|
||||
else {
|
||||
MyLogger.threadAlreadyRunning(lastActiveReloadThread.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void startStatThread(StatRequest statRequest) {
|
||||
statThreadID += 1;
|
||||
String cmdSender = statRequest.getCommandSender().getName();
|
||||
|
||||
if (config.limitStatRequests() && statThreads.containsKey(cmdSender)) {
|
||||
Thread runningThread = statThreads.get(cmdSender);
|
||||
if (runningThread.isAlive()) {
|
||||
outputManager.sendFeedbackMsg(statRequest.getCommandSender(), StandardMessage.REQUEST_ALREADY_RUNNING);
|
||||
} else {
|
||||
startNewStatThread(statRequest);
|
||||
}
|
||||
} else {
|
||||
startNewStatThread(statRequest);
|
||||
}
|
||||
}
|
||||
|
||||
/** Store the duration in milliseconds of the last top-stat-lookup
|
||||
(or of loading the offline-player-list if no look-ups have been done yet). */
|
||||
public static void recordCalcTime(long time) {
|
||||
lastRecordedCalcTime = time;
|
||||
}
|
||||
|
||||
/** Returns the duration in milliseconds of the last top-stat-lookup
|
||||
(or of loading the offline-player-list if no look-ups have been done yet). */
|
||||
public static long getLastRecordedCalcTime() {
|
||||
return lastRecordedCalcTime;
|
||||
}
|
||||
|
||||
private void startNewStatThread(StatRequest statRequest) {
|
||||
lastActiveStatThread = new StatThread(outputManager, statRetriever, statThreadID, statRequest, lastActiveReloadThread);
|
||||
statThreads.put(statRequest.getCommandSender().getName(), lastActiveStatThread);
|
||||
lastActiveStatThread.start();
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
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);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
||||
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();
|
||||
}
|
||||
|
||||
StatManager getStatManager();
|
||||
|
||||
Formatter getFormatter();
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRetriever;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.*;
|
||||
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
|
||||
|
||||
import static org.jetbrains.annotations.ApiStatus.Internal;
|
||||
|
||||
/** The implementation of the API Interface */
|
||||
public final class PlayerStatsAPI implements PlayerStats, StatManager {
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
private static StatRetriever statRetriever;
|
||||
private static StatFormatter statFormatter;
|
||||
|
||||
@Internal
|
||||
public PlayerStatsAPI(StatRetriever stat, StatFormatter format, OfflinePlayerHandler offlinePlayers) {
|
||||
statRetriever = stat;
|
||||
statFormatter = format;
|
||||
offlinePlayerHandler = offlinePlayers;
|
||||
}
|
||||
|
||||
static StatRetriever statCalculator() {
|
||||
return statRetriever;
|
||||
}
|
||||
|
||||
static StatFormatter statFormatter() {
|
||||
return statFormatter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter getFormatter() {
|
||||
return statFormatter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatManager getStatManager() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestGenerator<Integer> getPlayerStat(String playerName) {
|
||||
StatRequest request = StatRequestHandler.getBasicPlayerStatRequest(playerName);
|
||||
return new PlayerStatRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerStatRequest calculateServerStat() {
|
||||
StatRequest request = StatRequestHandler.getBasicServerStatRequest();
|
||||
return new ServerStatRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest calculateTopStat(int topListSize) {
|
||||
StatRequest request = StatRequestHandler.getBasicTopStatRequest(topListSize);
|
||||
return new TopStatRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest calculateTotalTopStatList() {
|
||||
int playerCount = offlinePlayerHandler.getOfflinePlayerCount();
|
||||
return calculateTopStat(playerCount);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRetriever;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.result.StatResult;
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.request.StatRequest;
|
||||
|
||||
/** 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> {
|
||||
|
||||
static StatRetriever getStatCalculator() {
|
||||
return PlayerStatsAPI.statCalculator();
|
||||
}
|
||||
|
||||
static StatFormatter getStatFormatter() {
|
||||
return PlayerStatsAPI.statFormatter();
|
||||
}
|
||||
|
||||
StatResult<T> execute();
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRetriever;
|
||||
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 StatRetriever}
|
||||
to get the desired statistic data.*/
|
||||
public interface RequestGenerator<T> {
|
||||
|
||||
/** 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*/
|
||||
RequestExecutor<T> 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>) */
|
||||
RequestExecutor<T> 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*/
|
||||
RequestExecutor<T> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException;
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
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);
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.api;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public interface StatManager {
|
||||
|
||||
/** 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*/
|
||||
RequestGenerator<Integer> getPlayerStat(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*/
|
||||
RequestGenerator<Long> calculateServerStat();
|
||||
|
||||
/** 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*/
|
||||
RequestGenerator<LinkedHashMap<String, Integer>> calculateTopStat(int topListSize);
|
||||
|
||||
RequestGenerator<LinkedHashMap<String, Integer>> calculateTotalTopStatList();
|
||||
}
|
@ -1,414 +0,0 @@
|
||||
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;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class ConfigHandler {
|
||||
|
||||
private static Main plugin;
|
||||
private static int configVersion;
|
||||
|
||||
private File configFile;
|
||||
private FileConfiguration config;
|
||||
|
||||
public ConfigHandler(Main plugin) {
|
||||
ConfigHandler.plugin = plugin;
|
||||
configVersion = 6;
|
||||
|
||||
saveDefaultConfig();
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
checkConfigVersion();
|
||||
|
||||
MyLogger.setDebugLevel(getDebugLevel());
|
||||
}
|
||||
|
||||
/** 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);
|
||||
reloadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a config file if none exists yet (from the config.yml in the plugin's resources). */
|
||||
private void saveDefaultConfig() {
|
||||
config = plugin.getConfig();
|
||||
plugin.saveDefaultConfig();
|
||||
configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||
}
|
||||
|
||||
/** Reloads the config from file, or creates a new file with default values if there is none.
|
||||
Also reads the value for debug-level and passes it on to {@link MyLogger}. */
|
||||
public boolean reloadConfig() {
|
||||
if (!configFile.exists()) {
|
||||
saveDefaultConfig();
|
||||
}
|
||||
try {
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
return true;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
MyLogger.logException(e, "ConfigHandler", "reloadConfig");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the desired debugging level.
|
||||
<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.
|
||||
<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.
|
||||
<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.
|
||||
<br>Default: 0</br>*/
|
||||
public int getStatShareWaitingTime() {
|
||||
return config.getInt("waiting-time-before-sharing-again", 0);
|
||||
}
|
||||
|
||||
/** Returns the config setting for include-whitelist-only.
|
||||
<br>Default: false</br>*/
|
||||
public boolean whitelistOnly() {
|
||||
return config.getBoolean("include-whitelist-only", false);
|
||||
}
|
||||
|
||||
/** Returns the config setting for exclude-banned-players.
|
||||
<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.
|
||||
<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.
|
||||
<br>Default: true</br>*/
|
||||
public boolean useTranslatableComponents() {
|
||||
return config.getBoolean("translate-to-client-language", true);
|
||||
}
|
||||
|
||||
/** Whether to use HoverComponents for additional information.
|
||||
<br>Default: true</br>*/
|
||||
public boolean useHoverText() {
|
||||
return config.getBoolean("enable-hover-text", true);
|
||||
}
|
||||
|
||||
/** Whether to use festive formatting, such as pride colors.
|
||||
<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.
|
||||
<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;
|
||||
if (section != null) {
|
||||
String path = switch (selection) {
|
||||
case TOP -> getSharedSetting ? "top-stats-shared" : "top-stats";
|
||||
case PLAYER -> getSharedSetting ? "player-stats-shared" : "player-stats";
|
||||
case SERVER -> getSharedSetting ? "server-stats-shared" : "server-stats";
|
||||
};
|
||||
return section.getBoolean(path, def);
|
||||
}
|
||||
MyLogger.logMsg("Config settings for use-enters could not be retrieved! " +
|
||||
"Please check your file if you want to use custom settings. " +
|
||||
"Using default values...", true);
|
||||
return def;
|
||||
}
|
||||
|
||||
/** Returns the config setting for use-dots.
|
||||
<br>Default: true</br>*/
|
||||
public boolean useDots() {
|
||||
return config.getBoolean("use-dots", true);
|
||||
}
|
||||
|
||||
/** Returns the config setting for top-list-max-size.
|
||||
<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.
|
||||
<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.
|
||||
<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.
|
||||
<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.
|
||||
<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.
|
||||
<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.
|
||||
<br>Default: true</br>*/
|
||||
public boolean autoDetectTimeUnit(boolean isUnitForHoverText) {
|
||||
String path = "auto-detect-biggest-time-unit";
|
||||
if (isUnitForHoverText) {
|
||||
path = path + "-for-hover-text";
|
||||
}
|
||||
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.
|
||||
<br>Default: 1 for plain text, 0 for hover-text</br>*/
|
||||
public int getNumberOfExtraTimeUnits(boolean isUnitForHoverText) {
|
||||
String path = "number-of-extra-units";
|
||||
if (isUnitForHoverText) {
|
||||
path = path + "-for-hover-text";
|
||||
}
|
||||
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).
|
||||
<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).
|
||||
<br>Default: hours for plain text, seconds for hover-text</br>*/
|
||||
public String getTimeUnit(boolean isUnitForHoverText, boolean smallUnit) {
|
||||
if (smallUnit) {
|
||||
return getUnitString(isUnitForHoverText, "hours", "seconds", "smallest-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.
|
||||
<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:
|
||||
* <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:
|
||||
* <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:
|
||||
<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) {
|
||||
def = "green";
|
||||
}
|
||||
else {
|
||||
def = "gold";
|
||||
}
|
||||
return getDecorationString(selection, getStyleSetting, def, "player-names");
|
||||
}
|
||||
|
||||
/** 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.TOP);
|
||||
|
||||
if (style != null) {
|
||||
String styleString = style.getString("player-names");
|
||||
return styleString != null && styleString.equalsIgnoreCase("bold");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
|
||||
<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:
|
||||
<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:
|
||||
<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) {
|
||||
def = "#55AAFF";
|
||||
}
|
||||
else {
|
||||
def = "#ADE7FF";
|
||||
}
|
||||
return getDecorationString(selection, getStyleSetting, def,"stat-numbers");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
|
||||
<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) {
|
||||
def = "yellow";
|
||||
}
|
||||
else {
|
||||
def = "gold";
|
||||
}
|
||||
return getDecorationString(selection, getStyleSetting, def, "title");
|
||||
}
|
||||
|
||||
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
|
||||
<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:
|
||||
<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:
|
||||
<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:
|
||||
<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 {@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
|
||||
@param defaultValue the default unit for plain text
|
||||
@param defaultHoverValue the default unit for hovering text
|
||||
@param pathName the config path to retrieve the value from*/
|
||||
private String getUnitString(boolean isHoverText, String defaultValue, String defaultHoverValue, String pathName) {
|
||||
String path = isHoverText ? pathName + "-for-hover-text" : pathName;
|
||||
String def = defaultValue;
|
||||
if (isHoverText && defaultHoverValue != null) {
|
||||
def = defaultHoverValue;
|
||||
}
|
||||
return config.getString(path, def);
|
||||
}
|
||||
|
||||
/** Returns the config value for a color or style option in string-format, the supplied default value,
|
||||
or null if no configSection was found.
|
||||
@param selection the Target this decoration is meant for (Player, Server or Top)
|
||||
@param getStyleSetting if true, the result will be a style String, otherwise a color String
|
||||
@param defaultColor the default color to return if the config value cannot be found (for style, the default is always "none")
|
||||
@param pathName the config path to retrieve the value from*/
|
||||
private @Nullable String getDecorationString(Target selection, boolean getStyleSetting, String defaultColor, String pathName){
|
||||
String path = getStyleSetting ? pathName + "-style" : pathName;
|
||||
String defaultValue = getStyleSetting ? "none" : defaultColor;
|
||||
|
||||
ConfigurationSection section = getRelevantSection(selection);
|
||||
return section != null ? section.getString(path, defaultValue) : null;
|
||||
}
|
||||
|
||||
/** Returns the config section that contains the relevant color or style option. */
|
||||
private @Nullable ConfigurationSection getRelevantSection(Target selection) {
|
||||
if (selection == null) { //rather than rework the whole Target enum, I have added shared-stats as the null-option for now
|
||||
return config.getConfigurationSection("shared-stats");
|
||||
}
|
||||
switch (selection) {
|
||||
case TOP -> {
|
||||
return config.getConfigurationSection("top-list");
|
||||
}
|
||||
case PLAYER -> {
|
||||
return config.getConfigurationSection("individual-statistics");
|
||||
}
|
||||
case SERVER -> {
|
||||
return config.getConfigurationSection("total-server");
|
||||
}
|
||||
default -> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.enums;
|
||||
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/** This enum represents the colorscheme PlayerStats uses in its output messages.
|
||||
The first set of colors is used throughout the plugin, while the set of NAME-colors
|
||||
represents the colors that player-names can be in the "shared by player-name" section of shared statistics.*/
|
||||
public enum PluginColor {
|
||||
/** ChatColor Gray (#AAAAAA) */
|
||||
GRAY (NamedTextColor.GRAY),
|
||||
|
||||
/** A Dark Purple that is mainly used for title-underscores (#6E3485).*/
|
||||
DARK_PURPLE (TextColor.fromHexString("#6E3485")),
|
||||
|
||||
/** A Light Purple that is meant to simulate the color of a clicked link.
|
||||
Used for the "Hover Here" part of shared statistics (#845EC2).*/
|
||||
LIGHT_PURPLE (TextColor.fromHexString("#845EC2")),
|
||||
|
||||
/** ChatColor Blue (#5555FF)*/
|
||||
BLUE (NamedTextColor.BLUE),
|
||||
|
||||
/** A Medium Blue that is used for default feedback and error messages (#55AAFF).*/
|
||||
MEDIUM_BLUE (TextColor.fromHexString("#55AAFF")),
|
||||
|
||||
/** A Light Blue that is used for hover-messages and the share-button (#55C6FF). */
|
||||
LIGHT_BLUE (TextColor.fromHexString("#55C6FF")),
|
||||
|
||||
/** ChatColor Gold (#FFAA00)*/
|
||||
GOLD (NamedTextColor.GOLD),
|
||||
|
||||
/** A Medium Gold that is used for the example message and for hover-text accents (#FFD52B).*/
|
||||
MEDIUM_GOLD (TextColor.fromHexString("#FFD52B")),
|
||||
|
||||
/** A Light Gold that is used for the example message and for hover-text accents (#FFEA40).*/
|
||||
LIGHT_GOLD (TextColor.fromHexString("#FFEA40")),
|
||||
|
||||
/** A Light Yellow that is used for final accents in the example message (#FFFF8E).*/
|
||||
LIGHT_YELLOW (TextColor.fromHexString("#FFFF8E")),
|
||||
|
||||
/** The color of vanilla Minecraft hearts (#FF1313). */
|
||||
RED (TextColor.fromHexString("#FF1313")),
|
||||
|
||||
/** ChatColor Blue (#5555FF)*/
|
||||
NAME_1 (NamedTextColor.BLUE), //#5555FF - blue
|
||||
|
||||
/** A shade of blue between Blue and Medium Blue (#4287F5)*/
|
||||
NAME_2 (TextColor.fromHexString("#4287F5")),
|
||||
|
||||
/** Medium Blue (#55AAFF)*/
|
||||
NAME_3 (TextColor.fromHexString("#55AAFF")),
|
||||
|
||||
/** A shade of magenta/purple (#D65DB1)*/
|
||||
NAME_4 (TextColor.fromHexString("#D65DB1")),
|
||||
|
||||
/** A dark shade of orange (#EE8A19)*/
|
||||
NAME_5 (TextColor.fromHexString("#EE8A19")),
|
||||
|
||||
/** A shade of green/aqua/cyan-ish (#01C1A7)*/
|
||||
NAME_6 (TextColor.fromHexString("#01C1A7")),
|
||||
|
||||
/** A light shade of green (#46D858)*/
|
||||
NAME_7 (TextColor.fromHexString("#46D858"));
|
||||
|
||||
|
||||
private final TextColor color;
|
||||
|
||||
PluginColor(TextColor color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/** Returns the TextColor value belonging to the corresponding enum constant.*/
|
||||
public TextColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/** Gets the nearest NamedTextColor for the corresponding enum constant.*/
|
||||
public TextColor getConsoleColor() {
|
||||
return NamedTextColor.nearestTo(color);
|
||||
}
|
||||
|
||||
/** Randomly selects one of the 7 different NAME-colors.*/
|
||||
public static TextColor getRandomNameColor() {
|
||||
return getRandomNameColor(false);
|
||||
}
|
||||
|
||||
/** Randomly selects one of the 7 different NAME-colors, and if isConsole is true,
|
||||
returns the closest NamedTextColor.*/
|
||||
public static TextColor getRandomNameColor(boolean isConsole) {
|
||||
Random randomizer = new Random();
|
||||
PluginColor color = switch (randomizer.nextInt(7)) {
|
||||
case 0 -> NAME_1;
|
||||
case 2 -> NAME_3;
|
||||
case 3 -> NAME_4;
|
||||
case 4 -> NAME_5;
|
||||
case 5 -> NAME_6;
|
||||
case 6 -> NAME_7;
|
||||
default -> NAME_2;
|
||||
};
|
||||
return getCorrespondingColor(color, isConsole);
|
||||
}
|
||||
|
||||
private static TextColor getCorrespondingColor(PluginColor nameColor, boolean isConsole) {
|
||||
return isConsole ? nameColor.getConsoleColor() : nameColor.getColor();
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,567 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.msg;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
|
||||
import com.gmail.artemis.the.gr8.playerstats.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.utils.MyLogger;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
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 {@link ComponentFactory}.
|
||||
By default, this class works with the default ComponentFactory, but you can
|
||||
give it a different ComponentFactory upon creation.*/
|
||||
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 MessageBuilder(ConfigHandler config) {
|
||||
this (config, new ComponentFactory(config));
|
||||
}
|
||||
|
||||
private MessageBuilder(ConfigHandler configHandler, ComponentFactory factory) {
|
||||
config = configHandler;
|
||||
useHoverText = config.useHoverText();
|
||||
componentFactory = factory;
|
||||
|
||||
formatter = new NumberFormatter();
|
||||
languageKeyHandler = new LanguageKeyHandler();
|
||||
MyLogger.logMsg("MessageBuilder created with factory: " + componentFactory.getClass().getSimpleName(), DebugLevel.MEDIUM);
|
||||
}
|
||||
|
||||
public static MessageBuilder defaultBuilder(ConfigHandler config) {
|
||||
return new MessageBuilder(config);
|
||||
}
|
||||
|
||||
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() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content("Config reloaded!"));
|
||||
}
|
||||
|
||||
public TextComponent stillReloading() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"The plugin is (re)loading, your request will be processed when it is done!"));
|
||||
}
|
||||
|
||||
public TextComponent waitAMoment(boolean longWait) {
|
||||
String msg = longWait ? "Calculating statistics, this may take a minute..." :
|
||||
"Calculating statistics, this may take a few moments...";
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(msg));
|
||||
}
|
||||
|
||||
public TextComponent missingStatName() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please provide a valid statistic name!"));
|
||||
}
|
||||
|
||||
public TextComponent missingSubStatName(Statistic.Type statType) {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please add a valid " + getSubStatTypeName(statType) + " to look up this statistic!"));
|
||||
}
|
||||
|
||||
public TextComponent missingPlayerName() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please specify a valid player-name!"));
|
||||
}
|
||||
|
||||
public TextComponent wrongSubStatType(Statistic.Type statType, String subStatName) {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.messageAccent().content("\"" + subStatName + "\""))
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"is not a valid " + getSubStatTypeName(statType) + "!"));
|
||||
}
|
||||
|
||||
public TextComponent requestAlreadyRunning() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Please wait for your previous lookup to finish!"));
|
||||
}
|
||||
|
||||
//TODO Make this say amount of time left
|
||||
public TextComponent stillOnShareCoolDown() {
|
||||
int waitTime = config.getStatShareWaitingTime();
|
||||
String minutes = waitTime == 1 ? " minute" : " minutes";
|
||||
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content("You need to wait")
|
||||
.append(space())
|
||||
.append(componentFactory.messageAccent()
|
||||
.content(waitTime + minutes))
|
||||
.append(space())
|
||||
.append(text("between sharing!")));
|
||||
}
|
||||
|
||||
public TextComponent resultsAlreadyShared() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content("You already shared these results!"));
|
||||
}
|
||||
|
||||
public TextComponent statResultsTooOld() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"It has been too long since you looked up this statistic, please repeat the original command!"));
|
||||
}
|
||||
|
||||
public TextComponent unknownError() {
|
||||
return componentFactory.pluginPrefix()
|
||||
.append(space())
|
||||
.append(componentFactory.message().content(
|
||||
"Something went wrong with your request, " +
|
||||
"please try again or see /statistic for a usage explanation!"));
|
||||
}
|
||||
|
||||
public TextComponent usageExamples() {
|
||||
return ExampleMessage.construct(componentFactory);
|
||||
}
|
||||
|
||||
public TextComponent helpMsg() {
|
||||
int listSize = config.getTopListMaxSize();
|
||||
if (!isConsoleBuilder && useHoverText) {
|
||||
return HelpMessage.constructHoverMsg(componentFactory, listSize);
|
||||
} else {
|
||||
return HelpMessage.constructPlainMsg(componentFactory, listSize);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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(statRequest.getPlayerName(), Target.PLAYER)
|
||||
.append(text(":"))
|
||||
.append(space()))
|
||||
.append(getStatNumberComponent(statRequest, stat))
|
||||
.append(space())
|
||||
.append(getStatNameComponent(statRequest))
|
||||
.append(getStatUnitComponent(statRequest.getStatistic(), statRequest.getTarget())) //space is provided by statUnitComponent
|
||||
.build();
|
||||
|
||||
return getFormattingFunction(playerStat, Target.PLAYER);
|
||||
}
|
||||
|
||||
/** 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(statRequest, stat))
|
||||
.append(space())
|
||||
.append(getStatNameComponent(statRequest))
|
||||
.append(getStatUnitComponent(statRequest.getStatistic(), statRequest.getTarget())) //space is provided by statUnit
|
||||
.build();
|
||||
|
||||
return getFormattingFunction(serverStat, Target.SERVER);
|
||||
}
|
||||
|
||||
/** 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);
|
||||
|
||||
return (shareCode, sender) -> {
|
||||
TextComponent.Builder topBuilder = text();
|
||||
|
||||
//if we're adding a share-button
|
||||
if (shareCode != null) {
|
||||
if (useEnters) {
|
||||
topBuilder.append(newline());
|
||||
}
|
||||
topBuilder.append(title)
|
||||
.append(space())
|
||||
.append(componentFactory.shareButton(shareCode))
|
||||
.append(list);
|
||||
}
|
||||
//if we're adding a "shared by" component
|
||||
else if (sender != null) {
|
||||
if (useEntersForShared) {
|
||||
topBuilder.append(newline());
|
||||
}
|
||||
topBuilder.append(shortTitle)
|
||||
.append(space())
|
||||
.append(componentFactory.statResultInHoverText(text()
|
||||
.append(title)
|
||||
.append(list)
|
||||
.build()))
|
||||
.append(newline())
|
||||
.append(componentFactory.sharedByMessage(
|
||||
getSharerNameComponent(sender)));
|
||||
}
|
||||
//if we're not adding a share-button or a "shared by" component
|
||||
else {
|
||||
if (useEnters) {
|
||||
topBuilder.append(newline());
|
||||
}
|
||||
topBuilder.append(title)
|
||||
.append(list);
|
||||
}
|
||||
return topBuilder.build();
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
//if we're adding a share-button
|
||||
if (shareCode != null) {
|
||||
if (useEnters) {
|
||||
statBuilder.append(newline());
|
||||
}
|
||||
statBuilder.append(statResult)
|
||||
.append(space())
|
||||
.append(componentFactory.shareButton(shareCode));
|
||||
}
|
||||
//if we're adding a "shared by" component
|
||||
else if (sender != null) {
|
||||
if (useEntersForShared) {
|
||||
statBuilder.append(newline());
|
||||
}
|
||||
statBuilder.append(statResult)
|
||||
.append(newline())
|
||||
.append(componentFactory.sharedByMessage(
|
||||
getSharerNameComponent(sender)));
|
||||
}
|
||||
//if we're not adding a share-button or a "shared by" component
|
||||
else {
|
||||
if (useEnters) {
|
||||
statBuilder.append(newline());
|
||||
}
|
||||
statBuilder.append(statResult);
|
||||
}
|
||||
return statBuilder.build();
|
||||
};
|
||||
}
|
||||
|
||||
/** Get an ArrayList consisting of 2 or 4 timeUnits. The order of items is:
|
||||
<p>0. maxUnit</p>
|
||||
<p>1. minUnit</p>
|
||||
<p>2. maxHoverUnit</p>
|
||||
<p>3. minHoverUnit</p>*/
|
||||
private ArrayList<Unit> getTimeUnitRange(long statNumber) {
|
||||
ArrayList<Unit> unitRange = new ArrayList<>();
|
||||
if (!config.autoDetectTimeUnit(false)) {
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(false)));
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(false, true)));
|
||||
}
|
||||
else {
|
||||
Unit bigUnit = Unit.getMostSuitableUnit(Unit.Type.TIME, statNumber);
|
||||
unitRange.add(bigUnit);
|
||||
unitRange.add(bigUnit.getSmallerUnit(config.getNumberOfExtraTimeUnits(false)));
|
||||
}
|
||||
if (useHoverText) {
|
||||
if (!config.autoDetectTimeUnit(true)) {
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(true)));
|
||||
unitRange.add(Unit.fromString(config.getTimeUnit(true, true)));
|
||||
}
|
||||
else {
|
||||
Unit bigHoverUnit = Unit.getMostSuitableUnit(Unit.Type.TIME, statNumber);
|
||||
unitRange.add(bigHoverUnit);
|
||||
unitRange.add(bigHoverUnit.getSmallerUnit(config.getNumberOfExtraTimeUnits(true)));
|
||||
}
|
||||
}
|
||||
MyLogger.logMsg("total selected unitRange for this statistic: " + unitRange, DebugLevel.MEDIUM);
|
||||
return unitRange;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
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() {
|
||||
}
|
||||
|
||||
public static int getNumberOfDotsToAlign(String displayText, boolean isConsoleSender, boolean fontIsBold) {
|
||||
if (isConsoleSender) {
|
||||
return (int) Math.round((130.0 - MinecraftFont.Font.getWidth(displayText))/6) + 7;
|
||||
} else if (!fontIsBold) {
|
||||
return (int) Math.round((130.0 - MinecraftFont.Font.getWidth(displayText))/2);
|
||||
} else {
|
||||
return (int) Math.round((130.0 - (MinecraftFont.Font.getWidth(displayText) * 1.5))/2);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.statistic;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||
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 StatRetriever {
|
||||
|
||||
private final OfflinePlayerHandler offlinePlayerHandler;
|
||||
|
||||
public StatRetriever(OfflinePlayerHandler offlinePlayerHandler) {
|
||||
this.offlinePlayerHandler = offlinePlayerHandler;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
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.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.utils.MyLogger;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/** The Thread that is in charge of getting and calculating statistics.*/
|
||||
public final class StatThread extends Thread {
|
||||
|
||||
private static OutputManager outputManager;
|
||||
private static StatRetriever statRetriever;
|
||||
|
||||
private final ReloadThread reloadThread;
|
||||
private final StatRequest statRequest;
|
||||
|
||||
public StatThread(OutputManager m, StatRetriever t, int ID, StatRequest s, @Nullable ReloadThread r) {
|
||||
outputManager = m;
|
||||
statRetriever = t;
|
||||
|
||||
reloadThread = r;
|
||||
statRequest = s;
|
||||
|
||||
this.setName("StatThread-" + statRequest.getCommandSender().getName() + "-" + ID);
|
||||
MyLogger.threadCreated(this.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() throws IllegalStateException, NullPointerException {
|
||||
MyLogger.threadStart(this.getName());
|
||||
|
||||
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(statRequest.getCommandSender(), StandardMessage.STILL_RELOADING);
|
||||
reloadThread.join();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
MyLogger.logException(e, "StatThread", "Trying to join " + reloadThread.getName());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
long lastCalc = ThreadManager.getLastRecordedCalcTime();
|
||||
if (lastCalc > 2000) {
|
||||
outputManager.sendFeedbackMsgWaitAMoment(statRequest.getCommandSender(), lastCalc > 20000);
|
||||
}
|
||||
|
||||
Target selection = statRequest.getTarget();
|
||||
try {
|
||||
TextComponent statResult = switch (selection) {
|
||||
case PLAYER -> outputManager.formatPlayerStat(statRequest, statRetriever.getPlayerStat(statRequest));
|
||||
case TOP -> outputManager.formatTopStat(statRequest, statRetriever.getTopStats(statRequest));
|
||||
case SERVER -> outputManager.formatServerStat(statRequest, statRetriever.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
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.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 RequestGenerator<Integer>, RequestExecutor<Integer> {
|
||||
|
||||
private final StatRequest statRequest;
|
||||
private final StatRequestHandler statRequestHandler;
|
||||
|
||||
public PlayerStatRequest(StatRequest request) {
|
||||
statRequest = request;
|
||||
statRequestHandler = new StatRequestHandler(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestExecutor<Integer> untyped(@NotNull Statistic statistic) {
|
||||
StatRequest completedRequest = statRequestHandler.untyped(statistic);
|
||||
return new PlayerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestExecutor<Integer> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
StatRequest completedRequest = statRequestHandler.blockOrItemType(statistic, material);
|
||||
return new PlayerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestExecutor<Integer> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
StatRequest completedRequest = statRequestHandler.entityType(statistic, entityType);
|
||||
return new PlayerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<Integer> execute() {
|
||||
return getStatResult(statRequest);
|
||||
}
|
||||
|
||||
private PlayerStatResult getStatResult(StatRequest completedRequest) {
|
||||
int stat = RequestExecutor.getStatCalculator()
|
||||
.getPlayerStat(completedRequest);
|
||||
TextComponent prettyStat = RequestExecutor.getStatFormatter()
|
||||
.formatPlayerStat(completedRequest, stat);
|
||||
|
||||
return new PlayerStatResult(stat, prettyStat);
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
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.api.RequestGenerator;
|
||||
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 RequestGenerator<Long>, RequestExecutor<Long> {
|
||||
|
||||
private final StatRequest statRequest;
|
||||
private final StatRequestHandler statRequestHandler;
|
||||
|
||||
public ServerStatRequest(StatRequest request) {
|
||||
statRequest = request;
|
||||
statRequestHandler = new StatRequestHandler(statRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestExecutor<Long> untyped(@NotNull Statistic statistic) {
|
||||
StatRequest completedRequest = statRequestHandler.untyped(statistic);
|
||||
return new ServerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestExecutor<Long> blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
StatRequest completedRequest = statRequestHandler.blockOrItemType(statistic, material);
|
||||
return new ServerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestExecutor<Long> entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
StatRequest completedRequest = statRequestHandler.entityType(statistic, entityType);
|
||||
return new ServerStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<Long> execute() {
|
||||
return getStatResult(statRequest);
|
||||
}
|
||||
|
||||
private ServerStatResult getStatResult(StatRequest completedRequest) {
|
||||
long stat = RequestExecutor.getStatCalculator()
|
||||
.getServerStat(completedRequest);
|
||||
TextComponent prettyStat = RequestExecutor.getStatFormatter()
|
||||
.formatServerStat(completedRequest, stat);
|
||||
|
||||
return new ServerStatResult(stat, prettyStat);
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
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 final class StatRequestHandler {
|
||||
|
||||
private final StatRequest statRequest;
|
||||
|
||||
public StatRequestHandler(StatRequest request) {
|
||||
statRequest = request;
|
||||
}
|
||||
|
||||
public static StatRequest getBasicPlayerStatRequest(String playerName) {
|
||||
StatRequest request = StatRequest.getBasicAPIRequest();
|
||||
request.setTarget(Target.PLAYER);
|
||||
request.setPlayerName(playerName);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static StatRequest getBasicServerStatRequest() {
|
||||
StatRequest request = StatRequest.getBasicAPIRequest();
|
||||
request.setTarget(Target.SERVER);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static StatRequest getBasicTopStatRequest(int topListSize) {
|
||||
StatRequest request = StatRequest.getBasicAPIRequest();
|
||||
request.setTarget(Target.TOP);
|
||||
request.setTopListSize(topListSize != 0 ? topListSize : Main.getConfigHandler().getTopListMaxSize());
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
@param sender the CommandSender that requested this specific statistic
|
||||
*/
|
||||
public static StatRequest getBasicInternalStatRequest(CommandSender sender) {
|
||||
StatRequest request = StatRequest.getBasicRequest(sender);
|
||||
request.setTopListSize(Main.getConfigHandler().getTopListMaxSize());
|
||||
return request;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
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.api.RequestGenerator;
|
||||
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 RequestGenerator<LinkedHashMap<String, Integer>>, RequestExecutor<LinkedHashMap<String, Integer>> {
|
||||
|
||||
private final StatRequest statRequest;
|
||||
private final StatRequestHandler statRequestHandler;
|
||||
|
||||
public TopStatRequest(StatRequest request) {
|
||||
statRequest = request;
|
||||
statRequestHandler = new StatRequestHandler(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest untyped(@NotNull Statistic statistic) {
|
||||
StatRequest completedRequest = statRequestHandler.untyped(statistic);
|
||||
return new TopStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest blockOrItemType(@NotNull Statistic statistic, @NotNull Material material) {
|
||||
StatRequest completedRequest = statRequestHandler.blockOrItemType(statistic, material);
|
||||
return new TopStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TopStatRequest entityType(@NotNull Statistic statistic, @NotNull EntityType entityType) {
|
||||
StatRequest completedRequest = statRequestHandler.entityType(statistic, entityType);
|
||||
return new TopStatRequest(completedRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatResult<LinkedHashMap<String, Integer>> execute() {
|
||||
return getStatResult(statRequest);
|
||||
}
|
||||
|
||||
private TopStatResult getStatResult(StatRequest completedRequest) {
|
||||
LinkedHashMap<String, Integer> stat = RequestExecutor.getStatCalculator()
|
||||
.getTopStats(completedRequest);
|
||||
TextComponent prettyStat = RequestExecutor.getStatFormatter()
|
||||
.formatTopStat(completedRequest, stat);
|
||||
|
||||
return new TopStatResult(stat, prettyStat);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
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();
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
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;
|
||||
private static DebugLevel debugLevel;
|
||||
|
||||
private static final String[] processedPlayers;
|
||||
private static final AtomicInteger playersIndex;
|
||||
private static ConcurrentHashMap<String, Integer> threadNames;
|
||||
|
||||
static {
|
||||
Plugin plugin = Bukkit.getPluginManager().getPlugin("PlayerStats");
|
||||
logger = (plugin != null) ? plugin.getLogger() : Bukkit.getLogger();
|
||||
debugLevel = DebugLevel.LOW;
|
||||
|
||||
processedPlayers = new String[10];
|
||||
playersIndex = new AtomicInteger(0);
|
||||
threadNames = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
private MyLogger() {
|
||||
}
|
||||
|
||||
/** Sets the desired debugging level.
|
||||
<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;
|
||||
}
|
||||
else if (level == 3) {
|
||||
debugLevel = DebugLevel.HIGH;
|
||||
}
|
||||
else {
|
||||
debugLevel = DebugLevel.LOW;
|
||||
}
|
||||
}
|
||||
|
||||
public static void logMsg(String content) {
|
||||
logMsg(content, DebugLevel.LOW, false);
|
||||
}
|
||||
|
||||
public static void logMsg(String content, boolean logAsWarning) {
|
||||
logMsg(content, DebugLevel.LOW, logAsWarning);
|
||||
}
|
||||
|
||||
public static void logMsg(String content, DebugLevel logThreshold) {
|
||||
logMsg(content, logThreshold, false);
|
||||
}
|
||||
|
||||
public static void logMsg(String content, DebugLevel logThreshold, boolean logAsWarning) {
|
||||
switch (logThreshold) {
|
||||
case LOW -> log(content, logAsWarning);
|
||||
case MEDIUM -> {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
log(content, logAsWarning);
|
||||
}
|
||||
}
|
||||
case HIGH -> {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
log(content, logAsWarning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@param exception The encountered exception
|
||||
@param caughtBy The name of the class that caught the exception
|
||||
@param additionalInfo e.g. the method-name or line where the exception is caught */
|
||||
public static void logException(@NotNull Exception exception, String caughtBy, @Nullable String additionalInfo) {
|
||||
String extraInfo = (additionalInfo != null) ? " [" + additionalInfo + "]" : "";
|
||||
String info = " (" + caughtBy + extraInfo + ")";
|
||||
|
||||
logger.warning(exception + info);
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** 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...");
|
||||
}
|
||||
}
|
||||
|
||||
/** Output to console that the given thread has been created (but not started yet).*/
|
||||
public static void threadCreated(String threadName) {
|
||||
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.HIGH) {
|
||||
logger.info(threadName + " started!");
|
||||
}
|
||||
}
|
||||
|
||||
/** Output to console that another reloadThread is already running. */
|
||||
public static void threadAlreadyRunning(String threadName) {
|
||||
logger.info("Another reloadThread is already running! (" + threadName + ")");
|
||||
}
|
||||
|
||||
/** Output to console that the executingThread is waiting for otherThread to finish up. */
|
||||
public static void waitingForOtherThread(String executingThread, String otherThread) {
|
||||
logger.info(executingThread + ": Waiting for " + otherThread + " to finish up...");
|
||||
}
|
||||
|
||||
/** If DebugLevel is MEDIUM or HIGH, output to console that an action has started.
|
||||
@param taskLength Length of the action (in terms of units-to-process)*/
|
||||
public static void actionCreated(int taskLength) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
threadNames = new ConcurrentHashMap<>();
|
||||
playersIndex.set(0);
|
||||
logger.info("Initial Action created for " + taskLength + " Players. Processing...");
|
||||
}
|
||||
}
|
||||
|
||||
/** Internally save the name of the executing thread for later logging of this action.
|
||||
The list of names is reset upon the start of every new action.
|
||||
@param threadName Name of the executing thread*/
|
||||
public static void subActionCreated(String threadName) {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
if (!threadNames.containsKey(threadName)) {
|
||||
threadNames.put(threadName, threadNames.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Internally save the name of the executing thread and processed player for logging,
|
||||
and for the ReloadThread, if DebugLevel is HIGH, output the last 10 processed players once
|
||||
there have been 10 names saved in MyLogger. This method is synchronized.
|
||||
@param threadName Name of the executing thread
|
||||
@param playerName Name of the player that was processed in this action
|
||||
@param thread 1 for ReloadThread, 2 for StatThread */
|
||||
public static synchronized void actionRunning(String threadName, String playerName, int thread) {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
if (!threadNames.containsKey(threadName)) {
|
||||
threadNames.put(threadName, threadNames.size());
|
||||
}
|
||||
if (thread == 1 && debugLevel == DebugLevel.HIGH) {
|
||||
if (incrementOfTen()) {
|
||||
logger.info(Arrays.asList(processedPlayers).toString());
|
||||
}
|
||||
processedPlayers[nextPlayersIndex() % 10] = playerName;
|
||||
}
|
||||
else if (debugLevel == DebugLevel.MEDIUM || debugLevel == DebugLevel.HIGH && thread == 2) {
|
||||
nextPlayersIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Output to console that an action has finished.
|
||||
<p>For the ReloadThread, if DebugLevel is HIGH, output the left-over processed players.
|
||||
For both threads, if DebugLevel is MEDIUM or HIGH, output the names of the threads that were used.</p>
|
||||
@param thread 1 for ReloadThread, 2 for StatThread */
|
||||
public static void actionFinished(int thread) {
|
||||
if (thread == 1 && debugLevel == DebugLevel.HIGH) {
|
||||
ArrayList<String> leftOvers = new ArrayList<>(Arrays.asList(processedPlayers).subList(playersIndex.intValue() % 10, 10));
|
||||
logger.info(leftOvers.toString());
|
||||
}
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
logger.info("Finished Recursive Action! In total " +
|
||||
threadNames.size() + " Threads were used to process " +
|
||||
playersIndex.get() + " Players.");
|
||||
}
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
logger.info(Collections.list(threadNames.keys()).toString());
|
||||
}
|
||||
}
|
||||
|
||||
/** Output to console how long a certain task has taken if DebugLevel is equal to or higher than the specified threshold.
|
||||
@param className Name of the class executing the task
|
||||
@param methodName Name or description of the task
|
||||
@param startTime Timestamp marking the beginning of the task
|
||||
@param logThreshold the DebugLevel threshold */
|
||||
public static void logTimeTaken(String className, String methodName, long startTime, DebugLevel logThreshold) {
|
||||
switch (logThreshold) {
|
||||
case LOW -> printTime(className, methodName, startTime);
|
||||
case MEDIUM -> {
|
||||
if (debugLevel != DebugLevel.LOW) {
|
||||
printTime(className, methodName, startTime);
|
||||
}
|
||||
}
|
||||
case HIGH -> {
|
||||
if (debugLevel == DebugLevel.HIGH) {
|
||||
printTime(className, methodName, startTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void log(String content, boolean logAsWarning) {
|
||||
if (logAsWarning) {
|
||||
logger.warning(content);
|
||||
} else {
|
||||
logger.info(content);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printTime(String className, String methodName, long startTime) {
|
||||
logger.info(className + " " + methodName + ": " + (System.currentTimeMillis() - startTime) + "ms");
|
||||
}
|
||||
|
||||
/** Accesses the playersIndex to up it by 1 and return its previous value. */
|
||||
private static int nextPlayersIndex() {
|
||||
return playersIndex.getAndIncrement();
|
||||
}
|
||||
|
||||
/** Returns true if the playersIndex is 10, or any subsequent increment of 10. */
|
||||
private static boolean incrementOfTen() {
|
||||
return (playersIndex.get() == 10 || (playersIndex.get() > 10 && playersIndex.get() % 10 == 0));
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/** A utility class that deals with OfflinePlayers. It stores a list of all OfflinePlayer-names
|
||||
that need to be included in statistic calculations, and can retrieve the corresponding OfflinePlayer
|
||||
object for a given player-name.*/
|
||||
public final class OfflinePlayerHandler {
|
||||
|
||||
private static ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||
private static ArrayList<String> playerNames;
|
||||
|
||||
public OfflinePlayerHandler() {
|
||||
offlinePlayerUUIDs = new ConcurrentHashMap<>();
|
||||
playerNames = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new HashMap that stores the players to include in stat calculations.
|
||||
* This HashMap is stored as a private variable in OfflinePlayerHandler.
|
||||
* @param playerList ConcurrentHashMap with keys: playerNames and values: UUIDs
|
||||
*/
|
||||
public static void updateOfflinePlayerList(ConcurrentHashMap<String, UUID> playerList) {
|
||||
offlinePlayerUUIDs = playerList;
|
||||
playerNames = Collections.list(offlinePlayerUUIDs.keys());
|
||||
}
|
||||
|
||||
/** Checks if a given playerName is on the private HashMap of players that should be included in statistic calculations
|
||||
@param playerName String, case-sensitive */
|
||||
public boolean isRelevantPlayer(String playerName) {
|
||||
return offlinePlayerUUIDs.containsKey(playerName);
|
||||
}
|
||||
|
||||
/** Returns the number of OfflinePlayers that are included in statistic calculations */
|
||||
public int getOfflinePlayerCount() {
|
||||
return offlinePlayerUUIDs.size();
|
||||
}
|
||||
|
||||
/** Get an ArrayList of names from all OfflinePlayers that should be included in statistic calculations */
|
||||
public ArrayList<String> getOfflinePlayerNames() {
|
||||
return playerNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the playerName to get the player's UUID from a private HashMap, and uses the UUID to get the corresponding OfflinePlayer Object.
|
||||
* @param playerName name of the target player
|
||||
* @return OfflinePlayer (if this player is on the list, otherwise null)
|
||||
*/
|
||||
public @Nullable OfflinePlayer getOfflinePlayer(String playerName) {
|
||||
if (offlinePlayerUUIDs.get(playerName) != null) {
|
||||
return Bukkit.getOfflinePlayer(offlinePlayerUUIDs.get(playerName));
|
||||
}
|
||||
else {
|
||||
MyLogger.logMsg("Cannot calculate statistics for player-name: " + playerName, true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.gmail.artemis.the.gr8.playerstats.utils;
|
||||
|
||||
/** A small utility class that calculates with unix time.*/
|
||||
public final class UnixTimeHandler {
|
||||
|
||||
private UnixTimeHandler() {
|
||||
}
|
||||
|
||||
/** Calculates whether a player has played recently enough to fall within the lastPlayedLimit.
|
||||
If lastPlayedLimit == 0, this always returns true (since there is no limit).
|
||||
@param lastPlayed a long that represents the amount of milliseconds between the unix start point and the time this player last joined
|
||||
@param lastPlayedLimit a long that represents the maximum-number-of-days-since-last-joined */
|
||||
public static boolean hasPlayedSince(long lastPlayedLimit, long lastPlayed) {
|
||||
long maxLastPlayed = System.currentTimeMillis() - lastPlayedLimit * 24 * 60 * 60 * 1000;
|
||||
return lastPlayedLimit == 0 || lastPlayed >= maxLastPlayed;
|
||||
}
|
||||
}
|
90
src/main/resources/language.yml
Normal file
90
src/main/resources/language.yml
Normal file
@ -0,0 +1,90 @@
|
||||
# ------------------------------------------------------------------------------------------------------ #
|
||||
# PlayerStats Language File #
|
||||
# ------------------------------------------------------------------------------------------------------ #
|
||||
|
||||
stat_type.minecraft.mined: "Times Mined"
|
||||
stat_type.minecraft.crafted: "Times Crafted"
|
||||
stat_type.minecraft.used: "Times Used"
|
||||
stat_type.minecraft.broken: "Times Broken"
|
||||
stat_type.minecraft.picked_up: "Picked Up"
|
||||
stat_type.minecraft.dropped: "Dropped"
|
||||
stat_type.minecraft.killed: "Times Killed"
|
||||
stat_type.minecraft.killed_by: "Number of Times Killed By"
|
||||
stat.minecraft.animals_bred: "Animals Bred"
|
||||
stat.minecraft.aviate_one_cm: "Distance by Elytra"
|
||||
stat.minecraft.clean_armor: "Armor Pieces Cleaned"
|
||||
stat.minecraft.clean_banner: "Banners Cleaned"
|
||||
stat.minecraft.clean_shulker_box: "Shulker Boxes Cleaned"
|
||||
stat.minecraft.climb_one_cm: "Distance Climbed"
|
||||
stat.minecraft.bell_ring: "Bells Rung"
|
||||
stat.minecraft.target_hit: "Targets Hit"
|
||||
stat.minecraft.boat_one_cm: "Distance by Boat"
|
||||
stat.minecraft.crouch_one_cm: "Distance Crouched"
|
||||
stat.minecraft.damage_dealt: "Damage Dealt"
|
||||
stat.minecraft.damage_dealt_absorbed: "Damage Dealt (Absorbed)"
|
||||
stat.minecraft.damage_dealt_resisted: "Damage Dealt (Resisted)"
|
||||
stat.minecraft.damage_taken: "Damage Taken"
|
||||
stat.minecraft.damage_blocked_by_shield: "Damage Blocked by Shield"
|
||||
stat.minecraft.damage_absorbed: "Damage Absorbed"
|
||||
stat.minecraft.damage_resisted: "Damage Resisted"
|
||||
stat.minecraft.deaths: "Number of Deaths"
|
||||
stat.minecraft.walk_under_water_one_cm: "Distance Walked under Water"
|
||||
stat.minecraft.drop: "Items Dropped"
|
||||
stat.minecraft.eat_cake_slice: "Cake Slices Eaten"
|
||||
stat.minecraft.enchant_item: "Items Enchanted"
|
||||
stat.minecraft.fall_one_cm: "Distance Fallen"
|
||||
stat.minecraft.fill_cauldron: "Cauldrons Filled"
|
||||
stat.minecraft.fish_caught: "Fish Caught"
|
||||
stat.minecraft.fly_one_cm: "Distance Flown"
|
||||
stat.minecraft.horse_one_cm: "Distance by Horse"
|
||||
stat.minecraft.inspect_dispenser: "Dispensers Searched"
|
||||
stat.minecraft.inspect_dropper: "Droppers Searched"
|
||||
stat.minecraft.inspect_hopper: "Hoppers Searched"
|
||||
stat.minecraft.interact_with_anvil: "Interactions with Anvil"
|
||||
stat.minecraft.interact_with_beacon: "Interactions with Beacon"
|
||||
stat.minecraft.interact_with_brewingstand: "Interactions with Brewing Stand"
|
||||
stat.minecraft.interact_with_campfire: "Interactions with Campfire"
|
||||
stat.minecraft.interact_with_cartography_table: "Interactions with Cartography Table"
|
||||
stat.minecraft.interact_with_crafting_table: "Interactions with Crafting Table"
|
||||
stat.minecraft.interact_with_furnace: "Interactions with Furnace"
|
||||
stat.minecraft.interact_with_grindstone: "Interactions with Grindstone"
|
||||
stat.minecraft.interact_with_lectern: "Interactions with Lectern"
|
||||
stat.minecraft.interact_with_loom: "Interactions with Loom"
|
||||
stat.minecraft.interact_with_blast_furnace: "Interactions with Blast Furnace"
|
||||
stat.minecraft.interact_with_smithing_table: "Interactions with Smithing Table"
|
||||
stat.minecraft.interact_with_smoker: "Interactions with Smoker"
|
||||
stat.minecraft.interact_with_stonecutter: "Interactions with Stonecutter"
|
||||
stat.minecraft.jump: "Jumps"
|
||||
stat.minecraft.junk_fished: "Junk Fished"
|
||||
stat.minecraft.leave_game: "Games Quit"
|
||||
stat.minecraft.minecart_one_cm: "Distance by Minecart"
|
||||
stat.minecraft.mob_kills: "Mob Kills"
|
||||
stat.minecraft.open_barrel: "Barrels Opened"
|
||||
stat.minecraft.open_chest: "Chests Opened"
|
||||
stat.minecraft.open_enderchest: "Ender Chests Opened"
|
||||
stat.minecraft.open_shulker_box: "Shulker Boxes Opened"
|
||||
stat.minecraft.pig_one_cm: "Distance by Pig"
|
||||
stat.minecraft.strider_one_cm: "Distance by Strider"
|
||||
stat.minecraft.player_kills: "Player Kills"
|
||||
stat.minecraft.play_noteblock: "Note Blocks Played"
|
||||
stat.minecraft.play_time: "Time Played"
|
||||
stat.minecraft.play_record: "Music Discs Played"
|
||||
stat.minecraft.pot_flower: "Plants Potted"
|
||||
stat.minecraft.raid_trigger: "Raids Triggered"
|
||||
stat.minecraft.raid_win: "Raids Won"
|
||||
stat.minecraft.ring_bell: "Bells Rung"
|
||||
stat.minecraft.sleep_in_bed: "Times Slept in a Bed"
|
||||
stat.minecraft.sneak_time: "Sneak Time"
|
||||
stat.minecraft.sprint_one_cm: "Distance Sprinted"
|
||||
stat.minecraft.walk_on_water_one_cm: "Distance Walked on Water"
|
||||
stat.minecraft.swim_one_cm: "Distance Swum"
|
||||
stat.minecraft.talked_to_villager: "Talked to Villagers"
|
||||
stat.minecraft.time_since_rest: "Time Since Last Rest"
|
||||
stat.minecraft.time_since_death: "Time Since Last Death"
|
||||
stat.minecraft.total_world_time: "Time with World Open"
|
||||
stat.minecraft.traded_with_villager: "Traded with Villagers"
|
||||
stat.minecraft.treasure_fished: "Treasure Fished"
|
||||
stat.minecraft.trigger_trapped_chest: "Trapped Chests Triggered"
|
||||
stat.minecraft.tune_noteblock: "Note Blocks Tuned"
|
||||
stat.minecraft.use_cauldron: "Water Taken from Cauldron"
|
||||
stat.minecraft.walk_one_cm: "Distance Walked"
|
@ -1,6 +1,6 @@
|
||||
main: com.gmail.artemis.the.gr8.playerstats.Main
|
||||
main: com.github.artemis.the.gr8.playerstats.Main
|
||||
name: PlayerStats
|
||||
version: 1.6.1
|
||||
version: 1.7
|
||||
api-version: 1.13
|
||||
description: adds commands to view player statistics in chat
|
||||
author: Artemis_the_gr8
|
||||
|
Loading…
Reference in New Issue
Block a user