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