Finished reworking the API, now onto testing we go

This commit is contained in:
Artemis-the-gr8 2022-07-24 16:22:07 +02:00
parent 096bb89607
commit f6dca79c57
16 changed files with 292 additions and 259 deletions

View File

@ -2,10 +2,9 @@ package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.Main;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@ -31,36 +30,36 @@ public interface PlayerStats {
}
/** Get a formatted player-statistic of Statistic.Type UNTYPED.*/
TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull OfflinePlayer player);
TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull String playerName) throws IllegalArgumentException;
/** Get a formatted player-statistic of Statistic.Type BLOCK or ITEM.*/
TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull Material material, @NotNull OfflinePlayer player);
TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull Material material, @NotNull String playerName) throws IllegalArgumentException;
/** Get a formatted player-statistic of Statistic.Type ENTITY.*/
TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull EntityType entity, @NotNull OfflinePlayer player);
TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull EntityType entity, @NotNull String playerName) throws IllegalArgumentException;
/** Get a formatted server-statistic of Statistic.Type UNTYPED. Not recommended to call this from the main Thread (see class description).*/
TextComponent getServerStat(@NotNull Statistic statistic);
TextComponent getServerStat(@NotNull Statistic statistic) throws IllegalArgumentException;
/** Get a formatted server-statistic of Statistic.Type BLOCK or ITEM. Not recommended to call this from the main Thread (see class description).*/
TextComponent getServerStat(@NotNull Statistic statistic, @NotNull Material material);
TextComponent getServerStat(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException;
/** Get a formatted server-statistic of Statistic.Type ENTITY. Not recommended to call this from the main Thread (see class description).*/
TextComponent getServerStat(@NotNull Statistic statistic, @NotNull EntityType entity);
TextComponent getServerStat(@NotNull Statistic statistic, @NotNull EntityType entity) throws IllegalArgumentException;
/** Get a formatted top-statistic of Statistic.Type UNTYPED. Not recommended to call this from the main Thread (see class description).*/
TextComponent getTopStats(@NotNull Statistic statistic);
TextComponent getTopStats(@NotNull Statistic statistic) throws IllegalArgumentException;
/** Get a formatted top-statistic of Statistic.Type BLOCK or ITEM. Not recommended to call this from the main Thread (see class description).*/
TextComponent getTopStats(@NotNull Statistic statistic, @NotNull Material material);
TextComponent getTopStats(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException;
/** Get a formatted top-statistic of Statistic.Type ENTITY. Not recommended to call this from the main Thread (see class description).*/
TextComponent getTopStats(@NotNull Statistic statistic, @NotNull EntityType entity);
TextComponent getTopStats(@NotNull Statistic statistic, @NotNull EntityType entity) throws IllegalArgumentException;
/** Turns a TextComponent into its String representation. If you don't want to work with
Adventure's TextComponents, you can call this method to turn any stat-result into a String.
It will lose all color and style, but it will keep line-breaks.*/
String statResultComponentToString(TextComponent component);
default String statResultComponentToString(TextComponent statResult) {
return MiniMessage.miniMessage().serialize(statResult);
}
}

View File

@ -1,15 +1,15 @@
package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.statistic.RequestManager;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
import net.kyori.adventure.text.TextComponent;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
@ -20,84 +20,80 @@ import static org.jetbrains.annotations.ApiStatus.Internal;
public final class PlayerStatsAPI implements PlayerStats {
private static RequestManager requestManager;
private static StatFormatter statFormatter;
private static StatManager statManager;
private static StatFormatter statFormatter;
@Internal
public PlayerStatsAPI(RequestManager request, StatManager stat, StatFormatter format) {
requestManager = request;
statFormatter = format;
statManager = stat;
statFormatter = format;
}
private TextComponent getFancyStat(CommandSender sender, String[] args) throws IllegalArgumentException {
StatRequest request = requestManager.generateRequest(sender, args);
if (requestManager.requestIsValid(request)) {
switch (request.getSelection()) {
@Override
public TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull String playerName) throws IllegalArgumentException {
return getFormattedStatistic(Target.PLAYER, statistic, null, null, playerName);
}
@Override
public TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull Material material, @NotNull String playerName) throws IllegalArgumentException {
return getFormattedStatistic(Target.PLAYER, statistic, material, null, playerName);
}
@Override
public TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull EntityType entity, @NotNull String playerName) throws IllegalArgumentException {
return getFormattedStatistic(Target.PLAYER, statistic, null, entity, playerName);
}
@Override
public TextComponent getServerStat(@NotNull Statistic statistic) throws IllegalArgumentException {
return getFormattedStatistic(Target.SERVER, statistic, null, null, null);
}
@Override
public TextComponent getServerStat(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
return getFormattedStatistic(Target.SERVER, statistic, material, null, null);
}
@Override
public TextComponent getServerStat(@NotNull Statistic statistic, @NotNull EntityType entity) throws IllegalArgumentException {
return getFormattedStatistic(Target.SERVER, statistic, null, entity, null);
}
@Override
public TextComponent getTopStats(@NotNull Statistic statistic) throws IllegalArgumentException {
return getFormattedStatistic(Target.TOP, statistic, null, null, null);
}
@Override
public TextComponent getTopStats(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
return getFormattedStatistic(Target.TOP, statistic, material, null, null);
}
@Override
public TextComponent getTopStats(@NotNull Statistic statistic, @NotNull EntityType entity) throws IllegalArgumentException {
return getFormattedStatistic(Target.TOP, statistic, null, entity, null);
}
private TextComponent getFormattedStatistic(@NotNull Target selection, @NotNull Statistic statistic,
@Nullable Material material, @Nullable EntityType entity, @Nullable String playerName) throws IllegalArgumentException {
StatRequest request = requestManager.generateRequest(selection, statistic, material, entity, playerName);
if (requestManager.validateAPIRequest(request)) {
switch (selection) {
case PLAYER -> {
int stat = statManager.getPlayerStat(request);
return statFormatter.formatPlayerStat(request, stat, true);
return statFormatter.formatPlayerStat(request, stat);
}
case SERVER -> {
long stat = statManager.getServerStat(request);
return statFormatter.formatServerStat(request, stat, true);
return statFormatter.formatServerStat(request, stat);
}
case TOP -> {
LinkedHashMap<String, Integer> stats = statManager.getTopStats(request);
return statFormatter.formatTopStat(request, stats, true);
return statFormatter.formatTopStat(request, stats);
}
}
}
throw new IllegalArgumentException("This is not a valid stat-request!");
}
@Override
public TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull OfflinePlayer player) {
return null;
}
@Override
public TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull Material material, @NotNull OfflinePlayer player) {
return null;
}
@Override
public TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull EntityType entity, @NotNull OfflinePlayer player) {
return null;
}
@Override
public TextComponent getServerStat(@NotNull Statistic statistic) {
return null;
}
@Override
public TextComponent getServerStat(@NotNull Statistic statistic, @NotNull Material material) {
return null;
}
@Override
public TextComponent getServerStat(@NotNull Statistic statistic, @NotNull EntityType entity) {
return null;
}
@Override
public TextComponent getTopStats(@NotNull Statistic statistic) {
return null;
}
@Override
public TextComponent getTopStats(@NotNull Statistic statistic, @NotNull Material material) {
return null;
}
@Override
public TextComponent getTopStats(@NotNull Statistic statistic, @NotNull EntityType entity) {
return null;
}
@Override
public String statResultComponentToString(TextComponent component) {
return statFormatter.statResultToString(component);
}
}

View File

@ -0,0 +1,29 @@
package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
/** The {@link RequestGenerator} can turn some user input, such as a String
(for example: "stat animals_bred") into a specific {@link StatRequest} that holds
all the information {@link PlayerStatsAPI} needs to work with.
This StatRequest is then used by the {@link StatCalculator} to get the desired statistic data.*/
public interface RequestGenerator {
/** This will create a {@link StatRequest} from the provided args, with the requesting Player (or Console)
as CommandSender. This CommandSender will receive feedback messages if the SimpleStatRequest could not be created.
@param args an Array of args such as a CommandSender would put in Minecraft chat:
<p>- a stat-name (example: "mine_block")</p>
<p>- if applicable, a sub-stat-name (example: diorite)(</p>
<p>- a target for this lookup: can be "top", "server", "player" (or "me" to indicate the current CommandSender)</p>
<p>- if "player" was chosen, include a player-name</p>
@param sender the CommandSender that requested this specific statistic
@throws IllegalArgumentException if the args do not result in a valid statistic look-up*/
StatRequest generateRequest(CommandSender sender, String[] args);
StatRequest generateRequest(@NotNull Target selection, @NotNull Statistic statistic, Material material, EntityType entity, String playerName);
}

View File

@ -1,37 +0,0 @@
package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.EntityType;
public interface SimpleRequest {
void setStatistic(Statistic statistic);
Statistic getStatistic();
void setSubStatEntry(String subStatEntry);
String getSubStatEntry();
void setPlayerName(String playerName);
String getPlayerName();
void setSelection(Target selection);
Target getSelection();
void setEntity(EntityType entity);
EntityType getEntity();
void setBlock(Material material);
Material getBlock();
void setItem(Material item);
Material getItem();
}

View File

@ -4,7 +4,9 @@ import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import java.util.LinkedHashMap;
public interface StatGetter {
/** The {@link StatCalculator} represents the actual statistic-getting magic that happens once a valid
{@link StatRequest} has been obtained. It takes a valid StatRequest, and returns (a map of) numbers. */
public interface StatCalculator {
int getPlayerStat(StatRequest request);

View File

@ -2,21 +2,19 @@ package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.minimessage.MiniMessage;
import java.util.LinkedHashMap;
/** Interface that defines the output functionality PlayerStats should have.
This is meant for an outgoing API - for internal use, more output functionality may exist. */
/** The {@link StatFormatter} defines what the output of any given statistic look-up should be.
<br></br>
<p>The Formatter takes a {@link StatRequest} and the result of {@link StatCalculator} calculations, and transforms the
request object and raw numbers into a pretty message (TextComponent) with all the relevant information in it.
This output is ready to be sent to a Player or Console, or can be turned into a String representation if necessary.*/
public interface StatFormatter {
default String statResultToString(TextComponent statResult) {
return MiniMessage.miniMessage().serialize(statResult);
}
TextComponent formatPlayerStat(StatRequest request, int playerStat);
TextComponent formatPlayerStat(StatRequest request, int playerStat, boolean isAPIRequest);
TextComponent formatServerStat(StatRequest request, long serverStat);
TextComponent formatServerStat(StatRequest request, long serverStat, boolean isAPIRequest);
TextComponent formatTopStat(StatRequest request, LinkedHashMap<String, Integer> topStats, boolean isAPIRequest);
TextComponent formatTopStat(StatRequest request, LinkedHashMap<String, Integer> topStats);
}

View File

@ -1,19 +1,14 @@
package com.gmail.artemis.the.gr8.playerstats.commands;
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
import com.gmail.artemis.the.gr8.playerstats.statistic.RequestManager;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import net.kyori.adventure.text.TextComponent;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
public class StatCommand implements CommandExecutor {
@ -38,7 +33,7 @@ public class StatCommand implements CommandExecutor {
}
else {
StatRequest request = requestManager.generateRequest(sender, args);
if (requestManager.requestIsValid(request)) {
if (requestManager.validateRequest(request)) {
threadManager.startStatThread(request);
} else {
return false;

View File

@ -31,10 +31,11 @@ public class ConfigHandler {
}
/** Checks the number that "config-version" returns to see if the config needs updating, and if so, send it to the {@link ConfigUpdateHandler}.
<p>PlayerStats 1.1: "config-version" doesn't exist.</p>
<p>PlayerStats 1.2: "config-version" is 2.</p>
<p>PlayerStats 1.3: "config-version" is 3. </P>
<p>PlayerStats 1.4: "config-version" is 4.</p>*/
<br></br>
<br>PlayerStats 1.1: "config-version" doesn't exist.</br>
<br>PlayerStats 1.2: "config-version" is 2.</br>
<br>PlayerStats 1.3: "config-version" is 3. </br>
<br>PlayerStats 1.4: "config-version" is 4.</br>*/
private void checkConfigVersion() {
if (!config.contains("config-version") || config.getInt("config-version") != configVersion) {
new ConfigUpdateHandler(plugin, configFile, configVersion);
@ -66,79 +67,81 @@ public class ConfigHandler {
}
/** Returns the desired debugging level.
<p>1 = low (only show unexpected errors)</p>
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
<p>3 = high (log all tasks and time taken)</p>
<p>Default: 1</p>*/
<br></br>
<br>1 = low (only show unexpected errors)</br>
<br>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</br>
<br>3 = high (log all tasks and time taken)</br>
<br></br>
<br>Default: 1</br>*/
public int getDebugLevel() {
return config.getInt("debug-level", 1);
}
/** Returns true if command-senders should be limited to one stat-request at a time.
<p>Default: true</p>*/
<br>Default: true</br>*/
public boolean limitStatRequests() {
return config.getBoolean("only-allow-one-lookup-at-a-time-per-player", true);
}
/** Returns true if stat-sharing is allowed.
<p>Default: true</p>*/
<br>Default: true</br>*/
public boolean allowStatSharing() {
return config.getBoolean("enable-stat-sharing", true);
}
/** Returns the number of minutes a player has to wait before being able to
share another stat-result.
<p>Default: 0</p>*/
<br>Default: 0</br>*/
public int getStatShareWaitingTime() {
return config.getInt("waiting-time-before-sharing-again", 0);
}
/** Returns the config setting for include-whitelist-only.
<p>Default: false</p>*/
<br>Default: false</br>*/
public boolean whitelistOnly() {
return config.getBoolean("include-whitelist-only", false);
}
/** Returns the config setting for exclude-banned-players.
<p>Default: false</p>*/
<br>Default: false</br>*/
public boolean excludeBanned() {
return config.getBoolean("exclude-banned-players", false);
}
/** Returns the number of maximum days since a player has last been online.
<p>Default: 0 (which signals not to use this limit)</p>*/
<br>Default: 0 (which signals not to use this limit)</br>*/
public int getLastPlayedLimit() {
return config.getInt("number-of-days-since-last-joined", 0);
}
/** Whether to use TranslatableComponents wherever possible.
Currently supported: statistic, block, item and entity names.
<p>Default: true</p>*/
<br>Default: true</br>*/
public boolean useTranslatableComponents() {
return config.getBoolean("translate-to-client-language", true);
}
/** Whether to use HoverComponents for additional information.
<p>Default: true</p>*/
<br>Default: true</br>*/
public boolean useHoverText() {
return config.getBoolean("enable-hover-text", true);
}
/** Whether to use festive formatting, such as pride colors.
<p>Default: true</p> */
<br>Default: true</br> */
public boolean useFestiveFormatting() {
return config.getBoolean("enable-festive-formatting", true);
}
/** Whether to use rainbow colors for the [PlayerStats] prefix rather than the default gold/purple.
<p>Default: false</p> */
<br>Default: false</br> */
public boolean useRainbowMode() {
return config.getBoolean("rainbow-mode", false);
}
/** Whether to use enters before the statistic output in chat.
Enters create some separation between the previous things that have been said in chat and the stat-result.
<p>Default: true for non-shared top statistics, false for everything else</p>*/
<br>Default: true for non-shared top statistics, false for everything else</br>*/
public boolean useEnters(Target selection, boolean getSharedSetting) {
ConfigurationSection section = config.getConfigurationSection("use-enters");
boolean def = selection == Target.TOP && !getSharedSetting;
@ -157,49 +160,49 @@ public class ConfigHandler {
}
/** Returns the config setting for use-dots.
<p>Default: true</p>*/
<br>Default: true</br>*/
public boolean useDots() {
return config.getBoolean("use-dots", true);
}
/** Returns the config setting for top-list-max-size.
<p>Default: 10</p> */
<br>Default: 10</br> */
public int getTopListMaxSize() {
return config.getInt("top-list-max-size", 10);
}
/** Returns a String that represents the title for a top statistic.
<p>Default: "Top"</p>*/
<br>Default: "Top"</br>*/
public String getTopStatsTitle() {
return config.getString("top-list-title", "Top");
}
/** Returns a String that represents the title for a server stat.
<p>Default: "Total on"</p> */
<br>Default: "Total on"</br> */
public String getServerTitle() {
return config.getString("total-server-stat-title", "Total on");
}
/** Returns the specified server name for a server stat title.
<p>Default: "this server"</p>*/
<br>Default: "this server"</br>*/
public String getServerName() {
return config.getString("your-server-name", "this server");
}
/** Returns the unit that should be used for distance-related statistics.
<p>Default: Blocks for plain text, km for hover-text</p>*/
<br>Default: Blocks for plain text, km for hover-text</br>*/
public String getDistanceUnit(boolean isHoverText) {
return getUnitString(isHoverText, "blocks", "km", "distance-unit");
}
/** Returns the unit that should be used for damage-based statistics.
<p>Default: Hearts for plain text, HP for hover-text.</p>*/
<br>Default: Hearts for plain text, HP for hover-text.</br>*/
public String getDamageUnit(boolean isHoverText) {
return getUnitString(isHoverText, "hearts", "hp", "damage-unit");
}
/** Whether PlayerStats should automatically detect the most suitable unit to use for time-based statistics.
<p>Default: true</p>*/
<br>Default: true</br>*/
public boolean autoDetectTimeUnit(boolean isHoverText) {
String path = "auto-detect-biggest-time-unit";
if (isHoverText) {
@ -210,7 +213,7 @@ public class ConfigHandler {
}
/** How many additional units should be displayed next to the most suitable largest unit for time-based statistics.
<p>Default: 1 for plain text, 0 for hover-text</p>*/
<br>Default: 1 for plain text, 0 for hover-text</br>*/
public int getNumberOfExtraTimeUnits(boolean isHoverText) {
String path = "number-of-extra-units";
if (isHoverText) {
@ -222,14 +225,14 @@ public class ConfigHandler {
/** Returns the unit that should be used for time-based statistics.
(this will return the largest unit that should be used).
<p>Default: days for plain text, hours for hover-text</p>*/
<br>Default: days for plain text, hours for hover-text</br>*/
public String getTimeUnit(boolean isHoverText) {
return getTimeUnit(isHoverText, false);
}
/** Returns the unit that should be used for time-based statistics. If the optional smallUnit flag is true,
this will return the smallest unit (and otherwise the largest).
<p>Default: hours for plain text, seconds for hover-text</p>*/
<br>Default: hours for plain text, seconds for hover-text</br>*/
public String getTimeUnit(boolean isHoverText, boolean smallUnit) {
if (smallUnit) {
return getUnitString(isHoverText, "hours", "seconds", "smallest-time-unit");
@ -239,30 +242,30 @@ public class ConfigHandler {
/** Returns an integer between 0 and 100 that represents how much lighter a hoverColor should be.
So 20 would mean 20% lighter.
<p>Default: 20</p>*/
<br>Default: 20</br>*/
public int getHoverTextAmountLighter() {
return config.getInt("hover-text-amount-lighter", 20);
}
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
* <p>Style: "italic"</p>
* <p>Color: "gray"</p>*/
* <br>Style: "italic"</br>
* <br>Color: "gray"</br>*/
public String getSharedByTextDecoration(boolean getStyleSetting) {
String def = getStyleSetting ? "italic" : "gray";
return getDecorationString(null, getStyleSetting, def, "shared-by");
}
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
* <p>Style: "none"</p>
* <p>Color: "#845EC2"</p>*/
* <br>Style: "none"</br>
* <br>Color: "#845EC2"</br>*/
public String getSharerNameDecoration(boolean getStyleSetting) {
return getDecorationString(null, getStyleSetting, "#845EC2", "player-name");
}
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
<p>Style: "none"</p>
<p>Color Top: "green"</p>
<p>Color Individual/Server: "gold"</p>*/
<br>Style: "none"</br>
<br>Color Top: "green"</br>
<br>Color Individual/Server: "gold"</br>*/
public String getPlayerNameDecoration(Target selection, boolean getStyleSetting) {
String def;
if (selection == Target.TOP) {
@ -275,7 +278,7 @@ public class ConfigHandler {
}
/** Returns true if playerNames Style is "bold", false if it is not.
<p>Default: false</p>*/
<br>Default: false</br>*/
public boolean playerNameIsBold() {
ConfigurationSection style = getRelevantSection(Target.PLAYER);
@ -287,23 +290,23 @@ public class ConfigHandler {
}
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
<p>Style: "none"</p>
<p>Color: "yellow"</p>*/
<br>Style: "none"</br>
<br>Color: "yellow"</br>*/
public String getStatNameDecoration(Target selection, boolean getStyleSetting) {
return getDecorationString(selection, getStyleSetting, "yellow", "stat-names");
}
/** Returns a String that represents either a Chat Color, hex color code, or a Style. Default values are:
<p>Style: "none"</p>
<p>Color: "#FFD52B"</p>*/
<br>Style: "none"</br>
<br>Color: "#FFD52B"</br>*/
public String getSubStatNameDecoration(Target selection, boolean getStyleSetting) {
return getDecorationString(selection, getStyleSetting, "#FFD52B", "sub-stat-names");
}
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p>
<p>Color Top: "#55AAFF"</p>
<p>Color Individual/Server: "#ADE7FF"</p> */
<br>Style: "none"</br>
<br>Color Top: "#55AAFF"</br>
<br>Color Individual/Server: "#ADE7FF"</br> */
public String getStatNumberDecoration(Target selection, boolean getStyleSetting) {
String def;
if (selection == Target.TOP) {
@ -316,9 +319,9 @@ public class ConfigHandler {
}
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p>
<p>Color Top: "yellow"</p>
<p>Color Server: "gold"</p>*/
<br>Style: "none"</br>
<br>Color Top: "yellow"</br>
<br>Color Server: "gold"</br>*/
public String getTitleDecoration(Target selection, boolean getStyleSetting) {
String def;
if (selection == Target.TOP) {
@ -331,29 +334,29 @@ public class ConfigHandler {
}
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p>
<p>Color: "gold"</p>*/
<br>Style: "none"</br>
<br>Color: "gold"</br>*/
public String getTitleNumberDecoration(boolean getStyleSetting) {
return getDecorationString(Target.TOP, getStyleSetting, "gold", "title-number");
}
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p>
<p>Color: "#FFB80E"</p>*/
<br>Style: "none"</br>
<br>Color: "#FFB80E"</br>*/
public String getServerNameDecoration(boolean getStyleSetting) {
return getDecorationString(Target.SERVER, getStyleSetting, "#FFB80E", "server-name");
}
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p>
<p>Color: "gold"</p>*/
<br>Style: "none"</br>
<br>Color: "gold"</br>*/
public String getRankNumberDecoration(boolean getStyleSetting) {
return getDecorationString(Target.TOP, getStyleSetting, "gold", "rank-numbers");
}
/** Returns a String that represents either a Chat Color, hex color code, or Style. Default values are:
<p>Style: "none"</p>
<p>Color: "dark_gray"</p> */
<br>Style: "none"</br>
<br>Color: "dark_gray"</br> */
public String getDotsDecoration(boolean getStyleSetting) {
return getDecorationString(Target.TOP, getStyleSetting, "dark_gray", "dots");
}

View File

@ -1,9 +1,10 @@
package com.gmail.artemis.the.gr8.playerstats.enums;
/** Represents the debugging level that PlayerStats can use.
<p>1 = low (only show unexpected errors)</p>
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
<p>3 = high (log all tasks and time taken)</p>*/
<br></br>
<br>1 = low (only show unexpected errors)</br>
<br>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</br>
<br>3 = high (log all tasks and time taken)</br>*/
public enum DebugLevel {
LOW, MEDIUM, HIGH
}

View File

@ -1,6 +1,5 @@
package com.gmail.artemis.the.gr8.playerstats.models;
import com.gmail.artemis.the.gr8.playerstats.api.SimpleRequest;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import org.bukkit.Material;
import org.bukkit.Statistic;
@ -13,9 +12,10 @@ import org.jetbrains.annotations.NotNull;
It is generated from the args provided by a CommandSender when /stat is called,
and always contains this CommandSender. By default, {@link #getSelection()}
will return {@link Target#TOP}, unless another selection is specified in the args.*/
public final class StatRequest implements SimpleRequest {
public final class StatRequest {
private CommandSender sender;
private final CommandSender sender;
private final boolean isAPIRequest;
private Statistic statistic;
private String playerName;
private Target selection;
@ -26,17 +26,20 @@ public final class StatRequest implements SimpleRequest {
private Material item;
private boolean playerFlag;
//make a SimpleRequest for a given CommandSender with some default values
public StatRequest(CommandSender s) {
if (s != null) {
setCommandSender(s);
}
//make a SimpleStatRequest for a given CommandSender with some default values
public StatRequest(@NotNull CommandSender sender) {
this(sender, false);
}
public StatRequest(@NotNull CommandSender sender, boolean isAPIRequest) {
this.sender = sender;
this.isAPIRequest = isAPIRequest;
selection = Target.TOP;
playerFlag = false;
}
public void setCommandSender(@NotNull CommandSender sender) {
this.sender = sender;
public boolean isAPIRequest() {
return isAPIRequest;
}
public @NotNull CommandSender getCommandSender() {

View File

@ -165,6 +165,11 @@ public class MessageBuilder {
}
}
/** Returns a BiFunction for a player statistic. This BiFunction will return a statResult,
the shape of which is determined by the 2 parameters the BiFunction gets.
<p>- UUID shareCode: if a shareCode is provided, a clickable "share" button will be added.
<br>- CommandSender sender: if a sender is provided, a signature with "shared by sender-name" will be added.</br>
<br>- If both parameters are null, the statResult will be returned as is.</br>*/
public BiFunction<UUID, CommandSender, TextComponent> formattedPlayerStatFunction(int stat, @NotNull StatRequest request) {
TextComponent playerStat = Component.text()
.append(componentFactory.playerName(request.getPlayerName(), Target.PLAYER)
@ -179,6 +184,11 @@ public class MessageBuilder {
return getFormattingFunction(playerStat, Target.PLAYER);
}
/** Returns a BiFunction for a server statistic. This BiFunction will return a statResult,
the shape of which is determined by the 2 parameters the BiFunction gets.
<p>- UUID shareCode: if a shareCode is provided, a clickable "share" button will be added.
<br>- CommandSender sender: if a sender is provided, a signature with "shared by sender-name" will be added.</br>
<br>- If both parameters are null, the statResult will be returned as is.</br>*/
public BiFunction<UUID, CommandSender, TextComponent> formattedServerStatFunction(long stat, @NotNull StatRequest request) {
TextComponent serverStat = text()
.append(componentFactory.title(config.getServerTitle(), Target.SERVER))
@ -194,6 +204,11 @@ public class MessageBuilder {
return getFormattingFunction(serverStat, Target.SERVER);
}
/** Returns a BiFunction for a top statistic. This BiFunction will return a statResult,
the shape of which is determined by the 2 parameters the BiFunction gets.
<p>- UUID shareCode: if a shareCode is provided, a clickable "share" button will be added.
<br>- CommandSender sender: if a sender is provided, a signature with "shared by sender-name" will be added.</br>
<br>- If both parameters are null, the statResult will be returned as is.</br>*/
public BiFunction<UUID, CommandSender, TextComponent> formattedTopStatFunction(@NotNull LinkedHashMap<String, Integer> topStats, @NotNull StatRequest request) {
final TextComponent title = getTopStatsTitleComponent(request, topStats.size());
final TextComponent shortTitle = getTopStatsTitleShortComponent(request, topStats.size());

View File

@ -51,30 +51,27 @@ public final class OutputManager implements StatFormatter {
}
@Override
public TextComponent formatPlayerStat(@NotNull StatRequest request, int playerStat, boolean isAPIRequest) {
CommandSender sender = request.getCommandSender();
public TextComponent formatPlayerStat(@NotNull StatRequest request, int playerStat) {
BiFunction<UUID, CommandSender, TextComponent> playerStatFunction =
getWriter(sender).formattedPlayerStatFunction(playerStat, request);
getWriter(request).formattedPlayerStatFunction(playerStat, request);
return processBuildFunction(sender, playerStatFunction);
return processFunction(request.getCommandSender(), playerStatFunction);
}
@Override
public TextComponent formatServerStat(@NotNull StatRequest request, long serverStat, boolean isAPIRequest) {
CommandSender sender = request.getCommandSender();
public TextComponent formatServerStat(@NotNull StatRequest request, long serverStat) {
BiFunction<UUID, CommandSender, TextComponent> serverStatFunction =
getWriter(sender).formattedServerStatFunction(serverStat, request);
getWriter(request).formattedServerStatFunction(serverStat, request);
return processBuildFunction(sender, serverStatFunction);
return processFunction(request.getCommandSender(), serverStatFunction);
}
@Override
public TextComponent formatTopStat(@NotNull StatRequest request, @NotNull LinkedHashMap<String, Integer> topStats, boolean isAPIRequest) {
CommandSender sender = request.getCommandSender();
public TextComponent formatTopStat(@NotNull StatRequest request, @NotNull LinkedHashMap<String, Integer> topStats) {
BiFunction<UUID, CommandSender, TextComponent> topStatFunction =
getWriter(sender).formattedTopStatFunction(topStats, request);
getWriter(request).formattedTopStatFunction(topStats, request);
return processBuildFunction(sender, topStatFunction);
return processFunction(request.getCommandSender(), topStatFunction);
}
public void sendFeedbackMsg(@NotNull CommandSender sender, StandardMessage message) {
@ -121,26 +118,33 @@ public final class OutputManager implements StatFormatter {
adventure.sender(sender).sendMessage(component);
}
private TextComponent processBuildFunction(@Nullable CommandSender sender, @NotNull BiFunction<UUID, CommandSender, TextComponent> buildFunction) {
boolean saveOutput = sender != null &&
private TextComponent processFunction(CommandSender sender, @NotNull BiFunction<UUID, CommandSender, TextComponent> statResultFunction) {
boolean saveOutput = !(sender instanceof ConsoleCommandSender) &&
ShareManager.isEnabled() &&
shareManager.senderHasPermission(sender);
if (saveOutput) {
UUID shareCode =
shareManager.saveStatResult(sender.getName(), buildFunction.apply(null, sender));
return buildFunction.apply(shareCode, null);
shareManager.saveStatResult(sender.getName(), statResultFunction.apply(null, sender));
return statResultFunction.apply(shareCode, null);
}
else {
return buildFunction.apply(null, null);
return statResultFunction.apply(null, null);
}
}
/** If sender == null, this will return the regular writer*/
private MessageBuilder getWriter(CommandSender sender) {
return sender instanceof ConsoleCommandSender ? consoleWriter : writer;
}
private MessageBuilder getWriter(StatRequest request) {
if (request.isAPIRequest() || !request.isConsoleSender()) {
return writer;
} else {
return consoleWriter;
}
}
private void getMessageWriters(ConfigHandler config) {
boolean isBukkit = Bukkit.getName().equalsIgnoreCase("CraftBukkit");
if (config.useRainbowMode() ||

View File

@ -1,13 +1,14 @@
package com.gmail.artemis.the.gr8.playerstats.statistic;
import com.gmail.artemis.the.gr8.playerstats.api.RequestGenerator;
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
@ -15,7 +16,7 @@ import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class RequestManager {
public class RequestManager implements RequestGenerator {
private final OfflinePlayerHandler offlinePlayerHandler;
private static OutputManager outputManager;
@ -25,15 +26,6 @@ public class RequestManager {
RequestManager.outputManager = outputManager;
}
/** This will create a {@link StatRequest} from the provided args, with the requesting Player (or Console)
as CommandSender. This CommandSender will receive feedback messages if the SimpleRequest could not be created.
@param args an Array of args such as a CommandSender would put in Minecraft chat:
<p>- a stat-name (example: "mine_block")</p>
<p>- if applicable, a sub-stat-name (example: diorite)(</p>
<p>- a target for this lookup: can be "top", "server", "player" (or "me" to indicate the current CommandSender)</p>
<p>- if "player" was chosen, include a player-name</p>
@param sender the CommandSender that requested this specific statistic
@throws IllegalArgumentException if the args do not result in a valid statistic look-up*/
public StatRequest generateRequest(CommandSender sender, String[] args) {
StatRequest request = new StatRequest(sender);
for (String arg : args) {
@ -75,32 +67,66 @@ public class RequestManager {
return request;
}
public StatRequest generateRequest(@NotNull Target selection, @NotNull Statistic statistic, Material material, EntityType entity, OfflinePlayer player) {
return null;
public StatRequest generateRequest(@NotNull Target selection, @NotNull Statistic statistic, Material material, EntityType entity, String playerName) {
StatRequest request = new StatRequest(Bukkit.getConsoleSender());
request.setSelection(selection);
request.setStatistic(statistic);
switch (statistic.getType()) {
case BLOCK -> {
request.setBlock(material);
request.setSubStatEntry(material.toString());
}
case ITEM -> {
request.setItem(material);
request.setSubStatEntry(material.toString());
}
case ENTITY -> {
request.setEntity(entity);
request.setSubStatEntry(entity.toString());
}
}
if (selection == Target.PLAYER) request.setPlayerName(playerName);
return request;
}
/** This method validates the {@link StatRequest} and returns a feedback message if the Request is invalid.
It checks the following:
<p>1. Is a Statistic set?</p>
<p>2. Is a subStat needed, and is a subStat Enum constant present? (block/entity/item)</p>
<p>3. If the target is PLAYER, is a valid PlayerName provided? </p>
@return true if the SimpleRequest is valid, and false + an explanation message otherwise. */
public boolean requestIsValid(StatRequest request) {
/** Checks if a given {@link StatRequest} would result in a valid statistic look-up,
and sends a feedback message to the CommandSender that prompted the request if it is invalid.
<p>The following is checked:
<br>1. Is a Statistic set?</br>
<br>2. Is a sub-Statistic needed, and if so, is a corresponding Material/EntityType present?</br>
<br>3. If the target is PLAYER, is a valid PlayerName provided? </br>
@return true if the StatRequest is valid, and false otherwise. */
public boolean validateRequest(StatRequest request) {
return validateRequestAndSendMessage(request, request.getCommandSender());
}
/** Checks if a given {@link StatRequest} would result in a valid statistic look-up,
and sends a feedback message in the server console if it is invalid.
<p>The following is checked:
<br>1. Is a Statistic set?</br>
<br>2. Is a sub-Statistic needed, and if so, is a corresponding Material/EntityType present?</br>
<br>3. If the target is PLAYER, is a valid PlayerName provided? </br>
@return true if the StatRequest is valid, and false otherwise. */
public boolean validateAPIRequest(StatRequest request) {
return validateRequestAndSendMessage(request, Bukkit.getConsoleSender());
}
private boolean validateRequestAndSendMessage(StatRequest request, CommandSender sender) {
if (request.getStatistic() == null) {
outputManager.sendFeedbackMsg(request.getCommandSender(), StandardMessage.MISSING_STAT_NAME);
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_STAT_NAME);
return false;
}
Statistic.Type type = request.getStatistic().getType();
if (request.getSubStatEntry() == null && type != Statistic.Type.UNTYPED) {
outputManager.sendFeedbackMsgMissingSubStat(request.getCommandSender(), type);
outputManager.sendFeedbackMsgMissingSubStat(sender, type);
return false;
}
else if (!hasMatchingSubStat(request)) {
outputManager.sendFeedbackMsgWrongSubStat(request.getCommandSender(), type, request.getSubStatEntry());
outputManager.sendFeedbackMsgWrongSubStat(sender, type, request.getSubStatEntry());
return false;
}
else if (request.getSelection() == Target.PLAYER && request.getPlayerName() == null) {
outputManager.sendFeedbackMsg(request.getCommandSender(), StandardMessage.MISSING_PLAYER_NAME);
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_PLAYER_NAME);
return false;
}
else {
@ -108,7 +134,7 @@ public class RequestManager {
}
}
/** Adjust the SimpleRequest object if needed: unpack the playerFlag into a subStatEntry,
/** Adjust the StatRequest object if needed: unpack the playerFlag into a subStatEntry,
try to retrieve the corresponding Enum Constant for any relevant block/entity/item,
and remove any unnecessary subStatEntries.*/
private void patchRequest(StatRequest request) {

View File

@ -1,9 +1,8 @@
package com.gmail.artemis.the.gr8.playerstats.statistic;
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
import com.gmail.artemis.the.gr8.playerstats.api.StatGetter;
import com.gmail.artemis.the.gr8.playerstats.api.StatCalculator;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.google.common.collect.ImmutableList;
@ -15,7 +14,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
public final class StatManager implements StatGetter {
public final class StatManager implements StatCalculator {
private final OfflinePlayerHandler offlinePlayerHandler;
private static int topListMaxSize;

View File

@ -59,9 +59,9 @@ public class StatThread extends Thread {
Target selection = request.getSelection();
try {
TextComponent statResult = switch (selection) {
case PLAYER -> outputManager.formatPlayerStat(request, statManager.getPlayerStat(request), false);
case TOP -> outputManager.formatTopStat(request, statManager.getTopStats(request), false);
case SERVER -> outputManager.formatServerStat(request, statManager.getServerStat(request), false);
case PLAYER -> outputManager.formatPlayerStat(request, statManager.getPlayerStat(request));
case TOP -> outputManager.formatTopStat(request, statManager.getTopStats(request));
case SERVER -> outputManager.formatServerStat(request, statManager.getServerStat(request));
};
outputManager.sendToCommandSender(request.getCommandSender(), statResult);
}

View File

@ -37,10 +37,10 @@ public final class MyLogger {
}
/** Sets the desired debugging level.
<p>1 = low (only show unexpected errors)</p>
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
<p>3 = high (log all tasks and time taken)</p>
<p>Default: 1</p>*/
<br>1 = low (only show unexpected errors)</br>
<br>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</br>
<br>3 = high (log all tasks and time taken)</br>
<br>Default: 1</br>*/
public static void setDebugLevel(int level) {
if (level == 2) {
debugLevel = DebugLevel.MEDIUM;
@ -108,26 +108,26 @@ public final class MyLogger {
}
}
/** StatFormatter to console that the given thread has been created (but not started yet).*/
/** Output to console that the given thread has been created (but not started yet).*/
public static void threadCreated(String threadName) {
if (debugLevel != DebugLevel.LOW) {
logger.info(threadName + " created!");
}
}
/** StatFormatter to console that the given thread has been started. */
/** Output to console that the given thread has been started. */
public static void threadStart(String threadName) {
if (debugLevel == DebugLevel.MEDIUM || debugLevel == DebugLevel.HIGH) {
logger.info(threadName + " started!");
}
}
/** StatFormatter to console that another reloadThread is already running. */
/** Output to console that another reloadThread is already running. */
public static void threadAlreadyRunning(String threadName) {
logger.info("Another reloadThread is already running! (" + threadName + ")");
}
/** StatFormatter to console that the executingThread is waiting for otherThread to finish up. */
/** Output to console that the executingThread is waiting for otherThread to finish up. */
public static void waitingForOtherThread(String executingThread, String otherThread) {
logger.info(executingThread + ": Waiting for " + otherThread + " to finish up...");
}
@ -176,7 +176,7 @@ public final class MyLogger {
}
}
/** StatFormatter to console that an action has finished.
/** Output to console that an action has finished.
<p>For the ReloadThread, if DebugLevel is HIGH, output the left-over processed players.
For both threads, if DebugLevel is MEDIUM or HIGH, output the names of the threads that were used.</p>
@param thread 1 for ReloadThread, 2 for StatThread */
@ -195,7 +195,7 @@ public final class MyLogger {
}
}
/** StatFormatter to console how long a certain task has taken (regardless of DebugLevel).
/** Output to console how long a certain task has taken (regardless of DebugLevel).
@param className Name of the class executing the task
@param methodName Name or description of the task
@param startTime Timestamp marking the beginning of the task */
@ -203,7 +203,7 @@ public final class MyLogger {
logTimeTaken(className, methodName, startTime, DebugLevel.LOW);
}
/** StatFormatter to console how long a certain task has taken if DebugLevel is equal to or higher than the specified threshold.
/** Output to console how long a certain task has taken if DebugLevel is equal to or higher than the specified threshold.
@param className Name of the class executing the task
@param methodName Name or description of the task
@param startTime Timestamp marking the beginning of the task