From 06ad69133784a654f6b2384360474d99b7fb5086 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Wed, 27 Dec 2023 23:11:10 -0700 Subject: [PATCH 01/21] update spigot api version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 64f4736..05749ba 100644 --- a/build.gradle +++ b/build.gradle @@ -76,7 +76,7 @@ dependencies { compileOnly 'me.clip:placeholderapi:2.11.2' compileOnly "net.kyori:adventure-text-serializer-legacy:4.13.1" compileOnly "net.kyori:adventure-text-serializer-plain:4.13.1" - compileOnly "org.spigotmc:spigot-api:1.20-R0.1-SNAPSHOT" + compileOnly "org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT" compileOnly 'com.github.LeonMangler:SuperVanish:6.2.17' compileOnly 'com.github.mbax:VanishNoPacket:3.22' compileOnly 'org.jetbrains:annotations:23.0.0' From a8b7084453a393f1fecaab31a45022f40208afda Mon Sep 17 00:00:00 2001 From: CyR1en Date: Wed, 27 Dec 2023 23:57:46 -0700 Subject: [PATCH 02/21] hook Towny for cache filter --- build.gradle | 2 + .../commandprompter/hook/hooks/TownyHook.java | 76 +++++++++++++++++++ .../commandprompter/prompt/ui/HeadCache.java | 5 ++ src/main/resources/plugin.yml | 2 +- 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java diff --git a/build.gradle b/build.gradle index 05749ba..39ddd57 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,7 @@ repositories { maven { url 'https://repo.codemc.io/repository/maven-snapshots/' } maven { url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } maven { url 'https://jitpack.io' } + maven { url 'https://repo.glaremasters.me/repository/towny/'} flatDir { dirs 'libs' } } @@ -76,6 +77,7 @@ dependencies { compileOnly 'me.clip:placeholderapi:2.11.2' compileOnly "net.kyori:adventure-text-serializer-legacy:4.13.1" compileOnly "net.kyori:adventure-text-serializer-plain:4.13.1" + compileOnly 'com.palmergames.bukkit.towny:towny:0.100.0.0' compileOnly "org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT" compileOnly 'com.github.LeonMangler:SuperVanish:6.2.17' compileOnly 'com.github.mbax:VanishNoPacket:3.22' diff --git a/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java b/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java new file mode 100644 index 0000000..676d886 --- /dev/null +++ b/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java @@ -0,0 +1,76 @@ +package com.cyr1en.commandprompter.hook.hooks; + +import com.cyr1en.commandprompter.CommandPrompter; +import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; +import com.cyr1en.commandprompter.prompt.ui.CacheFilter; +import com.cyr1en.commandprompter.prompt.ui.HeadCache; +import com.palmergames.bukkit.towny.TownyAPI; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +@TargetPlugin(pluginName = "Towny") +public class TownyHook extends BaseHook { + public TownyHook(CommandPrompter plugin) { + super(plugin); + } + + public void registerFilters(HeadCache cache) { + cache.registerFilter(new TownFilter()); + cache.registerFilter(new NationFilter()); + } + + /** + * A PlayerUI filter that would filter all players that are in the same town as the relative player. + */ + private static class TownFilter extends CacheFilter { + + + public TownFilter() { + super(Pattern.compile("t"), "PlayerUI.Filter-Format.TownyTown"); + } + + @Override + public CacheFilter reConstruct(String promptKey) { + return this; + } + + @Override + public List filter(Player relativePlayer) { + var town = TownyAPI.getInstance().getTown(relativePlayer); + if (town == null) return List.of(); + return town.getResidents().stream() + .map(r -> Bukkit.getPlayer(r.getName())) + .filter(Objects::nonNull) + .toList(); + } + } + + /** + * A PlayerUI filter that would filter all players that are in the same nation as the relative player. + */ + private static class NationFilter extends CacheFilter { + + public NationFilter() { + super(Pattern.compile("n"), "PlayerUI.Filter-Format.TownyNation"); + } + + @Override + public CacheFilter reConstruct(String promptKey) { + return this; + } + + @Override + public List filter(Player relativePlayer) { + var nation = TownyAPI.getInstance().getNation(relativePlayer); + if (nation == null) return List.of(); + return nation.getResidents().stream() + .map(r -> Bukkit.getPlayer(r.getName())) + .filter(Objects::nonNull) + .toList(); + } + } +} diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java index 38e8826..c5ab3c6 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java @@ -3,6 +3,7 @@ import com.cyr1en.commandprompter.CommandPrompter; import com.cyr1en.commandprompter.PluginLogger; import com.cyr1en.commandprompter.hook.hooks.PapiHook; +import com.cyr1en.commandprompter.hook.hooks.TownyHook; import com.cyr1en.commandprompter.util.Util; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -58,6 +59,10 @@ public HeadCache(CommandPrompter plugin) { private void registerFilters() { registerFilter(new CacheFilter.WorldFilter()); registerFilter(new CacheFilter.RadialFilter()); + plugin.getHookContainer() + .getHook(TownyHook.class) + .ifHooked(hook -> hook.registerFilters(this)) + .complete(); } public void registerFilter(CacheFilter filter) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index be9228f..72eda8a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,7 +2,7 @@ name: ${projectName} version: ${projectVersion} author: ${projectAuthor} description: ${projectDescription} -softdepend: [VentureChat, SuperVanish, PuerkasChat, PlaceholderAPI, CarbonChat] +softdepend: [VentureChat, SuperVanish, PuerkasChat, PlaceholderAPI, CarbonChat, Towny, PlaceholderAPI] main: ${projectEntry} api-version: 1.13 From d5010dc3ede9fafd8c70589239df762976ac2041 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Thu, 28 Dec 2023 01:51:29 -0700 Subject: [PATCH 03/21] better doc --- .../cyr1en/commandprompter/prompt/ui/CacheFilter.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java b/src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java index 83cf0a2..7aec8af 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java @@ -71,7 +71,16 @@ public String toString() { } /** - * Clone this cache filter with a new prompt key. + * A method that allows you to reconstruct a subclass of {@link CacheFilter} + * based on the prompt key. + * + *

+ * In cases where the prompt key contains additional information, this method + * could be used to reconstruct a certain subclass of {@link CacheFilter}. + * + *

+ * Additional filter information can be parsed from the {@link com.cyr1en.commandprompter.prompt.PromptParser} + * but for better readability, it is recommended to use this method instead. * * @param promptKey the prompt key * @return the cloned cache filter From 3df6211e7973f7bf5dea01f0df473860a487d871 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Fri, 29 Dec 2023 23:55:30 -0700 Subject: [PATCH 04/21] better input validation system --- .../api/prompt/InputValidator.java | 36 +++++++++++++++++++ .../commandprompter/api/prompt/Prompt.java | 30 ++++------------ .../commandprompter/config/PromptConfig.java | 26 ++++++++++---- .../commandprompter/prompt/PromptManager.java | 6 ++-- .../commandprompter/prompt/PromptParser.java | 13 ++++--- .../prompt/prompts/AbstractPrompt.java | 26 +++++--------- .../prompt/validators/NoopValidator.java | 25 +++++++++++++ .../validators/OnlinePlayerValidator.java | 22 ++++++++++++ .../prompt/validators/RegexValidator.java | 34 ++++++++++++++++++ 9 files changed, 163 insertions(+), 55 deletions(-) create mode 100644 src/main/java/com/cyr1en/commandprompter/api/prompt/InputValidator.java create mode 100644 src/main/java/com/cyr1en/commandprompter/prompt/validators/NoopValidator.java create mode 100644 src/main/java/com/cyr1en/commandprompter/prompt/validators/OnlinePlayerValidator.java create mode 100644 src/main/java/com/cyr1en/commandprompter/prompt/validators/RegexValidator.java diff --git a/src/main/java/com/cyr1en/commandprompter/api/prompt/InputValidator.java b/src/main/java/com/cyr1en/commandprompter/api/prompt/InputValidator.java new file mode 100644 index 0000000..c6381a8 --- /dev/null +++ b/src/main/java/com/cyr1en/commandprompter/api/prompt/InputValidator.java @@ -0,0 +1,36 @@ +package com.cyr1en.commandprompter.api.prompt; + + +/** + * A functional interface for validating input. + * + *

Implement this interface to create a custom validator for your prompt.

+ */ +public interface InputValidator { + + /** + * Validate the input. + * + *

Return true if the input is valid, false otherwise.

+ * + * @param input the input to validate + * @return true if the input is valid, false otherwise + */ + boolean validate(String input); + + /** + * Get the alias for this validator. + * + *

The alias is used to identify the validator.

+ * + * @return the alias for this validator + */ + String alias(); + + /** + * Get the message to send when validation fails. + * + * @return the message to send when validation fails + */ + String messageOnFail(); +} diff --git a/src/main/java/com/cyr1en/commandprompter/api/prompt/Prompt.java b/src/main/java/com/cyr1en/commandprompter/api/prompt/Prompt.java index bdb01b7..962c6ca 100644 --- a/src/main/java/com/cyr1en/commandprompter/api/prompt/Prompt.java +++ b/src/main/java/com/cyr1en/commandprompter/api/prompt/Prompt.java @@ -30,7 +30,6 @@ import com.cyr1en.commandprompter.prompt.PromptParser; import java.util.List; -import java.util.regex.Pattern; public interface Prompt { @@ -70,35 +69,20 @@ public interface Prompt { PromptManager getPromptManager(); List getArgs(); - - /** - * set the regex check - * - * @param regexCheck regular expression to check prompt input - */ - void setRegexCheck(String regexCheck); - - /** - * set the regex check using {@link Pattern} - * - * @param regexPattern regular expression to check prompt input - */ - void setRegexCheck(Pattern regexPattern); - + /** - * Get the regex check + * Set the input validator * - * @return regular expression to check prompt input + * @param inputValidator input validator to check prompt input */ - Pattern getRegexCheck(); + void setInputValidator(InputValidator inputValidator); /** - * Check if the input is valid using {@link Prompt#getRegexCheck()} + * Get the input validator * - * @param input input to check - * @return true if input is valid + * @return input validator to check prompt input */ - boolean isValidInput(String input); + InputValidator getInputValidator(); /** * Returns a boolean value if inputs should be sanitized. diff --git a/src/main/java/com/cyr1en/commandprompter/config/PromptConfig.java b/src/main/java/com/cyr1en/commandprompter/config/PromptConfig.java index 597789e..16d1eb3 100644 --- a/src/main/java/com/cyr1en/commandprompter/config/PromptConfig.java +++ b/src/main/java/com/cyr1en/commandprompter/config/PromptConfig.java @@ -1,12 +1,18 @@ package com.cyr1en.commandprompter.config; +import com.cyr1en.commandprompter.api.prompt.InputValidator; import com.cyr1en.commandprompter.config.annotations.field.*; import com.cyr1en.commandprompter.config.annotations.type.ConfigHeader; import com.cyr1en.commandprompter.config.annotations.type.ConfigPath; import com.cyr1en.commandprompter.config.annotations.type.Configuration; import com.cyr1en.commandprompter.prompt.ui.CacheFilter; +import com.cyr1en.commandprompter.prompt.validators.NoopValidator; +import com.cyr1en.commandprompter.prompt.validators.OnlinePlayerValidator; +import com.cyr1en.commandprompter.prompt.validators.RegexValidator; import com.cyr1en.kiso.mc.configuration.base.Config; +import java.util.regex.Pattern; + @Configuration @ConfigPath("prompt-config.yml") @ConfigHeader({"Prompts", "Configuration"}) @@ -287,20 +293,28 @@ public record PromptConfig( String strSampleErrMessage ) implements AliasedSection { - public String findIVRegexCheckInConfig(String alias) { + private String findIVRegexCheckInConfig(String alias) { return getIVValue("Alias", alias, "Regex"); } - public String getIVErrMessage(String alias) { + private String getIVErrMessage(String alias) { return getIVValue("Alias", alias, "Err-Message"); } - public String getIVErrMessageWithRegex(String regex) { - return getIVValue("Regex", regex, "Err-Message"); + private String getIVValue(String key, String keyVal, String query) { + return getInputValidationValue("Input-Validation", key, keyVal, query); } - public String getIVValue(String key, String keyVal, String query) { - return getInputValidationValue("Input-Validation", key, keyVal, query); + public InputValidator getInputValidator(String alias) { + if (alias == null || alias.isBlank()) + return new NoopValidator(); + var isPlayer = Boolean.parseBoolean(getIVValue("Alias", alias, "Online-Player")); + if (isPlayer) + return new OnlinePlayerValidator(alias, getIVErrMessage(alias)); + var regex = findIVRegexCheckInConfig(alias); + if (regex != null && !regex.isBlank()) + return new RegexValidator(alias, Pattern.compile(regex), getIVErrMessage(alias)); + return new NoopValidator(); } /** diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java b/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java index 4bfa53a..f426fd5 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java @@ -189,11 +189,11 @@ private boolean checkInput(PromptQueue promptQueue, PromptContext context) { return true; var prompt = promptQueue.peek(); - if (prompt.isValidInput(context.getContent())) + var validator = prompt.getInputValidator(); + if (validator.validate(context.getContent())) return true; - var errMsg = plugin.getPromptConfig().getIVErrMessageWithRegex(prompt.getRegexCheck().pattern()); - plugin.getMessenger().sendMessage(context.getSender(), errMsg); + plugin.getMessenger().sendMessage(context.getSender(), validator.messageOnFail()); sendPrompt(context.getSender()); return false; } diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java b/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java index 2e902c2..8845467 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java @@ -25,8 +25,10 @@ package com.cyr1en.commandprompter.prompt; import com.cyr1en.commandprompter.CommandPrompter; +import com.cyr1en.commandprompter.api.prompt.InputValidator; import com.cyr1en.commandprompter.api.prompt.Prompt; import com.cyr1en.commandprompter.hook.hooks.PapiHook; +import com.cyr1en.commandprompter.prompt.validators.NoopValidator; import com.cyr1en.kiso.utils.SRegex; import org.bukkit.entity.Player; @@ -120,7 +122,7 @@ public int parsePrompts(PromptContext promptContext) { var sender = promptContext.getSender(); var promptArgs = ArgumentUtil.findPattern(PromptArgument.class, cleanPrompt); plugin.getPluginLogger().debug("Prompt args: " + promptArgs); - var inputValidation = extractInputValidation(cleanPrompt); + var inputValidator = extractInputValidation(cleanPrompt); // Set papi placeholders if exists var promptTxt = ArgumentUtil.stripArgs(cleanPrompt); @@ -131,7 +133,7 @@ public int parsePrompts(PromptContext promptContext) { String.class, List.class) .newInstance(plugin, promptContext, promptTxt, promptArgs); - p.setRegexCheck(plugin.getPromptConfig().findIVRegexCheckInConfig(inputValidation)); + p.setInputValidator(inputValidator); if (promptArgs.contains(PromptArgument.DISABLE_SANITATION)) p.setInputSanitization(false); @@ -146,13 +148,14 @@ public int parsePrompts(PromptContext promptContext) { } - private String extractInputValidation(String prompt) { + private InputValidator extractInputValidation(String prompt) { // iv is with pattern -iv: var pattern = Pattern.compile(PromptArgument.INPUT_VALIDATION.getKey()); var matcher = pattern.matcher(prompt); - if (!matcher.find()) return ""; + if (!matcher.find()) return new NoopValidator(); var found = matcher.group(); - return found.split(":")[1]; + var alias = found.split(":")[1]; + return plugin.getPromptConfig().getInputValidator(alias); } private String resolvePapiPlaceholders(Player sender, String prompt) { diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/prompts/AbstractPrompt.java b/src/main/java/com/cyr1en/commandprompter/prompt/prompts/AbstractPrompt.java index c3108c9..6423536 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/prompts/AbstractPrompt.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/prompts/AbstractPrompt.java @@ -25,14 +25,15 @@ package com.cyr1en.commandprompter.prompt.prompts; import com.cyr1en.commandprompter.CommandPrompter; +import com.cyr1en.commandprompter.api.prompt.InputValidator; import com.cyr1en.commandprompter.api.prompt.Prompt; import com.cyr1en.commandprompter.prompt.PromptContext; import com.cyr1en.commandprompter.prompt.PromptManager; import com.cyr1en.commandprompter.prompt.PromptParser; +import com.cyr1en.commandprompter.prompt.validators.NoopValidator; import com.cyr1en.commandprompter.util.Util; import java.util.List; -import java.util.regex.Pattern; public abstract class AbstractPrompt implements Prompt { @@ -43,7 +44,7 @@ public abstract class AbstractPrompt implements Prompt { private final List args; - private Pattern regexCheck; + private InputValidator validator; private boolean inputSanitation; @@ -55,7 +56,7 @@ public AbstractPrompt(CommandPrompter plugin, PromptContext context, this.promptManager = plugin.getPromptManager(); this.args = args; this.inputSanitation = true; - this.regexCheck = Pattern.compile(""); + this.validator = new NoopValidator(); } protected String stripColor(String msg) { @@ -95,18 +96,13 @@ public List getArgs() { } @Override - public void setRegexCheck(String regexCheck) { - this.regexCheck = Pattern.compile(regexCheck); + public void setInputValidator(InputValidator inputValidator) { + this.validator = inputValidator; } @Override - public void setRegexCheck(Pattern regexCheck) { - this.regexCheck = regexCheck; - } - - @Override - public Pattern getRegexCheck() { - return regexCheck; + public InputValidator getInputValidator() { + return this.validator; } @Override @@ -119,10 +115,4 @@ public boolean sanitizeInput() { return this.inputSanitation; } - @Override - public boolean isValidInput(String input) { - plugin.getPluginLogger().debug("Checking input with regex: " + regexCheck); - if (regexCheck.pattern().isBlank() || regexCheck.pattern().isEmpty()) return true; - return regexCheck.matcher(input).matches(); - } } diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/validators/NoopValidator.java b/src/main/java/com/cyr1en/commandprompter/prompt/validators/NoopValidator.java new file mode 100644 index 0000000..38d02c8 --- /dev/null +++ b/src/main/java/com/cyr1en/commandprompter/prompt/validators/NoopValidator.java @@ -0,0 +1,25 @@ +package com.cyr1en.commandprompter.prompt.validators; + +import com.cyr1en.commandprompter.api.prompt.InputValidator; + +/** + * A validator that does nothing. + *

+ * This will always return true. + */ +public class NoopValidator implements InputValidator { + @Override + public boolean validate(String input) { + return true; + } + + @Override + public String alias() { + return "noop"; + } + + @Override + public String messageOnFail() { + return ""; + } +} diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/validators/OnlinePlayerValidator.java b/src/main/java/com/cyr1en/commandprompter/prompt/validators/OnlinePlayerValidator.java new file mode 100644 index 0000000..360cf8f --- /dev/null +++ b/src/main/java/com/cyr1en/commandprompter/prompt/validators/OnlinePlayerValidator.java @@ -0,0 +1,22 @@ +package com.cyr1en.commandprompter.prompt.validators; + +import com.cyr1en.commandprompter.api.prompt.InputValidator; +import org.bukkit.Bukkit; + +/** + * A validator that checks if the input is an online player. + * + * @param alias The alias for this validator. + * @param messageOnFail The message to send when validation fails. + */ +public record OnlinePlayerValidator(String alias, String messageOnFail) implements InputValidator { + + @Override + public boolean validate(String input) { + if (input == null || input.isBlank()) + return false; + var player = Bukkit.getPlayer(input); + return player != null && player.isOnline(); + } + +} diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/validators/RegexValidator.java b/src/main/java/com/cyr1en/commandprompter/prompt/validators/RegexValidator.java new file mode 100644 index 0000000..f17179b --- /dev/null +++ b/src/main/java/com/cyr1en/commandprompter/prompt/validators/RegexValidator.java @@ -0,0 +1,34 @@ +package com.cyr1en.commandprompter.prompt.validators; + +import com.cyr1en.commandprompter.api.prompt.InputValidator; + +import java.util.regex.Pattern; + +/** + * A validator that uses regex to validate input. + *

+ * This validator is used to validate input based on a regex pattern. + * + * @param alias The alias for this validator. + * @param regex The regex pattern to use for validation. + * @param messageOnFail The message to send when validation fails. + */ +public record RegexValidator(String alias, Pattern regex, String messageOnFail) implements InputValidator { + + @Override + public boolean validate(String input) { + if (input == null || regex == null) + return false; + return regex.matcher(input).matches(); + } + + /** + * Get the regex pattern. + * + * @return the regex pattern. + */ + @Override + public Pattern regex() { + return regex; + } +} From a0ab00662d9a311bd1ea2cedc1ae79bf7e28a173 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 30 Dec 2023 00:00:51 -0700 Subject: [PATCH 05/21] allow to set custom model data for heads --- .../com/cyr1en/commandprompter/config/PromptConfig.java | 7 +++++++ .../com/cyr1en/commandprompter/prompt/ui/HeadCache.java | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/main/java/com/cyr1en/commandprompter/config/PromptConfig.java b/src/main/java/com/cyr1en/commandprompter/config/PromptConfig.java index 16d1eb3..9fc66ac 100644 --- a/src/main/java/com/cyr1en/commandprompter/config/PromptConfig.java +++ b/src/main/java/com/cyr1en/commandprompter/config/PromptConfig.java @@ -27,6 +27,8 @@ public record PromptConfig( "PlayerUI formatting", "", "Skull-Name-Format - The display name format", " for the player heads", "", + "Skull-Custom-Model-Data - The custom model data for the", + " player heads", "", "Size - the size of the UI (multiple of 9, between 18-54)", "", "Cache-Size - Size for the head cache", "", "Cache-Delay - Delay in ticks after the player", "", @@ -39,6 +41,11 @@ public record PromptConfig( }) String skullNameFormat, + @ConfigNode + @NodeName("PlayerUI.Skull-Custom-Model-Data") + @NodeDefault("0") + int skullCustomModelData, + @ConfigNode @NodeName("PlayerUI.Size") @NodeDefault("54") diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java index c5ab3c6..4fb8744 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java @@ -151,6 +151,11 @@ private SkullMeta makeSkullMeta(Player owningPlayer, PluginLogger logger) { Objects.requireNonNull(skullMeta).setOwningPlayer(owningPlayer); var skullFormat = plugin.getPromptConfig().skullNameFormat(); + var customModelData = plugin.getPromptConfig().skullCustomModelData(); + if (customModelData != 0) { + logger.debug("Setting custom model data: %s", customModelData); + skullMeta.setCustomModelData(customModelData); + } var skullName = skullFormat.replaceAll("%s", owningPlayer.getName()); setDisplayName(skullMeta, skullName); logger.debug("Skull Meta: {%s. %s}", skullMeta.getDisplayName(), skullMeta.getOwningPlayer()); From 64700b9c90d98c1e4031b9dde594511f50f0c04e Mon Sep 17 00:00:00 2001 From: CyR1en Date: Thu, 4 Jan 2024 00:55:51 -0700 Subject: [PATCH 06/21] fix null pointer on filter registration --- src/main/java/com/cyr1en/commandprompter/CommandPrompter.java | 1 + .../java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cyr1en/commandprompter/CommandPrompter.java b/src/main/java/com/cyr1en/commandprompter/CommandPrompter.java index 72c4719..ca809a7 100644 --- a/src/main/java/com/cyr1en/commandprompter/CommandPrompter.java +++ b/src/main/java/com/cyr1en/commandprompter/CommandPrompter.java @@ -102,6 +102,7 @@ public void onEnable() { Bukkit.getScheduler().runTaskLater(this, () -> { hookContainer = new HookContainer(this); hookContainer.initHooks(); + headCache.registerFilters(); ChatPrompt.resolveListener(this); }, 1L); } diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java index 4fb8744..312c380 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java @@ -37,7 +37,6 @@ public HeadCache(CommandPrompter plugin) { this.plugin = plugin; this.logger = plugin.getPluginLogger(); this.filters = new ArrayList<>(); - registerFilters(); HEAD_CACHE = CacheBuilder.newBuilder().maximumSize(plugin.getPromptConfig().cacheSize()) .build(new CacheLoader<>() { @Override @@ -56,7 +55,7 @@ public HeadCache(CommandPrompter plugin) { }); } - private void registerFilters() { + public void registerFilters() { registerFilter(new CacheFilter.WorldFilter()); registerFilter(new CacheFilter.RadialFilter()); plugin.getHookContainer() From 66d89d6668e37d1d2540d8ed156e45290ab060a7 Mon Sep 17 00:00:00 2001 From: cyr1en Date: Fri, 5 Jan 2024 15:18:42 -0700 Subject: [PATCH 07/21] add debug information --- .../java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java index 312c380..011996f 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java @@ -66,8 +66,10 @@ public void registerFilters() { public void registerFilter(CacheFilter filter) { if (Objects.isNull(filter)) return; - if (!filters.contains(filter)) + if (!filters.contains(filter)) { filters.add(filter); + logger.debug("Registered filter: " + filter.getClass().getSimpleName()); + } } public List getFilters() { From 070049654c663e2d546ef537106920bd5c77b252 Mon Sep 17 00:00:00 2001 From: cyr1en Date: Fri, 5 Jan 2024 15:19:17 -0700 Subject: [PATCH 08/21] fix prompt registration order --- src/main/java/com/cyr1en/commandprompter/CommandPrompter.java | 1 + .../java/com/cyr1en/commandprompter/prompt/PromptManager.java | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cyr1en/commandprompter/CommandPrompter.java b/src/main/java/com/cyr1en/commandprompter/CommandPrompter.java index ca809a7..547969a 100644 --- a/src/main/java/com/cyr1en/commandprompter/CommandPrompter.java +++ b/src/main/java/com/cyr1en/commandprompter/CommandPrompter.java @@ -103,6 +103,7 @@ public void onEnable() { hookContainer = new HookContainer(this); hookContainer.initHooks(); headCache.registerFilters(); + promptManager.registerPrompts(); ChatPrompt.resolveListener(this); }, 1L); } diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java b/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java index f426fd5..751c994 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java @@ -68,10 +68,9 @@ public PromptManager(CommandPrompter commandPrompter) { this.promptRegistry = new PromptRegistry(plugin); this.promptParser = new PromptParser(this); this.scheduler = Bukkit.getScheduler(); - registerPrompts(); } - private void registerPrompts() { + public void registerPrompts() { this.put("", ChatPrompt.class); this.put("a", AnvilPrompt.class); this.put(plugin.getHeadCache().makeFilteredPattern(), PlayerUIPrompt.class); From ddcfa69bf2d2396d7fa3b39c0e0e2b2f52e14780 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Fri, 16 Feb 2024 01:23:37 -0700 Subject: [PATCH 09/21] add LuckPerms cache filters --- build.gradle | 1 + .../hook/hooks/LuckPermsHook.java | 175 ++++++++++++++++++ .../commandprompter/prompt/ui/HeadCache.java | 5 + 3 files changed, 181 insertions(+) create mode 100644 src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java diff --git a/build.gradle b/build.gradle index 39ddd57..401f294 100644 --- a/build.gradle +++ b/build.gradle @@ -82,6 +82,7 @@ dependencies { compileOnly 'com.github.LeonMangler:SuperVanish:6.2.17' compileOnly 'com.github.mbax:VanishNoPacket:3.22' compileOnly 'org.jetbrains:annotations:23.0.0' + compileOnly 'net.luckperms:api:5.4' // Local implementation fileTree(dir: 'libs', include: '*.jar') diff --git a/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java b/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java new file mode 100644 index 0000000..e75ef1f --- /dev/null +++ b/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java @@ -0,0 +1,175 @@ +package com.cyr1en.commandprompter.hook.hooks; + +import com.cyr1en.commandprompter.CommandPrompter; +import com.cyr1en.commandprompter.hook.Hook; +import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; +import com.cyr1en.commandprompter.prompt.ui.CacheFilter; +import com.cyr1en.commandprompter.prompt.ui.HeadCache; +import net.luckperms.api.LuckPerms; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; + +import java.util.List; +import java.util.regex.Pattern; + +/** + * Hook for LuckPerms plugin. + * + *

+ * Main component of this hook are the cache filters for LuckPerms groups. + */ +@TargetPlugin(pluginName = "LuckPerms") +public class LuckPermsHook extends BaseHook { + + private LuckPerms api; + + /** + * Construct a new LuckPermsHook. + * + * @param plugin the plugin + */ + public LuckPermsHook(CommandPrompter plugin) { + super(plugin); + RegisteredServiceProvider provider = plugin.getServer().getServicesManager().getRegistration(LuckPerms.class); + if (provider != null) { + this.api = provider.getProvider(); + } else { + // If LuckPerms was loaded but the service provider was not found then we make sure that the container + // is empty. + plugin.getHookContainer().replace(LuckPermsHook.class, Hook.empty()); + } + } + + /** + * Register the cache filters for LuckPerms groups. + * + * @param cache the head cache. + */ + public void registerFilters(HeadCache cache) { + cache.registerFilter(new OwnGroupFilter(this)); + cache.registerFilter(new GroupFilter(this)); + } + + /** + * Get all players that are in the same group as the relative player. + * + * @param groupName the group name + * @return a list of players with the same group + */ + private List getPlayersWithGroup(String groupName) { + if (api == null || groupName.isBlank()) return List.of(); + return (List) Bukkit.getOnlinePlayers().stream() + .filter(p -> { + var user = api.getUserManager().getUser(p.getUniqueId()); + if (user == null) return false; + var group = user.getPrimaryGroup(); + return group.equals(groupName); + }).toList(); + } + + /** + * Get the LuckPerms API. + * + * @return the LuckPerms API + */ + private LuckPerms getApi() { + return api; + } + + + /** + * A PlayerUI filter that would filter all players that are in the same group as the relative player. + * + *

+ * This is a special case of {@link CacheFilter} where the regex key is 'og'. + * This would filter all players that are in the same group as the relative player. + */ + private static class OwnGroupFilter extends CacheFilter { + + private final LuckPermsHook hook; + + /** + * Construct a new own group filter. + * + * @param hook the LuckPerms hook + */ + public OwnGroupFilter(LuckPermsHook hook) { + super(Pattern.compile("og"), "PlayerUI.Filter-Format.LuckPermsOwnGroup"); + this.hook = hook; + } + + @Override + public CacheFilter reConstruct(String promptKey) { + // No need to re-construct + return this; + } + + /** + * Filter all players that are in the same group as the relative player. + * + * @param relativePlayer the players to filter + * @return a list of players with the same group + */ + @Override + public List filter(Player relativePlayer) { + var user = hook.getApi().getUserManager().getUser(relativePlayer.getUniqueId()); + if (user == null) return List.of(); + var group = user.getPrimaryGroup(); + return hook.getPlayersWithGroup(group); + } + } + + /** + * A PlayerUI filter that would filter all players based on the group name. + * + *

+ * The regex for this filter is 'g(\S+)'. In regex capturing group 1 is the group + * name that would be used to filter. + */ + private static class GroupFilter extends CacheFilter { + + private final String groupName; + private final LuckPermsHook hook; + + /** + * Default construct a new group filter. + * + * @param hook the LuckPerms hook + */ + public GroupFilter(LuckPermsHook hook) { + super(Pattern.compile("g(\\S+)"), "PlayerUI.Filter-Format.LuckPermsGroup"); + this.groupName = ""; + this.hook = hook; + } + + /** + * Constructor to construct a new group filter with a group name. + * + *

+ * This constructor will be used by the {@link #reConstruct(String)} function. + * + * @param groupName the group name that would be used to filter + * @param hook the LuckPerms hook + */ + public GroupFilter(String groupName, LuckPermsHook hook) { + super(Pattern.compile("g(\\S+)"), "PlayerUI.Filter-Format.LuckPermsGroup"); + this.groupName = groupName; + this.hook = hook; + } + + @Override + public CacheFilter reConstruct(String promptKey) { + var matcher = this.getRegexKey().matcher(promptKey); + var found = matcher.find(); + var groupName = found ? matcher.group(1) : ""; + return new GroupFilter(groupName, hook); + } + + @Override + public List filter(Player relativePlayer) { + return hook.getPlayersWithGroup(groupName); + } + } + +} diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java index 011996f..94feb51 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java @@ -2,6 +2,7 @@ import com.cyr1en.commandprompter.CommandPrompter; import com.cyr1en.commandprompter.PluginLogger; +import com.cyr1en.commandprompter.hook.hooks.LuckPermsHook; import com.cyr1en.commandprompter.hook.hooks.PapiHook; import com.cyr1en.commandprompter.hook.hooks.TownyHook; import com.cyr1en.commandprompter.util.Util; @@ -62,6 +63,10 @@ public void registerFilters() { .getHook(TownyHook.class) .ifHooked(hook -> hook.registerFilters(this)) .complete(); + plugin.getHookContainer() + .getHook(LuckPermsHook.class) + .ifHooked(hook -> hook.registerFilters(this)) + .complete(); } public void registerFilter(CacheFilter filter) { From f7565550a5b07e31fb2f192d1eab52f66ae2bab6 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 00:27:14 -0700 Subject: [PATCH 10/21] bump version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 401f294..67ad9c4 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ java { PluginManifest pluginManifest = [ name : 'CommandPrompter', - version : new Version(major: 2, minor: 7, patch: 0, fix: 0, classifier: 'SNAPSHOT'), + version : new Version(major: 2, minor: 8, patch: 0, fix: 0, classifier: 'SNAPSHOT'), author : 'CyR1en', description: 'Perfect companion plugin for inventory UI menu.', entry : 'com.cyr1en.commandprompter.CommandPrompter' From c4abac6e5cce41454d86caa3c7f57cb8484d4a4a Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 00:27:42 -0700 Subject: [PATCH 11/21] update deps --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 67ad9c4..2cd5a1d 100644 --- a/build.gradle +++ b/build.gradle @@ -75,8 +75,8 @@ dependencies { implementation 'com.github.stefvanschie.inventoryframework:IF:0.10.11' compileOnly 'me.clip:placeholderapi:2.11.2' - compileOnly "net.kyori:adventure-text-serializer-legacy:4.13.1" - compileOnly "net.kyori:adventure-text-serializer-plain:4.13.1" + compileOnly "net.kyori:adventure-text-serializer-legacy:4.15.0" + compileOnly "net.kyori:adventure-text-serializer-plain:4.15.0" compileOnly 'com.palmergames.bukkit.towny:towny:0.100.0.0' compileOnly "org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT" compileOnly 'com.github.LeonMangler:SuperVanish:6.2.17' From 886d16ab529c0d7ffb86ed07186586a27b2e488d Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 00:29:40 -0700 Subject: [PATCH 12/21] allow to ignore MiniMessage syntax --- build.gradle | 1 + .../config/CommandPrompterConfig.java | 12 ++++++++++- .../commandprompter/prompt/PromptParser.java | 21 +++++++++++++++++++ src/main/resources/runtime-deps.json | 9 ++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2cd5a1d..ed6e6d6 100644 --- a/build.gradle +++ b/build.gradle @@ -77,6 +77,7 @@ dependencies { compileOnly 'me.clip:placeholderapi:2.11.2' compileOnly "net.kyori:adventure-text-serializer-legacy:4.15.0" compileOnly "net.kyori:adventure-text-serializer-plain:4.15.0" + compileOnly "net.kyori:adventure-text-minimessage:4.15.0" compileOnly 'com.palmergames.bukkit.towny:towny:0.100.0.0' compileOnly "org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT" compileOnly 'com.github.LeonMangler:SuperVanish:6.2.17' diff --git a/src/main/java/com/cyr1en/commandprompter/config/CommandPrompterConfig.java b/src/main/java/com/cyr1en/commandprompter/config/CommandPrompterConfig.java index 7b24c46..05e3077 100644 --- a/src/main/java/com/cyr1en/commandprompter/config/CommandPrompterConfig.java +++ b/src/main/java/com/cyr1en/commandprompter/config/CommandPrompterConfig.java @@ -187,7 +187,17 @@ public record CommandPrompterConfig( "for CommandPrompter" }) @NodeDefault("true") - boolean commandTabComplete + boolean commandTabComplete, + + @ConfigNode + @NodeName("Ignore-MiniMessage") + @NodeComment({ + "If Prompt-Regex is left to default", + "this will ignore MiniMessage syntax." + }) + boolean ignoreMiniMessage + + ) implements AliasedSection { public String[] getPermissionAttachment(String key) { diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java b/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java index 8845467..e0b740b 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java @@ -30,6 +30,9 @@ import com.cyr1en.commandprompter.hook.hooks.PapiHook; import com.cyr1en.commandprompter.prompt.validators.NoopValidator; import com.cyr1en.kiso.utils.SRegex; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; import org.bukkit.entity.Player; import java.lang.reflect.InvocationTargetException; @@ -75,6 +78,8 @@ public String getEscapedRegex() { public boolean isParsable(PromptContext promptContext) { var prompts = getPrompts(promptContext); + if (plugin.getConfiguration().ignoreMiniMessage()) + prompts = filterOutMiniMessageTags(prompts); return !prompts.isEmpty(); } @@ -145,7 +150,23 @@ public int parsePrompts(PromptContext promptContext) { } } return manager.getPromptRegistry().get(promptContext.getSender()).hashCode(); + } + + private List filterOutMiniMessageTags(List prompts) { + return prompts.stream().filter(prompt -> !isMiniMessageTag(prompt)).toList(); + } + /** + * Function that checks if the prompt is a mini message tag. + * + * @param prompt Prompt to check + * @return true if the prompt is a mini message tag, false otherwise. + */ + private boolean isMiniMessageTag(String prompt) { + var serializer = MiniMessage.builder() + .tags(StandardTags.defaults()).build(); + var parsed = serializer.deserialize(prompt); + return !Component.text(prompt).equals(parsed); } private InputValidator extractInputValidation(String prompt) { diff --git a/src/main/resources/runtime-deps.json b/src/main/resources/runtime-deps.json index b6437f9..99fa9d1 100644 --- a/src/main/resources/runtime-deps.json +++ b/src/main/resources/runtime-deps.json @@ -25,5 +25,14 @@ "com.cyr1en.commandapi" ], "sha1": "145ee0e27a97733cc4cc40641910aabfb70d18c2" + }, + "MiniMessage": { + "filename": "adventure-text-minimessage-4.15.0.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/adventure-text-minimessage/4.15.0/adventure-text-minimessage-4.15.0.jar", + "relocation": [ + "net.kyori.adventure.text.minimessage", + "com.cyr1en.minimessage" + ], + "sha1": "16299c7bf78b2cd51d893046cfb69357355b276b" } } \ No newline at end of file From 3a864156d538dcf951a5bad7c1bf666f09869897 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 00:30:08 -0700 Subject: [PATCH 13/21] dispatch queue if empty --- .../java/com/cyr1en/commandprompter/prompt/PromptManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java b/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java index 751c994..6b883b8 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/PromptManager.java @@ -118,8 +118,10 @@ public void processPrompt(PromptContext context) { return; var queue = promptRegistry.get(sender); - if (queue.isEmpty() && !queue.containsPCM()) + if (queue.isEmpty() && !queue.containsPCM()) { + dispatchQueue(sender, queue); return; + } if (!checkInput(queue, context)) return; From 8271f6456694da5ef1917d52cfa542a7a41b012c Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 00:30:17 -0700 Subject: [PATCH 14/21] better docs --- .../com/cyr1en/commandprompter/prompt/PromptParser.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java b/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java index e0b740b..3bf2f1c 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java @@ -86,6 +86,15 @@ public boolean isParsable(PromptContext promptContext) { /** * Parses a contents of {@link PromptContext} * + *

+ * This method will parse the contents of the {@link PromptContext} and + * create a {@link PromptQueue} for the sender. It will also parse the + * {@link Prompt} and add it to the {@link PromptQueue}. + * + *

+ * This function returns the hashCode of the {@link PromptQueue} that was + * created. This is used to retroactively cancel the prompt within a certain time. + * * @param promptContext Context to parse * @return hashCode of the {@link PromptQueue} that was created. */ From 794b44e1bf300147044153a49fd4ec8d4c04a02e Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 00:31:02 -0700 Subject: [PATCH 15/21] improved structure for hooked filters --- .../hook/hooks/FilterHook.java | 16 +++++++++++++++ .../hook/hooks/LuckPermsHook.java | 2 +- .../commandprompter/hook/hooks/TownyHook.java | 2 +- .../commandprompter/prompt/ui/HeadCache.java | 20 +++++++++++-------- 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/cyr1en/commandprompter/hook/hooks/FilterHook.java diff --git a/src/main/java/com/cyr1en/commandprompter/hook/hooks/FilterHook.java b/src/main/java/com/cyr1en/commandprompter/hook/hooks/FilterHook.java new file mode 100644 index 0000000..ec60c4d --- /dev/null +++ b/src/main/java/com/cyr1en/commandprompter/hook/hooks/FilterHook.java @@ -0,0 +1,16 @@ +package com.cyr1en.commandprompter.hook.hooks; + +import com.cyr1en.commandprompter.prompt.ui.HeadCache; + +/** + * A hook that registers filters to the head cache. + */ +public interface FilterHook { + + /** + * Register filters to the head cache. + * + * @param headCache the head cache + */ + void registerFilters(HeadCache headCache); +} diff --git a/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java b/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java index e75ef1f..4f94bcf 100644 --- a/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java +++ b/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java @@ -20,7 +20,7 @@ * Main component of this hook are the cache filters for LuckPerms groups. */ @TargetPlugin(pluginName = "LuckPerms") -public class LuckPermsHook extends BaseHook { +public class LuckPermsHook extends BaseHook implements FilterHook{ private LuckPerms api; diff --git a/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java b/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java index 676d886..0b5df73 100644 --- a/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java +++ b/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java @@ -13,7 +13,7 @@ import java.util.regex.Pattern; @TargetPlugin(pluginName = "Towny") -public class TownyHook extends BaseHook { +public class TownyHook extends BaseHook implements FilterHook { public TownyHook(CommandPrompter plugin) { super(plugin); } diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java index 94feb51..204ac9d 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/ui/HeadCache.java @@ -2,6 +2,7 @@ import com.cyr1en.commandprompter.CommandPrompter; import com.cyr1en.commandprompter.PluginLogger; +import com.cyr1en.commandprompter.hook.hooks.FilterHook; import com.cyr1en.commandprompter.hook.hooks.LuckPermsHook; import com.cyr1en.commandprompter.hook.hooks.PapiHook; import com.cyr1en.commandprompter.hook.hooks.TownyHook; @@ -56,17 +57,20 @@ public HeadCache(CommandPrompter plugin) { }); } + private static final Class[] fHooks = new Class[]{ + TownyHook.class, + LuckPermsHook.class + }; + public void registerFilters() { registerFilter(new CacheFilter.WorldFilter()); registerFilter(new CacheFilter.RadialFilter()); - plugin.getHookContainer() - .getHook(TownyHook.class) - .ifHooked(hook -> hook.registerFilters(this)) - .complete(); - plugin.getHookContainer() - .getHook(LuckPermsHook.class) - .ifHooked(hook -> hook.registerFilters(this)) - .complete(); + for (var fHook : fHooks) { + plugin.getHookContainer() + .getHook(fHook) + .ifHooked(hook -> hook.registerFilters(this)) + .complete(); + } } public void registerFilter(CacheFilter filter) { From eaf8360ecc574adbd9bf60c52ab14fd53476be49 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 00:56:20 -0700 Subject: [PATCH 16/21] refactor to allow general usage for MiniMessage related functions --- .../commandprompter/prompt/PromptParser.java | 25 +++-------------- .../cyr1en/commandprompter/util/MMUtil.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/cyr1en/commandprompter/util/MMUtil.java diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java b/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java index 3bf2f1c..605fd9a 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/PromptParser.java @@ -29,10 +29,8 @@ import com.cyr1en.commandprompter.api.prompt.Prompt; import com.cyr1en.commandprompter.hook.hooks.PapiHook; import com.cyr1en.commandprompter.prompt.validators.NoopValidator; +import com.cyr1en.commandprompter.util.MMUtil; import com.cyr1en.kiso.utils.SRegex; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; import org.bukkit.entity.Player; import java.lang.reflect.InvocationTargetException; @@ -79,7 +77,7 @@ public String getEscapedRegex() { public boolean isParsable(PromptContext promptContext) { var prompts = getPrompts(promptContext); if (plugin.getConfiguration().ignoreMiniMessage()) - prompts = filterOutMiniMessageTags(prompts); + prompts = MMUtil.filterOutMiniMessageTags(prompts); return !prompts.isEmpty(); } @@ -100,6 +98,8 @@ public boolean isParsable(PromptContext promptContext) { */ public int parsePrompts(PromptContext promptContext) { var prompts = getPrompts(promptContext); + prompts = MMUtil.filterOutMiniMessageTags(prompts); + plugin.getPluginLogger().debug("Prompts: " + prompts); var command = promptContext.getContent().trim(); @@ -161,23 +161,6 @@ public int parsePrompts(PromptContext promptContext) { return manager.getPromptRegistry().get(promptContext.getSender()).hashCode(); } - private List filterOutMiniMessageTags(List prompts) { - return prompts.stream().filter(prompt -> !isMiniMessageTag(prompt)).toList(); - } - - /** - * Function that checks if the prompt is a mini message tag. - * - * @param prompt Prompt to check - * @return true if the prompt is a mini message tag, false otherwise. - */ - private boolean isMiniMessageTag(String prompt) { - var serializer = MiniMessage.builder() - .tags(StandardTags.defaults()).build(); - var parsed = serializer.deserialize(prompt); - return !Component.text(prompt).equals(parsed); - } - private InputValidator extractInputValidation(String prompt) { // iv is with pattern -iv: var pattern = Pattern.compile(PromptArgument.INPUT_VALIDATION.getKey()); diff --git a/src/main/java/com/cyr1en/commandprompter/util/MMUtil.java b/src/main/java/com/cyr1en/commandprompter/util/MMUtil.java new file mode 100644 index 0000000..1174286 --- /dev/null +++ b/src/main/java/com/cyr1en/commandprompter/util/MMUtil.java @@ -0,0 +1,28 @@ +package com.cyr1en.commandprompter.util; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; + +import java.util.List; + +public class MMUtil { + + public static List filterOutMiniMessageTags(List strs) { + return strs.stream().filter(str -> !isMiniMessageTag(str)).toList(); + } + + /** + * Function that checks if the prompt is a mini message tag. + * + * @param str Prompt to check + * @return true if the prompt is a mini message tag, false otherwise. + */ + public static boolean isMiniMessageTag(String str) { + var serializer = MiniMessage.builder() + .tags(StandardTags.defaults()).build(); + var parsed = serializer.deserialize(str); + return !Component.text(str).equals(parsed); + } + +} From d8c96b7043b1e6567dd59eaac1edad390ba3ba47 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 00:56:51 -0700 Subject: [PATCH 17/21] improve prompt replacements --- .../commandprompter/prompt/PromptQueue.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/PromptQueue.java b/src/main/java/com/cyr1en/commandprompter/prompt/PromptQueue.java index 8fd6bfe..e889aee 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/PromptQueue.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/PromptQueue.java @@ -4,12 +4,15 @@ import com.cyr1en.commandprompter.PluginLogger; import com.cyr1en.commandprompter.api.Dispatcher; import com.cyr1en.commandprompter.api.prompt.Prompt; +import com.cyr1en.commandprompter.util.MMUtil; +import com.cyr1en.kiso.utils.SRegex; import org.bukkit.entity.Player; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.function.Consumer; +import java.util.regex.Pattern; public class PromptQueue extends LinkedList { @@ -61,8 +64,17 @@ public boolean isConsoleDelegate() { public String getCompleteCommand() { command = command.formatted(completed); LinkedList completedClone = new LinkedList<>(this.completed); - while (!completedClone.isEmpty()) - command = command.replaceFirst(escapedRegex, completedClone.pollFirst()); + + // get all prompts that we have to replace in the command + var sRegex = new SRegex(); + var prompts = sRegex.find(Pattern.compile(escapedRegex), command).getResultsList(); + prompts = MMUtil.filterOutMiniMessageTags(prompts); + + for (String prompt : prompts) { + if (completedClone.isEmpty()) + break; + command = command.replaceFirst(prompt, completedClone.pollFirst()); + } return "/" + command; } From 9206b3d003e9522934f84645a0ff6a3e0a782e94 Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 02:51:16 -0700 Subject: [PATCH 18/21] fix bug on getting capturing group of a filter --- .../prompt/prompts/PlayerUIPrompt.java | 10 +++++++-- .../prompt/ui/CacheFilter.java | 22 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/prompts/PlayerUIPrompt.java b/src/main/java/com/cyr1en/commandprompter/prompt/prompts/PlayerUIPrompt.java index b7fba87..91ee412 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/prompts/PlayerUIPrompt.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/prompts/PlayerUIPrompt.java @@ -108,6 +108,7 @@ private List extractFilters() { var extractedFilters = new ArrayList(); for (var filter : headCache.getFilters()) { var capGroup = getCapturingGroup(filter); + getPlugin().getPluginLogger().debug("Capturing group: " + capGroup); var filterKey = matcher.group(capGroup); if (Objects.isNull(filterKey)) continue; extractedFilters.add(filter.reConstruct(promptKey)); @@ -141,8 +142,13 @@ private CacheFilter getFirstFilter(List filters) { } private int getCapturingGroup(CacheFilter cacheFilter) { - var index = headCache.getFilters().indexOf(cacheFilter); - return index + 2; + getPlugin().getPluginLogger().debug("Getting capturing group for filter: " + cacheFilter.getRegexKey()); + var idx = 2; // index starts at 2 because 0 is the whole match and 1 is just a blank. + for (var filter : headCache.getFilters()) { + if (filter.equals(cacheFilter)) return idx; + idx = idx + filter.getCapGroupOffset() + 1; + } + return -1; } private void send(Player p) { diff --git a/src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java b/src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java index 7aec8af..a320276 100644 --- a/src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java +++ b/src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java @@ -2,6 +2,7 @@ import com.cyr1en.commandprompter.CommandPrompter; import com.cyr1en.commandprompter.config.PromptConfig; +import com.cyr1en.commandprompter.prompt.PromptParser; import org.bukkit.entity.Player; import java.util.List; @@ -26,14 +27,21 @@ public abstract class CacheFilter { */ private final String configKey; + private final int capGroupOffset; + /** * Construct a new cache filter. * * @param regexKey the regex key */ public CacheFilter(Pattern regexKey, String configKey) { + this(regexKey, configKey, 0); + } + + public CacheFilter(Pattern regexKey, String configKey, int capGroupOffset) { this.regexKey = regexKey; this.configKey = configKey; + this.capGroupOffset = capGroupOffset; } /** @@ -65,6 +73,16 @@ public String getFormat(PromptConfig config) { } + /** + * Get the capture group offset. + * + * @return the capture group offset + */ + public int getCapGroupOffset() { + return capGroupOffset; + } + + @Override public String toString() { return this.getClass().getSimpleName(); @@ -79,7 +97,7 @@ public String toString() { * could be used to reconstruct a certain subclass of {@link CacheFilter}. * *

- * Additional filter information can be parsed from the {@link com.cyr1en.commandprompter.prompt.PromptParser} + * Additional filter information can be parsed from the {@link PromptParser} * but for better readability, it is recommended to use this method instead. * * @param promptKey the prompt key @@ -144,7 +162,7 @@ public static class RadialFilter extends CacheFilter { * @param radius the radius */ public RadialFilter(int radius) { - super(Pattern.compile("r(\\d+)"), "PlayerUI.Filter-Format.Radial"); + super(Pattern.compile("r(\\d+)"), "PlayerUI.Filter-Format.Radial", 1); this.radius = radius; } From 4cef049a8eff84f0586869600f54c073de5a4b5b Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 02:51:42 -0700 Subject: [PATCH 19/21] finalize LuckPerms filters --- .../commandprompter/hook/HookContainer.java | 1 + .../hook/hooks/LuckPermsHook.java | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java b/src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java index a278d12..b68a3a6 100644 --- a/src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java +++ b/src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java @@ -26,6 +26,7 @@ public void initHooks() { hook(CarbonChatHook.class); hook(VanishNoPacketHook.class); hook(PapiHook.class); + hook(LuckPermsHook.class); } @Override diff --git a/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java b/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java index 4f94bcf..575165c 100644 --- a/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java +++ b/src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java @@ -20,7 +20,7 @@ * Main component of this hook are the cache filters for LuckPerms groups. */ @TargetPlugin(pluginName = "LuckPerms") -public class LuckPermsHook extends BaseHook implements FilterHook{ +public class LuckPermsHook extends BaseHook implements FilterHook { private LuckPerms api; @@ -61,7 +61,7 @@ private List getPlayersWithGroup(String groupName) { if (api == null || groupName.isBlank()) return List.of(); return (List) Bukkit.getOnlinePlayers().stream() .filter(p -> { - var user = api.getUserManager().getUser(p.getUniqueId()); + var user = api.getUserManager().getUser(p.getName()); if (user == null) return false; var group = user.getPrimaryGroup(); return group.equals(groupName); @@ -95,7 +95,7 @@ private static class OwnGroupFilter extends CacheFilter { * @param hook the LuckPerms hook */ public OwnGroupFilter(LuckPermsHook hook) { - super(Pattern.compile("og"), "PlayerUI.Filter-Format.LuckPermsOwnGroup"); + super(Pattern.compile("lpo"), "PlayerUI.Filter-Format.LuckPermsOwnGroup"); this.hook = hook; } @@ -138,9 +138,7 @@ private static class GroupFilter extends CacheFilter { * @param hook the LuckPerms hook */ public GroupFilter(LuckPermsHook hook) { - super(Pattern.compile("g(\\S+)"), "PlayerUI.Filter-Format.LuckPermsGroup"); - this.groupName = ""; - this.hook = hook; + this("", hook); } /** @@ -153,7 +151,7 @@ public GroupFilter(LuckPermsHook hook) { * @param hook the LuckPerms hook */ public GroupFilter(String groupName, LuckPermsHook hook) { - super(Pattern.compile("g(\\S+)"), "PlayerUI.Filter-Format.LuckPermsGroup"); + super(Pattern.compile("lpg(\\S+);"), "PlayerUI.Filter-Format.LuckPermsGroup", 1); this.groupName = groupName; this.hook = hook; } @@ -168,7 +166,9 @@ public CacheFilter reConstruct(String promptKey) { @Override public List filter(Player relativePlayer) { - return hook.getPlayersWithGroup(groupName); + var players = hook.getPlayersWithGroup(groupName); + hook.getPlugin().getPluginLogger().debug("Players: %s", players); + return players; } } From 009257e7d668cafb4ed2526fb8e397ac4e7d743e Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 02:53:48 -0700 Subject: [PATCH 20/21] change regex key for towny filters --- .../java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java b/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java index 0b5df73..3270669 100644 --- a/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java +++ b/src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java @@ -30,7 +30,7 @@ private static class TownFilter extends CacheFilter { public TownFilter() { - super(Pattern.compile("t"), "PlayerUI.Filter-Format.TownyTown"); + super(Pattern.compile("tt"), "PlayerUI.Filter-Format.TownyTown"); } @Override @@ -55,7 +55,7 @@ public List filter(Player relativePlayer) { private static class NationFilter extends CacheFilter { public NationFilter() { - super(Pattern.compile("n"), "PlayerUI.Filter-Format.TownyNation"); + super(Pattern.compile("tn"), "PlayerUI.Filter-Format.TownyNation"); } @Override From 2dcb29c8fdb3d97050dc574700afaa549a22c71e Mon Sep 17 00:00:00 2001 From: CyR1en Date: Sat, 17 Feb 2024 02:54:22 -0700 Subject: [PATCH 21/21] add Towny hook to hook container --- src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java b/src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java index b68a3a6..c359969 100644 --- a/src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java +++ b/src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java @@ -26,6 +26,7 @@ public void initHooks() { hook(CarbonChatHook.class); hook(VanishNoPacketHook.class); hook(PapiHook.class); + hook(TownyHook.class); hook(LuckPermsHook.class); }