diff --git a/.gitignore b/.gitignore index 4788b4b..dd0ea15 100644 --- a/.gitignore +++ b/.gitignore @@ -95,19 +95,13 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk +# Maven target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next - -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -.mvn/wrapper/maven-wrapper.jar -.flattened-pom.xml +# Gradle +.gradle/ +**/build/ +**/.gradle/ # Common working directory run/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6d0aa63 --- /dev/null +++ b/build.gradle @@ -0,0 +1,23 @@ +allprojects { + group = 'simplexity' + version = '3.2.2' +} + +subprojects { + apply plugin: 'java' + + java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } + } + + repositories { + mavenCentral() + maven { url 'https://repo.papermc.io/repository/maven-public/' } + maven { url 'https://maven.fabricmc.net/' } + maven { url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } + maven { url 'https://oss.sonatype.org/content/groups/public/' } + maven { url 'https://maven.nucleoid.xyz/' } + } +} diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 0000000..2abfca6 --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java-library' +} + +java { + targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_21 +} + +tasks.withType(JavaCompile).configureEach { + options.release = 21 +} + +dependencies { + compileOnly "net.kyori:adventure-api:${adventureVersion}" + compileOnly "net.kyori:adventure-text-minimessage:${miniMessageVersion}" + compileOnly "org.slf4j:slf4j-api:${slf4jVersion}" + api "com.zaxxer:HikariCP:${hikariVersion}" + compileOnly "org.jetbrains:annotations:24.0.0" + compileOnly "com.mojang:brigadier:1.3.10" +} diff --git a/core/src/main/java/simplexity/simplenicks/SimpleNicksCore.java b/core/src/main/java/simplexity/simplenicks/SimpleNicksCore.java new file mode 100644 index 0000000..d9ad9f4 --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/SimpleNicksCore.java @@ -0,0 +1,75 @@ +package simplexity.simplenicks; + +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.platform.PlatformAdapter; + +/** + * Central singleton for the SimpleNicks core. + *

+ * Initialized by the platform entry point (e.g. {@code SimpleNicks} on Paper, + * {@code SimpleNicksFabric} on Fabric) before any core class is used. + * All core classes access the platform through {@link #get()}. + *

+ */ +public final class SimpleNicksCore { + + private static SimpleNicksCore instance; + + private final PlatformAdapter platform; + private final MiniMessage miniMessage; + + private SimpleNicksCore(@NotNull PlatformAdapter platform) { + this.platform = platform; + this.miniMessage = platform.getMiniMessage(); + } + + /** + * Initializes the core with the given platform adapter. + * Must be called before any core class accesses {@link #get()}. + * + * @param platform the platform-specific adapter + */ + public static void initialize(@NotNull PlatformAdapter platform) { + instance = new SimpleNicksCore(platform); + } + + /** + * Returns the active core instance. + * + * @return the core singleton + * @throws IllegalStateException if {@link #initialize(PlatformAdapter)} has not been called + */ + @NotNull + public static SimpleNicksCore get() { + if (instance == null) throw new IllegalStateException("SimpleNicksCore has not been initialized"); + return instance; + } + + /** + * Tears down the core instance. Called during plugin/mod shutdown. + */ + public static void shutdown() { + instance = null; + } + + /** + * Returns the platform adapter for this environment. + * + * @return the platform adapter + */ + @NotNull + public PlatformAdapter platform() { + return platform; + } + + /** + * Returns the configured {@link MiniMessage} instance. + * + * @return the MiniMessage instance + */ + @NotNull + public MiniMessage miniMessage() { + return miniMessage; + } +} diff --git a/core/src/main/java/simplexity/simplenicks/commands/NicknameProcessor.java b/core/src/main/java/simplexity/simplenicks/commands/NicknameProcessor.java new file mode 100644 index 0000000..918776c --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/commands/NicknameProcessor.java @@ -0,0 +1,147 @@ +package simplexity.simplenicks.commands; + +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.saving.Cache; +import simplexity.simplenicks.saving.Nickname; +import simplexity.simplenicks.saving.SqlHandler; + +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Handles the high-level logic for nickname management. + *

+ * This class acts as the main entry point for commands or other + * external systems that want to interact with nicknames. + * It delegates persistence and caching to {@link Cache} and {@link SqlHandler}. + *

+ */ +@SuppressWarnings("UnusedReturnValue") +public class NicknameProcessor { + private static NicknameProcessor instance; + + private NicknameProcessor() { + } + + public static NicknameProcessor getInstance() { + if (instance == null) instance = new NicknameProcessor(); + return instance; + } + + /** + * Sets a player's active nickname. + * + * @param uuid the player's UUID + * @param username the player's last known username + * @param nickname the nickname string to assign + * @return {@code true} if the nickname was set successfully, + * {@code false} if it failed to persist or cache + */ + public boolean setNickname(@NotNull UUID uuid, @NotNull String username, @NotNull String nickname) { + return Cache.getInstance().setActiveNickname(uuid, username, nickname); + } + + /** + * Resets a player's nickname back to their original username. + * + * @param uuid the player's UUID + * @return {@code true} if the nickname was cleared successfully, + * {@code false} if the database update failed + */ + public boolean resetNickname(@NotNull UUID uuid) { + return Cache.getInstance().clearCurrentNickname(uuid); + } + + /** + * Saves a nickname to the player's list of saved nicknames. + * + * @param uuid the player's UUID + * @param username the player's last known username + * @param nickname the nickname string to save + * @return {@code true} if the nickname was saved successfully, + * {@code false} if it failed to persist or cache + */ + public boolean saveNickname(@NotNull UUID uuid, @NotNull String username, @NotNull String nickname) { + return Cache.getInstance().saveNickname(uuid, username, nickname); + } + + /** + * Deletes a previously saved nickname for a player. + * + * @param uuid the player's UUID + * @param nickname the nickname string to delete + * @return {@code true} if the nickname was deleted successfully, + * {@code false} if no such nickname was found or persistence failed + */ + public boolean deleteNickname(@NotNull UUID uuid, @NotNull String nickname) { + return Cache.getInstance().deleteSavedNickname(uuid, nickname); + } + + /** + * Gets all saved nicknames for a player. + *

+ * Uses the in-memory cache if the player is online, + * otherwise queries SQL directly. + *

+ * + * @param uuid the player's UUID + * @param isOnline whether the player is currently online + * @return a non-null list of {@link Nickname}; empty if none exist + */ + @NotNull + public List getSavedNicknames(@NotNull UUID uuid, boolean isOnline) { + if (isOnline) return Cache.getInstance().getSavedNicknames(uuid); + List nicks = SqlHandler.getInstance().getSavedNicknamesForPlayer(uuid); + if (nicks == null) return new ArrayList<>(); + return nicks; + } + + /** + * Gets the currently active nickname for a player. + *

+ * Uses the in-memory cache if the player is online, + * otherwise queries SQL directly. + *

+ * + * @param uuid the player's UUID + * @param isOnline whether the player is currently online + * @return the current {@link Nickname}, or {@code null} if none is set + */ + @Nullable + public Nickname getCurrentNickname(@NotNull UUID uuid, boolean isOnline) { + if (isOnline) return Cache.getInstance().getActiveNickname(uuid); + return SqlHandler.getInstance().getCurrentNicknameForPlayer(uuid); + } + + /** + * Gets the number of saved nicknames for a player. + *

+ * Uses the in-memory cache if the player is online, + * otherwise queries SQL directly. + *

+ * + * @param uuid the player's UUID + * @param isOnline whether the player is currently online + * @return the number of saved nicknames + */ + public int getCurrentSavedNickCount(@NotNull UUID uuid, boolean isOnline) { + if (isOnline) return Cache.getInstance().getSavedNickCount(uuid); + List savedNicks = SqlHandler.getInstance().getSavedNicknamesForPlayer(uuid); + if (savedNicks == null || savedNicks.isEmpty()) return 0; + return savedNicks.size(); + } + + /** + * Checks if a player has already saved the given nickname. + * + * @param uuid the player's UUID + * @param nickname the nickname string to search for + * @return {@code true} if the player already saved this nickname, + * {@code false} otherwise + */ + public boolean playerAlreadySavedThis(@NotNull UUID uuid, @NotNull String nickname) { + return SqlHandler.getInstance().userAlreadySavedThisName(uuid, nickname); + } +} diff --git a/core/src/main/java/simplexity/simplenicks/commands/subcommands/Exceptions.java b/core/src/main/java/simplexity/simplenicks/commands/subcommands/Exceptions.java new file mode 100644 index 0000000..14c933a --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/commands/subcommands/Exceptions.java @@ -0,0 +1,106 @@ +package simplexity.simplenicks.commands.subcommands; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.config.MessageUtils; + +/** + * Factory methods for Brigadier {@link CommandSyntaxException} instances. + *

+ * Exception messages are rendered as plain text by stripping MiniMessage tags, + * which works correctly on both Paper and Fabric. Methods are used instead of + * static fields to avoid class-load timing issues with {@link SimpleNicksCore}. + *

+ */ +public class Exceptions { + + private static String strip(String miniMessage) { + return SimpleNicksCore.get().miniMessage().stripTags(miniMessage); + } + + public static CommandSyntaxException nickIsNull() { + return new SimpleCommandExceptionType( + () -> strip(LocaleMessage.ERROR_NICK_IS_NULL.getMessage()) + ).create(); + } + + public static CommandSyntaxException emptyNickAfterParse() { + return new SimpleCommandExceptionType( + () -> strip(LocaleMessage.ERROR_INVALID_NICK_EMPTY.getMessage()) + ).create(); + } + + public static CommandSyntaxException cannotSave() { + return new SimpleCommandExceptionType( + () -> strip(LocaleMessage.ERROR_SAVE_FAILURE.getMessage()) + ).create(); + } + + public static CommandSyntaxException tooManySavedNames() { + return new SimpleCommandExceptionType( + () -> strip(LocaleMessage.ERROR_TOO_MANY_TO_SAVE.getMessage()) + ).create(); + } + + public static CommandSyntaxException tagsNotPermitted() { + return new SimpleCommandExceptionType( + () -> strip(LocaleMessage.ERROR_INVALID_TAGS.getMessage()) + ).create(); + } + + public static CommandSyntaxException alreadySaved() { + return new SimpleCommandExceptionType( + () -> strip(LocaleMessage.ERROR_ALREADY_SAVED.getMessage()) + ).create(); + } + + public static CommandSyntaxException lengthError(Object nickname) { + return new DynamicCommandExceptionType( + nick -> () -> strip(SimpleNicksCore.get().miniMessage().stripTags( + LocaleMessage.ERROR_INVALID_NICK_LENGTH.getMessage() + .replace("", String.valueOf(ConfigHandler.getInstance().getMaxLength())) + .replace("", nick.toString()) + )) + ).create(nickname); + } + + public static CommandSyntaxException regexError(Object nickname) { + return new DynamicCommandExceptionType( + nick -> () -> strip( + LocaleMessage.ERROR_INVALID_NICK.getMessage() + .replace("", ConfigHandler.getInstance().getRegexString()) + ) + ).create(nickname); + } + + public static CommandSyntaxException invalidPlayerSpecified(Object playerName) { + return new DynamicCommandExceptionType( + name -> () -> strip( + LocaleMessage.ERROR_INVALID_PLAYER.getMessage() + .replace("", name.toString()) + ) + ).create(playerName); + } + + public static CommandSyntaxException nicknameSomeonesUsername(Object nickname) { + return new DynamicCommandExceptionType( + nick -> () -> strip( + LocaleMessage.ERROR_INVALID_OTHER_PLAYERS_USERNAME.getMessage() + .replace("", nick.toString()) + ) + ).create(nickname); + } + + public static CommandSyntaxException someoneUsingThatNickname(Object nickname) { + return new DynamicCommandExceptionType( + nick -> () -> strip( + LocaleMessage.ERROR_INVALID_OTHER_PLAYERS_NICKNAME.getMessage() + .replace("", nick.toString()) + ) + ).create(nickname); + } +} diff --git a/src/main/java/simplexity/simplenicks/config/ConfigHandler.java b/core/src/main/java/simplexity/simplenicks/config/ConfigHandler.java similarity index 86% rename from src/main/java/simplexity/simplenicks/config/ConfigHandler.java rename to core/src/main/java/simplexity/simplenicks/config/ConfigHandler.java index 2111b3e..cd2927b 100644 --- a/src/main/java/simplexity/simplenicks/config/ConfigHandler.java +++ b/core/src/main/java/simplexity/simplenicks/config/ConfigHandler.java @@ -1,31 +1,21 @@ package simplexity.simplenicks.config; -import org.bukkit.configuration.file.FileConfiguration; -import simplexity.simplenicks.SimpleNicks; +import org.slf4j.Logger; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.platform.ConfigProvider; -import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; public class ConfigHandler { - public String getRegexString() { - return regexString; - } - - public String getNickPrefix() { - return nickPrefix; - } - - private static ConfigHandler instance; - private final Logger logger = SimpleNicks.getSimpleNicksLogger(); private Pattern regex; - private boolean mySql, tablistNick, usernameProtection, onlineNickProtection, offlineNickProtection, debugMode, nickRequiresPermission, - colorRequiresPermission, formatRequiresPermission, whoRequiresPermission; + private boolean mySql, tablistNick, usernameProtection, onlineNickProtection, offlineNickProtection, debugMode, + nickRequiresPermission, colorRequiresPermission, formatRequiresPermission, whoRequiresPermission; private int maxLength, maxSaves; - private final int MILLI_PER_DAY = 86_400_000; + private static final int MILLI_PER_DAY = 86_400_000; private String regexString, nickPrefix, mySqlIp, mySqlName, mySqlUsername, mySqlPassword; private long usernameProtectionTime, offlineNickProtectionTime = 0; @@ -38,17 +28,20 @@ public static ConfigHandler getInstance() { return instance; } + private static Logger logger() { + return SimpleNicksCore.get().platform().getLogger(); + } + public void reloadConfig() { - SimpleNicks.getInstance().reloadConfig(); + ConfigProvider config = SimpleNicksCore.get().platform().getConfigProvider(); + config.reload(); LocaleHandler.getInstance().reloadLocale(); - FileConfiguration config = SimpleNicks.getInstance().getConfig(); - // Check the validity of the regex. try { String regexSetting = config.getString("nickname-regex", "[A-Za-z0-9_]+"); regexString = regexSetting; regex = Pattern.compile(regexSetting); } catch (PatternSyntaxException e) { - logger.severe(LocaleMessage.ERROR_INVALID_CONFIG_REGEX.getMessage()); + logger().error(LocaleMessage.ERROR_INVALID_CONFIG_REGEX.getMessage(), e); } debugMode = config.getBoolean("debug-mode", false); mySql = config.getBoolean("mysql.enabled", false); @@ -71,11 +64,14 @@ public void reloadConfig() { offlineNickProtectionTime = config.getLong("nickname-protection.offline.expires", 30) * MILLI_PER_DAY; } - public Pattern getRegex() { return regex; } + public String getRegexString() { + return regexString; + } + public int getMaxLength() { return maxLength; } @@ -147,4 +143,8 @@ public boolean isWhoRequiresPermission() { public boolean isUsernameProtection() { return usernameProtection; } + + public String getNickPrefix() { + return nickPrefix; + } } diff --git a/core/src/main/java/simplexity/simplenicks/config/LocaleHandler.java b/core/src/main/java/simplexity/simplenicks/config/LocaleHandler.java new file mode 100644 index 0000000..896fccb --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/config/LocaleHandler.java @@ -0,0 +1,45 @@ +package simplexity.simplenicks.config; + +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.platform.ConfigProvider; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +@SuppressWarnings("CallToPrintStackTrace") +public class LocaleHandler { + + private static LocaleHandler instance; + + private LocaleHandler() { + } + + public static LocaleHandler getInstance() { + if (instance == null) { + instance = new LocaleHandler(); + } + return instance; + } + + public void reloadLocale() { + ConfigProvider locale = SimpleNicksCore.get().platform().getLocaleProvider(); + locale.reload(); + populateLocale(locale); + locale.save(); + } + + private void populateLocale(ConfigProvider locale) { + Set missing = new HashSet<>(Arrays.asList(LocaleMessage.values())); + for (LocaleMessage localeMessage : LocaleMessage.values()) { + if (locale.contains(localeMessage.getPath())) { + localeMessage.setMessage(locale.getString(localeMessage.getPath(), localeMessage.getDefaultMessage())); + missing.remove(localeMessage); + } + } + for (LocaleMessage localeMessage : missing) { + locale.set(localeMessage.getPath(), localeMessage.getDefaultMessage()); + localeMessage.setMessage(localeMessage.getDefaultMessage()); + } + } +} diff --git a/src/main/java/simplexity/simplenicks/config/LocaleMessage.java b/core/src/main/java/simplexity/simplenicks/config/LocaleMessage.java similarity index 95% rename from src/main/java/simplexity/simplenicks/config/LocaleMessage.java rename to core/src/main/java/simplexity/simplenicks/config/LocaleMessage.java index e469d8b..ca34e99 100644 --- a/src/main/java/simplexity/simplenicks/config/LocaleMessage.java +++ b/core/src/main/java/simplexity/simplenicks/config/LocaleMessage.java @@ -15,7 +15,6 @@ public enum LocaleMessage { HELP_ADMIN_DELETE("plugin.help.admin.delete", "/nick admin delete (username) (nickname) - Delete a saved nickname from a player"), HELP_ADMIN_LOOKUP("plugin.help.admin.lookup", "/nick admin lookup (username) - Look up a player's nickname info"), HELP_RELOAD("plugin.help.reload", "/nick reload - Reload the plugin configuration"), - SHOWN_HELP("plugin.user-shown-help", " has been shown the help screen"), CONFIG_RELOADED("plugin.config-reloaded", "SimpleNicks config and locale reloaded"), SERVER_DISPLAY_NAME("plugin.server-display-name", "[Server]"), @@ -84,10 +83,12 @@ public enum LocaleMessage { private final String path; + private final String defaultMessage; private String message; LocaleMessage(String path, String message) { this.path = path; + this.defaultMessage = message; this.message = message; } @@ -102,6 +103,17 @@ public String getMessage() { return message; } + /** + * Returns the hardcoded default message as declared in the enum. + * Used when writing missing keys back to a freshly created locale file. + * + * @return the default message string + */ + @NotNull + public String getDefaultMessage() { + return defaultMessage; + } + public void setMessage(@Nullable String message) { if (message == null) message = ""; this.message = message; diff --git a/src/main/java/simplexity/simplenicks/config/MessageUtils.java b/core/src/main/java/simplexity/simplenicks/config/MessageUtils.java similarity index 82% rename from src/main/java/simplexity/simplenicks/config/MessageUtils.java rename to core/src/main/java/simplexity/simplenicks/config/MessageUtils.java index 86e0460..438690d 100644 --- a/src/main/java/simplexity/simplenicks/config/MessageUtils.java +++ b/core/src/main/java/simplexity/simplenicks/config/MessageUtils.java @@ -1,21 +1,17 @@ package simplexity.simplenicks.config; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.saving.Nickname; import java.util.List; public class MessageUtils { - - private static final MiniMessage miniMessage = SimpleNicks.getMiniMessage(); - @NotNull public static TagResolver getTimeFormat(long timeSeconds) { long seconds = timeSeconds % 60; @@ -24,7 +20,7 @@ public static TagResolver getTimeFormat(long timeSeconds) { long days = (timeSeconds / (60 * 60 * 24)) % 365; if (days == 0 && hours == 0 && minutes == 0 && seconds == 0) { - Component nowComponent = miniMessage.deserialize(LocaleMessage.TIME_FORMAT_NOW.getMessage()); + Component nowComponent = SimpleNicksCore.get().miniMessage().deserialize(LocaleMessage.TIME_FORMAT_NOW.getMessage()); return TagResolver.resolver(Placeholder.component("time", nowComponent)); } @@ -59,7 +55,7 @@ public static TagResolver getTimeFormat(long timeSeconds) { ); } finalComponent = finalComponent.append( - miniMessage.deserialize(LocaleMessage.TIME_FORMAT_AGO.getMessage()) + SimpleNicksCore.get().miniMessage().deserialize(LocaleMessage.TIME_FORMAT_AGO.getMessage()) ); return TagResolver.resolver(Placeholder.component("time", finalComponent)); @@ -69,13 +65,13 @@ public static TagResolver getTimeFormat(long timeSeconds) { public static TagResolver savedNickListResolver(@Nullable List nicknames) { if (nicknames == null || nicknames.isEmpty()) { return TagResolver.resolver(Placeholder.component("list", - miniMessage.deserialize(LocaleMessage.LOOKUP_NO_SAVED_NICKS.getMessage()))); + SimpleNicksCore.get().miniMessage().deserialize(LocaleMessage.LOOKUP_NO_SAVED_NICKS.getMessage()))); } Component finalComponent = Component.empty(); for (Nickname nick : nicknames) { - Component nickname = SimpleNicks.getMiniMessage().deserialize(nick.getNickname()); + Component nickname = SimpleNicksCore.get().miniMessage().deserialize(nick.getNickname()); finalComponent = finalComponent.append( - miniMessage.deserialize( + SimpleNicksCore.get().miniMessage().deserialize( LocaleMessage.LOOKUP_SAVED_NICK.getMessage(), Placeholder.component("name", nickname) )); @@ -85,9 +81,7 @@ public static TagResolver savedNickListResolver(@Nullable List nicknam @NotNull private static Component parseNumber(@NotNull String message, long number) { - return miniMessage.deserialize(message, + return SimpleNicksCore.get().miniMessage().deserialize(message, Placeholder.parsed("count", String.valueOf(number))); } - - } diff --git a/src/main/java/simplexity/simplenicks/logic/NickUtils.java b/core/src/main/java/simplexity/simplenicks/logic/NickUtils.java similarity index 54% rename from src/main/java/simplexity/simplenicks/logic/NickUtils.java rename to core/src/main/java/simplexity/simplenicks/logic/NickUtils.java index c0513c6..7b797d8 100644 --- a/src/main/java/simplexity/simplenicks/logic/NickUtils.java +++ b/core/src/main/java/simplexity/simplenicks/logic/NickUtils.java @@ -4,14 +4,12 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.subcommands.Exceptions; import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.platform.PlayerInfo; +import simplexity.simplenicks.platform.SenderContext; import simplexity.simplenicks.saving.Cache; import simplexity.simplenicks.saving.Nickname; import simplexity.simplenicks.saving.SqlHandler; @@ -19,6 +17,8 @@ import simplexity.simplenicks.util.FormatTag; import simplexity.simplenicks.util.NickPermission; +import org.jetbrains.annotations.Nullable; + import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -35,47 +35,46 @@ @SuppressWarnings("UnusedReturnValue") public class NickUtils { - private static final MiniMessage miniMessage = SimpleNicks.getMiniMessage(); - + private static MiniMessage mm() { + return SimpleNicksCore.get().miniMessage(); + } /** * Performs all configured checks on a nickname, including length, regex, * username conflicts, and nickname protection. Throws a {@link CommandSyntaxException} * if any of the checks fail. * - * @param sender The sender attempting to set the nickname - * @param nickname The nickname to validate + * @param sender the sender attempting to set the nickname + * @param nickname the nickname to validate * @throws CommandSyntaxException if any of the nickname checks fail */ - public static void nicknameChecks(@NotNull CommandSender sender, @NotNull Nickname nickname) throws CommandSyntaxException { + public static void nicknameChecks(@NotNull SenderContext sender, @NotNull Nickname nickname) throws CommandSyntaxException { String normalizedNick = nickname.getNormalizedNickname(); if (normalizedNick.isEmpty()) { - throw Exceptions.ERROR_EMPTY_NICK_AFTER_PARSE.create(); + throw Exceptions.emptyNickAfterParse(); } - boolean bypassUsername = sender.hasPermission(NickPermission.NICK_BYPASS_USERNAME.getPermission()); - boolean bypassLength = sender.hasPermission(NickPermission.NICK_BYPASS_LENGTH.getPermission()); - boolean bypassRegex = sender.hasPermission(NickPermission.NICK_BYPASS_REGEX.getPermission()); - boolean bypassNickProtection = sender.hasPermission(NickPermission.NICK_BYPASS_NICK_PROTECTION.getPermission()); + boolean bypassUsername = sender.hasPermission(NickPermission.NICK_BYPASS_USERNAME.getPermissionKey()); + boolean bypassLength = sender.hasPermission(NickPermission.NICK_BYPASS_LENGTH.getPermissionKey()); + boolean bypassRegex = sender.hasPermission(NickPermission.NICK_BYPASS_REGEX.getPermissionKey()); + boolean bypassNickProtection = sender.hasPermission(NickPermission.NICK_BYPASS_NICK_PROTECTION.getPermissionKey()); - if (!bypassUsername) { - if (ConfigHandler.getInstance().isUsernameProtection() && isProtectedUsername(normalizedNick)) - throw Exceptions.ERROR_NICKNAME_IS_SOMEONES_USERNAME.create(normalizedNick); + if (!bypassUsername && ConfigHandler.getInstance().isUsernameProtection() && isProtectedUsername(normalizedNick)) { + throw Exceptions.nicknameSomeonesUsername(normalizedNick); } - if (!bypassLength) { - if (normalizedNick.length() > ConfigHandler.getInstance().getMaxLength()) - throw Exceptions.ERROR_LENGTH.create(normalizedNick); + if (!bypassLength && normalizedNick.length() > ConfigHandler.getInstance().getMaxLength()) { + throw Exceptions.lengthError(normalizedNick); } - if (!bypassRegex) { - if (!passesRegexCheck(normalizedNick)) throw Exceptions.ERROR_REGEX.create(normalizedNick); + if (!bypassRegex && !passesRegexCheck(normalizedNick)) { + throw Exceptions.regexError(normalizedNick); } - if (ConfigHandler.getInstance().shouldOnlineNicksBeProtected() && !bypassNickProtection) { - if (someoneOnlineUsingThis(sender, normalizedNick)) - throw Exceptions.ERROR_SOMEONE_USING_THAT_NICKNAME.create(normalizedNick); - } - if (ConfigHandler.getInstance().shouldOfflineNicksBeProtected() && !bypassNickProtection) { - if (someoneSavedUsingThis(sender, normalizedNick)) - throw Exceptions.ERROR_SOMEONE_USING_THAT_NICKNAME.create(normalizedNick); + if (!bypassNickProtection) { + if (ConfigHandler.getInstance().shouldOnlineNicksBeProtected() && someoneOnlineUsingThis(sender, normalizedNick)) { + throw Exceptions.someoneUsingThatNickname(normalizedNick); + } + if (ConfigHandler.getInstance().shouldOfflineNicksBeProtected() && someoneSavedUsingThis(sender, normalizedNick)) { + throw Exceptions.someoneUsingThatNickname(normalizedNick); + } } } @@ -83,57 +82,57 @@ public static void nicknameChecks(@NotNull CommandSender sender, @NotNull Nickna * Updates a player's display name and optionally their tab list name to reflect their * active nickname. * - * @param uuid The UUID of the player whose display name should be refreshed + * @param uuid the UUID of the player whose display name should be refreshed * @return true if the player's display name was successfully refreshed, false if the player is offline */ public static boolean refreshDisplayName(@NotNull UUID uuid) { - Player player = Bukkit.getPlayer(uuid); - if (player == null) return false; + if (!SimpleNicksCore.get().platform().isPlayerOnline(uuid)) return false; Nickname nickname = Cache.getInstance().getActiveNickname(uuid); if (nickname == null) { - player.displayName(null); + SimpleNicksCore.get().platform().clearDisplayName(uuid); + if (ConfigHandler.getInstance().shouldNickTablist()) { + SimpleNicksCore.get().platform().clearTablistName(uuid); + } return true; } - Component displayName = miniMessage.deserialize(ConfigHandler.getInstance().getNickPrefix()) - .append(SimpleNicks.getMiniMessage().deserialize(nickname.getNickname())); - player.displayName(displayName); + Component displayName = mm().deserialize(ConfigHandler.getInstance().getNickPrefix()) + .append(mm().deserialize(nickname.getNickname())); + SimpleNicksCore.get().platform().setDisplayName(uuid, displayName); if (ConfigHandler.getInstance().shouldNickTablist()) { - player.playerListName(SimpleNicks.getMiniMessage().deserialize(nickname.getNickname())); + SimpleNicksCore.get().platform().setTablistName(uuid, mm().deserialize(nickname.getNickname())); } return true; } - /** - * Checks whether the given nickname only uses tags and formatting that the player + * Checks whether the given nickname only uses tags and formatting that the sender * has permission to use. * - * @param user The command sender attempting to use the nickname - * @param nick The nickname to validate + * @param user the sender attempting to use the nickname + * @param nick the nickname to validate * @return true if the nickname only uses allowed tags, false otherwise */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public static boolean isValidTags(@NotNull CommandSender user, @NotNull String nick) { + public static boolean isValidTags(@NotNull SenderContext user, @NotNull String nick) { TagResolver.Builder resolver = TagResolver.builder(); for (ColorTag colorTag : ColorTag.values()) { - if (user.hasPermission(colorTag.getPermission()) || !ConfigHandler.getInstance().isColorRequiresPermission()) { + if (user.hasPermission(colorTag.getPermissionKey()) || !ConfigHandler.getInstance().isColorRequiresPermission()) { resolver.resolver(colorTag.getTagResolver()); } } for (FormatTag formatTag : FormatTag.values()) { - if (user.hasPermission(formatTag.getPermission()) || !ConfigHandler.getInstance().isFormatRequiresPermission()) { + if (user.hasPermission(formatTag.getPermissionKey()) || !ConfigHandler.getInstance().isFormatRequiresPermission()) { resolver.resolver(formatTag.getTagResolver()); } } - MiniMessage parser = MiniMessage.builder().strict(false).tags(resolver.build()).build(); - Component defaultParsed = miniMessage.deserialize(nick); - String defaultSerialized = miniMessage.serialize(defaultParsed); + Component defaultParsed = mm().deserialize(nick); + String defaultSerialized = mm().serialize(defaultParsed); Component permissionParsed = parser.deserialize(nick); - String permissionSerialized = miniMessage.serialize(permissionParsed); + String permissionSerialized = mm().serialize(permissionParsed); return defaultSerialized.equals(permissionSerialized); } @@ -142,37 +141,42 @@ public static boolean isValidTags(@NotNull CommandSender user, @NotNull String n * Converts a nickname into a "normalized" version by stripping all MiniMessage tags * and converting to lowercase. Used for comparisons and storage. * - * @param nickname The nickname to normalize - * @return The normalized nickname string + * @param nickname the nickname to normalize + * @return the normalized nickname string */ public static String normalizeNickname(@NotNull String nickname) { - return miniMessage.stripTags(nickname).toLowerCase(); + return mm().stripTags(nickname).toLowerCase(); } /** - * Retrieves offline players who have saved a specific normalized nickname. + * Retrieves players who have a specific normalized nickname set as their active nickname. + *

+ * Uses {@link SqlHandler#playerSaveExists(UUID)} instead of platform APIs to determine + * whether a UUID belongs to a known player. + *

* - * @param normalizedNickname The normalized nickname to search for - * @return A list of OfflinePlayer objects who have used the nickname, - * or null if no players were found + * @param normalizedNickname the normalized nickname to search for + * @return a list of {@link PlayerInfo} for players who use the nickname */ @NotNull - public static List getOfflinePlayersByNickname(@NotNull String normalizedNickname) { + public static List getPlayersByNickname(@NotNull String normalizedNickname) { List usersWithThisName = SqlHandler.getInstance().getUuidsOfNickname(normalizedNickname); if (usersWithThisName == null || usersWithThisName.isEmpty()) return new ArrayList<>(); - List playersByNick = new ArrayList<>(); + List result = new ArrayList<>(); for (UUID uuid : usersWithThisName) { - OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); - if (!offlinePlayer.hasPlayedBefore()) continue; - playersByNick.add(offlinePlayer); + if (!SqlHandler.getInstance().playerSaveExists(uuid)) continue; + String username = SimpleNicksCore.get().platform().getPlayerUsername(uuid) + .orElseGet(() -> uuid.toString()); + long lastLogin = SqlHandler.getInstance().getLastLoginMillis(uuid); + result.add(new PlayerInfo(uuid, username, lastLogin)); } - return playersByNick; + return result; } /** * Checks if a normalized nickname passes the regex pattern defined in the configuration. * - * @param normalizedNick The normalized nickname to validate + * @param normalizedNick the normalized nickname to validate * @return true if the nickname matches the regex, false otherwise */ public static boolean passesRegexCheck(@NotNull String normalizedNick) { @@ -185,12 +189,14 @@ public static boolean passesRegexCheck(@NotNull String normalizedNick) { * This prevents nicknames from being set to usernames of other players within the * protection period. * - * @param normalizedName The normalized nickname to check + * @param normalizedName the normalized nickname to check * @return true if the nickname matches a protected username, false otherwise */ public static boolean isProtectedUsername(@NotNull String normalizedName) { normalizedName = normalizedName.toLowerCase(); - long expireTime = ConfigHandler.getInstance().getUsernameProtectionTime() == -1 ? System.currentTimeMillis() - ConfigHandler.getInstance().getUsernameProtectionTime() : -1; + long expireTime = ConfigHandler.getInstance().getUsernameProtectionTime() == -1 + ? System.currentTimeMillis() - ConfigHandler.getInstance().getUsernameProtectionTime() + : -1; return SqlHandler.getInstance().lastLoginOfUsername(normalizedName, expireTime) != null; } @@ -198,13 +204,12 @@ public static boolean isProtectedUsername(@NotNull String normalizedName) { * Checks if an online player (other than the sender) is currently using the given * normalized nickname. * - * @param sender The command sender attempting to set the nickname - * @param normalizedNick The normalized nickname to check + * @param sender the sender attempting to set the nickname + * @param normalizedNick the normalized nickname to check * @return true if another online player is using this nickname, false otherwise */ - public static boolean someoneOnlineUsingThis(@NotNull CommandSender sender, @NotNull String normalizedNick) { - UUID playerUuid = null; - if (sender instanceof Player playerSender) playerUuid = playerSender.getUniqueId(); + public static boolean someoneOnlineUsingThis(@NotNull SenderContext sender, @NotNull String normalizedNick) { + UUID playerUuid = sender.getUuid().orElse(null); return Cache.getInstance().nickInUseOnlinePlayers(playerUuid, normalizedNick); } @@ -212,21 +217,19 @@ public static boolean someoneOnlineUsingThis(@NotNull CommandSender sender, @Not * Checks if the given normalized nickname is already saved by another player and is * protected based on offline nickname protection settings. * - * @param sender The command sender attempting to set the nickname - * @param normalizedNick The normalized nickname to check + * @param sender the sender attempting to set the nickname + * @param normalizedNick the normalized nickname to check * @return true if the nickname is already saved and protected, false otherwise */ - public static boolean someoneSavedUsingThis(@NotNull CommandSender sender, @NotNull String normalizedNick) { - UUID senderUuid = null; - if (sender instanceof Player playerSender) senderUuid = playerSender.getUniqueId(); + public static boolean someoneSavedUsingThis(@NotNull SenderContext sender, @NotNull String normalizedNick) { + UUID senderUuid = sender.getUuid().orElse(null); List uuidsWithThis = SqlHandler.getInstance().nickAlreadySavedTo(senderUuid, normalizedNick); if (uuidsWithThis == null || uuidsWithThis.isEmpty()) return false; for (UUID uuid : uuidsWithThis) { - if (SqlHandler.getInstance().lastLoginOfUuid(uuid, ConfigHandler.getInstance().getOfflineNickProtectionTime()) != null) + if (SqlHandler.getInstance().lastLoginOfUuid(uuid, ConfigHandler.getInstance().getOfflineNickProtectionTime()) != null) { return true; + } } return false; } - - } diff --git a/core/src/main/java/simplexity/simplenicks/platform/ConfigProvider.java b/core/src/main/java/simplexity/simplenicks/platform/ConfigProvider.java new file mode 100644 index 0000000..728b17d --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/platform/ConfigProvider.java @@ -0,0 +1,74 @@ +package simplexity.simplenicks.platform; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Abstracts YAML file reading and writing so that core config/locale handlers + * do not depend on platform-specific config APIs (e.g. Bukkit {@code FileConfiguration}). + */ +public interface ConfigProvider { + + /** + * Reloads values from the backing file. + */ + void reload(); + + /** + * Returns the string value at {@code key}, or {@code defaultValue} if absent. + * + * @param key the config key + * @param defaultValue fallback value + * @return the string value, or {@code defaultValue} + */ + @Nullable + String getString(@NotNull String key, @Nullable String defaultValue); + + /** + * Returns the int value at {@code key}, or {@code defaultValue} if absent or not an int. + * + * @param key the config key + * @param defaultValue fallback value + * @return the int value, or {@code defaultValue} + */ + int getInt(@NotNull String key, int defaultValue); + + /** + * Returns the boolean value at {@code key}, or {@code defaultValue} if absent. + * + * @param key the config key + * @param defaultValue fallback value + * @return the boolean value, or {@code defaultValue} + */ + boolean getBoolean(@NotNull String key, boolean defaultValue); + + /** + * Returns the long value at {@code key}, or {@code defaultValue} if absent. + * + * @param key the config key + * @param defaultValue fallback value + * @return the long value, or {@code defaultValue} + */ + long getLong(@NotNull String key, long defaultValue); + + /** + * Sets a value in the backing store. Does not persist until {@link #save()} is called. + * + * @param key the config key + * @param value the value to set + */ + void set(@NotNull String key, @Nullable Object value); + + /** + * Persists the current state of the backing store to disk. + */ + void save(); + + /** + * Returns whether the given key exists in the backing store. + * + * @param key the config key + * @return {@code true} if the key is present + */ + boolean contains(@NotNull String key); +} diff --git a/core/src/main/java/simplexity/simplenicks/platform/PlatformAdapter.java b/core/src/main/java/simplexity/simplenicks/platform/PlatformAdapter.java new file mode 100644 index 0000000..bafb491 --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/platform/PlatformAdapter.java @@ -0,0 +1,140 @@ +package simplexity.simplenicks.platform; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; + +/** + * Abstracts all platform-specific operations (scheduling, player access, display names, permissions). + *

+ * Each platform (Paper, Fabric) provides its own implementation. Core logic accesses the + * platform exclusively through this interface via {@link simplexity.simplenicks.SimpleNicksCore}. + *

+ */ +public interface PlatformAdapter { + + /** + * Runs a task on a background thread. + * + * @param task the task to run asynchronously + */ + void runAsync(@NotNull Runnable task); + + /** + * Runs a task on the main server thread. + * + * @param task the task to run synchronously + */ + void runSync(@NotNull Runnable task); + + /** + * Returns whether a player is currently online. + * + * @param uuid the player's UUID + * @return {@code true} if the player is online + */ + boolean isPlayerOnline(@NotNull UUID uuid); + + /** + * Returns the username of a player if they are currently online. + * + * @param uuid the player's UUID + * @return the username, or empty if offline + */ + @NotNull + Optional getPlayerUsername(@NotNull UUID uuid); + + /** + * Returns the UUIDs of all currently online players. + * + * @return collection of online player UUIDs + */ + @NotNull + Collection getOnlinePlayers(); + + /** + * Sets the display name shown in chat and above the player's head. + * + * @param uuid the player's UUID + * @param displayName the Adventure component to display + */ + void setDisplayName(@NotNull UUID uuid, @NotNull Component displayName); + + /** + * Sets the name shown in the tab list. + * + * @param uuid the player's UUID + * @param tablistName the Adventure component to display + */ + void setTablistName(@NotNull UUID uuid, @NotNull Component tablistName); + + /** + * Clears the player's display name, reverting to their username. + * + * @param uuid the player's UUID + */ + void clearDisplayName(@NotNull UUID uuid); + + /** + * Clears the player's tab list name, reverting to their username. + * + * @param uuid the player's UUID + */ + void clearTablistName(@NotNull UUID uuid); + + /** + * Checks whether the given player has a permission node. + * + * @param uuid the player's UUID + * @param permission the permission node string + * @return {@code true} if the player has the permission + */ + boolean hasPermission(@NotNull UUID uuid, @NotNull String permission); + + /** + * Returns the plugin's data directory (where config and database files are stored). + * + * @return the data directory path + */ + @NotNull + Path getDataDirectory(); + + /** + * Returns the plugin logger. + * + * @return SLF4J logger instance + */ + @NotNull + Logger getLogger(); + + /** + * Returns the configured {@link MiniMessage} instance with all permitted color and format + * tag resolvers registered. + * + * @return the MiniMessage instance + */ + @NotNull + MiniMessage getMiniMessage(); + + /** + * Returns the {@link ConfigProvider} for {@code config.yml}. + * + * @return config provider + */ + @NotNull + ConfigProvider getConfigProvider(); + + /** + * Returns the {@link ConfigProvider} for {@code locale.yml}. + * + * @return locale provider + */ + @NotNull + ConfigProvider getLocaleProvider(); +} diff --git a/core/src/main/java/simplexity/simplenicks/platform/PlayerInfo.java b/core/src/main/java/simplexity/simplenicks/platform/PlayerInfo.java new file mode 100644 index 0000000..9f2cd21 --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/platform/PlayerInfo.java @@ -0,0 +1,18 @@ +package simplexity.simplenicks.platform; + +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +/** + * Immutable snapshot of a player's identity and last login time. + *

+ * Replaces {@code OfflinePlayer} in core logic so that player data can be + * resolved purely from the database without platform API calls. + *

+ * + * @param uuid the player's UUID + * @param username the player's last known username + * @param lastLoginMillis epoch milliseconds of the player's last login, or {@code -1} if unknown + */ +public record PlayerInfo(@NotNull UUID uuid, @NotNull String username, long lastLoginMillis) {} diff --git a/core/src/main/java/simplexity/simplenicks/platform/SenderContext.java b/core/src/main/java/simplexity/simplenicks/platform/SenderContext.java new file mode 100644 index 0000000..b01dd8b --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/platform/SenderContext.java @@ -0,0 +1,56 @@ +package simplexity.simplenicks.platform; + +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; +import java.util.UUID; + +/** + * Platform-agnostic abstraction for a command sender (player or console). + *

+ * Paper wraps {@code CommandSender}; Fabric wraps {@code CommandSourceStack}. + * Core logic uses this interface instead of platform types for permission checks + * and message delivery. + *

+ */ +public interface SenderContext { + + /** + * Checks whether this sender has the given permission node. + * + * @param permission the permission node string + * @return {@code true} if the sender has the permission + */ + boolean hasPermission(@NotNull String permission); + + /** + * Returns the UUID of this sender if they are a player, otherwise empty. + * + * @return the player's UUID, or empty for the console + */ + @NotNull + Optional getUuid(); + + /** + * Sends an Adventure component message to this sender. + * + * @param message the component to send + */ + void sendMessage(@NotNull Component message); + + /** + * Returns whether this sender is a player (as opposed to the console or a command block). + * + * @return {@code true} if the sender is a player + */ + boolean isPlayer(); + + /** + * Returns a display-friendly name for this sender (player name or "[Console]"). + * + * @return the sender's display name + */ + @NotNull + String getDisplayName(); +} diff --git a/src/main/java/simplexity/simplenicks/saving/Cache.java b/core/src/main/java/simplexity/simplenicks/saving/Cache.java similarity index 92% rename from src/main/java/simplexity/simplenicks/saving/Cache.java rename to core/src/main/java/simplexity/simplenicks/saving/Cache.java index 9cce695..96bc431 100644 --- a/src/main/java/simplexity/simplenicks/saving/Cache.java +++ b/core/src/main/java/simplexity/simplenicks/saving/Cache.java @@ -1,13 +1,11 @@ package simplexity.simplenicks.saving; -import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.config.ConfigHandler; -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -39,7 +37,6 @@ */ public class Cache { private static Cache instance; - private static final Logger logger = SimpleNicks.getInstance().getSLF4JLogger(); public Cache() { } @@ -49,7 +46,10 @@ public static Cache getInstance() { return instance; } - private final MiniMessage miniMessage = SimpleNicks.getMiniMessage(); + private static Logger logger() { + return SimpleNicksCore.get().platform().getLogger(); + } + private final HashMap activeNicknames = new HashMap<>(); private final HashMap> savedNicknames = new HashMap<>(); @@ -59,7 +59,6 @@ public static Cache getInstance() { * @param uuid player UUID * @see SqlHandler#getCurrentNicknameForPlayer(UUID) */ - public void loadCurrentNickname(@NotNull UUID uuid) { debug("Loading current nickname for UUID %s", uuid); Nickname currentNick = SqlHandler.getInstance().getCurrentNicknameForPlayer(uuid); @@ -123,10 +122,10 @@ public Nickname getActiveNickname(@NotNull UUID uuid) { public List getSavedNicknames(@NotNull UUID uuid) { debug("Getting saved nicknames for UUID %s", uuid); if (savedNicknames.containsKey(uuid)) { - debug("Saved nicknames exists for UUID %s, Nicknames:", uuid, savedNicknames.get(uuid)); + debug("Saved nicknames exists for UUID %s", uuid); return savedNicknames.get(uuid); } - debug("No Saved Nicknames for UUID %s", uuid); + debug("No saved nicknames for UUID %s", uuid); return new ArrayList<>(); } @@ -145,10 +144,9 @@ public List getSavedNicknames(@NotNull UUID uuid) { * @see SqlHandler#setActiveNickname(UUID, String, String, String) * @see SqlHandler#updatePlayerTable(UUID, String) */ - public boolean setActiveNickname(@NotNull UUID uuid, @NotNull String username, @NotNull String nickname) { debug("Setting active nickname '%s' for UUID %s", nickname, uuid); - String normalizedNick = miniMessage.stripTags(nickname).toLowerCase(); + String normalizedNick = SimpleNicksCore.get().miniMessage().stripTags(nickname).toLowerCase(); Nickname nick = new Nickname(nickname, normalizedNick); boolean sqlActiveNameSet = SqlHandler.getInstance().setActiveNickname(uuid, username, nickname, normalizedNick); if (!sqlActiveNameSet) { @@ -175,7 +173,6 @@ public boolean setActiveNickname(@NotNull UUID uuid, @NotNull String username, @ * @return {@code true} if the nickname was deleted; {@code false} if it didn't exist or SQL failed * @see SqlHandler#deleteNickname(UUID, String) */ - public boolean deleteSavedNickname(@NotNull UUID uuid, @NotNull String nickname) { debug("Deleting saved nickname '%s' for UUID %s", nickname, uuid); boolean sqlDeleted = SqlHandler.getInstance().deleteNickname(uuid, nickname); @@ -227,11 +224,10 @@ public boolean clearCurrentNickname(@NotNull UUID uuid) { /** * Resolves all UUIDs of players who currently use a given normalized nickname. * - * @param nickname normalized nickname (tags stripped & lowercased) + * @param nickname normalized nickname (tags stripped & lowercased) * @return list of UUIDs; empty if none found or SQL error * @see SqlHandler#getUuidsOfNickname(String) */ - @NotNull public List getUuidOfNormalizedName(@NotNull String nickname) { debug("Getting UUIDs for normalized nickname '%s'", nickname); @@ -279,7 +275,7 @@ public boolean nickInUseOnlinePlayers(@Nullable UUID uuid, @NotNull String norma */ public boolean saveNickname(@NotNull UUID uuid, @NotNull String username, @NotNull String nickname) { debug("Saving nickname '%s' for UUID %s", nickname, uuid); - String normalized = miniMessage.stripTags(nickname).toLowerCase(); + String normalized = SimpleNicksCore.get().miniMessage().stripTags(nickname).toLowerCase(); Nickname nick = new Nickname(nickname, normalized); List userSavedNicknames = getSavedNicknames(uuid); userSavedNicknames.add(nick); @@ -297,7 +293,6 @@ public boolean saveNickname(@NotNull UUID uuid, @NotNull String username, @NotNu return true; } - /** * Gets the number of saved nicknames for a player in the cache. * @@ -335,21 +330,18 @@ public void removePlayerFromCache(@NotNull UUID uuid) { * @return unmodifiable map of UUID → active nickname */ @NotNull - public Map getOnlineNicknames(){ + public Map getOnlineNicknames() { debug("Getting all online nicknames from cache"); return Collections.unmodifiableMap(activeNicknames); } - private boolean playerIsOffline(@NotNull UUID uuid){ - return Bukkit.getPlayer(uuid) == null; + private boolean playerIsOffline(@NotNull UUID uuid) { + return !SimpleNicksCore.get().platform().isPlayerOnline(uuid); } private void debug(@NotNull String message, @Nullable Object... args) { if (ConfigHandler.getInstance().isDebugMode()) { - String messageToSend = String.format(message, args); - logger.info("[CACHE DEBUG] {}", messageToSend); + logger().info("[CACHE DEBUG] " + String.format(message, args)); } } - - } diff --git a/src/main/java/simplexity/simplenicks/saving/Nickname.java b/core/src/main/java/simplexity/simplenicks/saving/Nickname.java similarity index 100% rename from src/main/java/simplexity/simplenicks/saving/Nickname.java rename to core/src/main/java/simplexity/simplenicks/saving/Nickname.java diff --git a/src/main/java/simplexity/simplenicks/saving/NicknameRecord.java b/core/src/main/java/simplexity/simplenicks/saving/NicknameRecord.java similarity index 100% rename from src/main/java/simplexity/simplenicks/saving/NicknameRecord.java rename to core/src/main/java/simplexity/simplenicks/saving/NicknameRecord.java diff --git a/src/main/java/simplexity/simplenicks/saving/SqlHandler.java b/core/src/main/java/simplexity/simplenicks/saving/SqlHandler.java similarity index 85% rename from src/main/java/simplexity/simplenicks/saving/SqlHandler.java rename to core/src/main/java/simplexity/simplenicks/saving/SqlHandler.java index 8ba1740..2bdf12a 100644 --- a/src/main/java/simplexity/simplenicks/saving/SqlHandler.java +++ b/core/src/main/java/simplexity/simplenicks/saving/SqlHandler.java @@ -4,15 +4,18 @@ import com.zaxxer.hikari.HikariDataSource; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.config.ConfigHandler; -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.UUID; @@ -41,7 +44,7 @@ private SqlHandler() { } /** - * Returns the instance of the SQL handler + * Returns the instance of the SQL handler. * * @return SqlHandler */ @@ -53,8 +56,16 @@ public static SqlHandler getInstance() { private static final HikariConfig hikariConfig = new HikariConfig(); private static HikariDataSource dataSource; - private final Logger logger = SimpleNicks.getInstance().getSLF4JLogger(); private static final int SCHEMA_VERSION = 1; + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + static { + // SQLite CURRENT_TIMESTAMP stores UTC; parse accordingly regardless of JVM timezone + DATE_FORMAT.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); + } + + private static Logger logger() { + return SimpleNicksCore.get().platform().getLogger(); + } /** * Initializes the database, creating tables if they do not exist @@ -120,7 +131,7 @@ REFERENCES players(uuid) """); currentNickStatement.execute(); } catch (SQLException e) { - logger.warn("Issue connecting to database: {} ", e.getMessage(), e); + logger().warn("Issue connecting to database: {} ", e.getMessage(), e); } } @@ -133,8 +144,7 @@ REFERENCES players(uuid) */ @Nullable public List nickAlreadySavedTo(@Nullable UUID uuidToExclude, @NotNull String normalized) { - debug("Checking if nickname '{}' is already in use (excluding UUID='{}')", normalized, - uuidToExclude); + debug("Checking if nickname '{}' is already in use (excluding UUID='{}')", normalized, uuidToExclude); String queryString = "SELECT uuid FROM current_nicknames WHERE nickname = ?"; List uuidsWithName = new ArrayList<>(); try (Connection connection = getConnection()) { @@ -149,7 +159,7 @@ public List nickAlreadySavedTo(@Nullable UUID uuidToExclude, @NotNull Stri } return uuidsWithName; } catch (SQLException e) { - logger.warn("Failed to check if nickname exists: {}", normalized, e); + logger().warn("Failed to check if nickname exists: {}", normalized, e); return null; } } @@ -178,7 +188,7 @@ public List getSavedNicknamesForPlayer(@NotNull UUID uuid) { savedNicknames.add(nick); } } catch (SQLException e) { - logger.warn("Failed to get saved nicknames for player with UUID: {}", uuid, e); + logger().warn("Failed to get saved nicknames for player with UUID: {}", uuid, e); return null; } return savedNicknames; @@ -191,7 +201,6 @@ public List getSavedNicknamesForPlayer(@NotNull UUID uuid) { * @param nickname the nickname string to check * @return true if the nickname is already saved, false otherwise */ - public boolean userAlreadySavedThisName(@NotNull UUID uuid, @NotNull String nickname) { debug("Checking if UUID={} already saved nickname='{}'", uuid, nickname); if (!playerSaveExists(uuid)) return false; @@ -203,12 +212,11 @@ public boolean userAlreadySavedThisName(@NotNull UUID uuid, @NotNull String nick ResultSet resultSet = statement.executeQuery(); return resultSet.next(); } catch (SQLException e) { - logger.warn("Failed to check if UUID '{}' has already saved the nickname '{}'", uuid, nickname, e); + logger().warn("Failed to check if UUID '{}' has already saved the nickname '{}'", uuid, nickname, e); return false; } } - /** * Gets the player's currently active nickname. * @@ -232,10 +240,9 @@ public Nickname getCurrentNicknameForPlayer(@NotNull UUID uuid) { } return null; } catch (SQLException e) { - logger.warn("Failed to get active nickname for UUID: {}", uuid, e); + logger().warn("Failed to get active nickname for UUID: {}", uuid, e); return null; } - } /** @@ -260,7 +267,7 @@ public List getUuidsOfNickname(@NotNull String normalized) { } return uuids; } catch (SQLException e) { - logger.warn("Failed to get UUID list from normalized nickname: {}", normalized, e); + logger().warn("Failed to get UUID list from normalized nickname: {}", normalized, e); return null; } } @@ -268,15 +275,14 @@ public List getUuidsOfNickname(@NotNull String normalized) { /** * Saves a nickname for a player in the database. * - * @param uuid the player's UUID - * @param username the last known username - * @param nickname the chosen nickname + * @param uuid the player's UUID + * @param username the last known username + * @param nickname the chosen nickname * @param normalized normalized form of the nickname * @return true if the nickname was saved successfully, false otherwise */ public boolean saveNickname(@NotNull UUID uuid, @NotNull String username, @NotNull String nickname, @NotNull String normalized) { - debug("Saving nickname '{}' (normalized '{}') for UUID={}, username={}", nickname, - normalized, uuid, username); + debug("Saving nickname '{}' (normalized '{}') for UUID={}, username={}", nickname, normalized, uuid, username); String saveString = "REPLACE INTO saved_nicknames (uuid, nickname, normalized) VALUES (?, ?, ?)"; if (!playerSaveExists(uuid)) { if (!updatePlayerTable(uuid, username)) return false; @@ -290,13 +296,12 @@ public boolean saveNickname(@NotNull UUID uuid, @NotNull String username, @NotNu debug("Rows modified when saving nickname: {}", rowsChanged); return rowsChanged > 0; } catch (SQLException e) { - logger.warn("Failed to save nickname '{}' for UUID '{}'. Normalized nickname: {}, Username: {} ", + logger().warn("Failed to save nickname '{}' for UUID '{}'. Normalized nickname: {}, Username: {} ", nickname, uuid, normalized, username, e); return false; } } - /** * Deletes a saved nickname for a player. * @@ -316,19 +321,17 @@ public boolean deleteNickname(@NotNull UUID uuid, @NotNull String nickname) { debug("Rows affected in delete: {}", rowsChanged); return rowsChanged > 0; } catch (SQLException e) { - logger.warn("Failed to delete nickname '{}' for UUID '{}'", nickname, uuid, e); + logger().warn("Failed to delete nickname '{}' for UUID '{}'", nickname, uuid, e); return false; } } - /** * Clears the player's currently active nickname. * * @param uuid the player's UUID * @return true if a nickname was cleared, false otherwise */ - public boolean clearActiveNickname(@NotNull UUID uuid) { debug("Clearing current nickname for UUID={}", uuid); if (!playerSaveExists(uuid)) return false; @@ -340,7 +343,7 @@ public boolean clearActiveNickname(@NotNull UUID uuid) { debug("Rows affected in clear: {}", rowsAffected); return rowsAffected > 0; } catch (SQLException e) { - logger.warn("Failed to clear active nickname for UUID '{}'", uuid, e); + logger().warn("Failed to clear active nickname for UUID '{}'", uuid, e); return false; } } @@ -354,11 +357,9 @@ public boolean clearActiveNickname(@NotNull UUID uuid) { * @param normalized normalized form of the nickname * @return true if the nickname was set, false otherwise */ - public boolean setActiveNickname(@NotNull UUID uuid, @NotNull String username, @NotNull String nickname, @NotNull String normalized) { - debug("Setting active nickname '{}' (normalized '{}') for UUID={}, username={}", nickname, - normalized, uuid, username); + debug("Setting active nickname '{}' (normalized '{}') for UUID={}, username={}", nickname, normalized, uuid, username); String setQuery = "REPLACE INTO current_nicknames (uuid, nickname, normalized) VALUES (?, ?, ?)"; if (!playerSaveExists(uuid)) { if (!updatePlayerTable(uuid, username)) return false; @@ -372,21 +373,18 @@ public boolean setActiveNickname(@NotNull UUID uuid, @NotNull String username, @ debug("Rows affected setting active nickname: {}", rowsModified); return rowsModified > 0; } catch (SQLException e) { - logger.warn("Failed to set active nickname '{}' for UUID '{}'. Normalized name: {}, Username: {}", + logger().warn("Failed to set active nickname '{}' for UUID '{}'. Normalized name: {}, Username: {}", nickname, uuid, normalized, username, e); - e.printStackTrace(); return false; } } - /** * Checks if the player exists in the database. * * @param uuid the player's UUID * @return true if the player exists, false otherwise */ - public boolean playerSaveExists(@NotNull UUID uuid) { String queryString = "SELECT 1 FROM players WHERE uuid = ? LIMIT 1"; try (Connection connection = getConnection()) { @@ -394,11 +392,10 @@ public boolean playerSaveExists(@NotNull UUID uuid) { statement.setString(1, String.valueOf(uuid)); ResultSet resultSet = statement.executeQuery(); boolean exists = resultSet.next(); - debug("Check if player UUID=%s exists: %s", uuid, exists); + debug("Check if player UUID={} exists: {}", uuid, exists); return exists; } catch (SQLException e) { - logger.warn("Failed to check if player with UUID {} exists", uuid, e); - e.printStackTrace(); + logger().warn("Failed to check if player with UUID {} exists", uuid, e); return false; } } @@ -410,10 +407,9 @@ public boolean playerSaveExists(@NotNull UUID uuid) { * @param expiryTime maximum age in milliseconds (negative for no limit) * @return last login time, or {@code null} if not found */ - @Nullable public Long lastLoginOfUsername(@NotNull String username, long expiryTime) { - debug("Fetching last login for username='%s', expiry=%g", username, expiryTime); + debug("Fetching last login for username='{}', expiry={}", username, expiryTime); String queryString = "SELECT last_login FROM players WHERE last_known_name = ? AND (? < 0 OR last_login >= ?)"; try (Connection connection = getConnection()) { PreparedStatement statement = connection.prepareStatement(queryString); @@ -425,7 +421,7 @@ public Long lastLoginOfUsername(@NotNull String username, long expiryTime) { if (resultSet.next()) return resultSet.getLong("last_login"); return null; } catch (SQLException e) { - logger.warn("Failed to get last login for username: {}, expiry time: {}", username, expiryTime, e); + logger().warn("Failed to get last login for username: {}, expiry time: {}", username, expiryTime, e); return null; } } @@ -439,7 +435,7 @@ public Long lastLoginOfUsername(@NotNull String username, long expiryTime) { */ @Nullable public Long lastLoginOfUuid(@NotNull UUID uuid, long expiryTime) { - debug("Fetching last login for UUID='%s', expiry=%g", uuid, expiryTime); + debug("Fetching last login for UUID='{}', expiry={}", uuid, expiryTime); String queryString = "SELECT last_login FROM players WHERE uuid = ? AND (? < 0 OR last_login >= ?)"; try (Connection connection = getConnection()) { PreparedStatement statement = connection.prepareStatement(queryString); @@ -451,11 +447,41 @@ public Long lastLoginOfUuid(@NotNull UUID uuid, long expiryTime) { if (resultSet.next()) return resultSet.getLong("last_login"); return null; } catch (SQLException e) { - logger.warn("Failed to get last login for UUID: {}, with expiry time: {}", uuid, expiryTime, e); + logger().warn("Failed to get last login for UUID: {}, with expiry time: {}", uuid, expiryTime, e); return null; } } + /** + * Returns the last login time of a player as epoch milliseconds, parsed from the + * {@code last_login} DATETIME column stored as {@code "yyyy-MM-dd HH:mm:ss"}. + * + * @param uuid the player's UUID + * @return epoch milliseconds of last login, or {@code -1} if not found or unparseable + */ + public long getLastLoginMillis(@NotNull UUID uuid) { + debug("Fetching last_login millis for UUID={}", uuid); + String queryString = "SELECT last_login FROM players WHERE uuid = ? LIMIT 1"; + try (Connection connection = getConnection()) { + PreparedStatement statement = connection.prepareStatement(queryString); + statement.setString(1, String.valueOf(uuid)); + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + String raw = resultSet.getString("last_login"); + if (raw == null) return -1; + try { + Date parsed = DATE_FORMAT.parse(raw); + return parsed.getTime(); + } catch (ParseException e) { + logger().warn("Could not parse last_login '{}' for UUID {}", raw, uuid); + } + } + } catch (SQLException e) { + logger().warn("Failed to get last_login millis for UUID: {}", uuid, e); + } + return -1; + } + /** * Inserts or updates a player in the players table. * @@ -465,7 +491,7 @@ public Long lastLoginOfUuid(@NotNull UUID uuid, long expiryTime) { */ @SuppressWarnings("ExtractMethodRecommender") public boolean updatePlayerTable(@NotNull UUID uuid, @NotNull String username) { - debug("Saving player to players table: UUID=%s, username=%g", uuid, username); + debug("Saving player to players table: UUID={}, username={}", uuid, username); boolean isMySql = ConfigHandler.getInstance().isMySql(); String insertQuery; if (isMySql) { @@ -490,10 +516,10 @@ ON CONFLICT(uuid) DO UPDATE SET statement.setString(1, String.valueOf(uuid)); statement.setString(2, username.toLowerCase()); int rowsChanged = statement.executeUpdate(); - debug("Rows affected in savePlayerToPlayers: %d", rowsChanged); + debug("Rows affected in updatePlayerTable: {}", rowsChanged); return rowsChanged > 0; } catch (SQLException e) { - logger.warn("Failed to save player with UUID: {}, username: {}", uuid, username, e); + logger().warn("Failed to save player with UUID: {}, username: {}", uuid, username, e); return false; } } @@ -534,7 +560,7 @@ public boolean batchInsertNicknames(@NotNull List records) { for (NicknameRecord record : records) { addPlayerStatement.setString(1, record.uuid().toString()); addPlayerStatement.setString(2, record.username()); - addPlayerStatement.setString(3, new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date(record.lastLogin()))); + addPlayerStatement.setString(3, DATE_FORMAT.format(new Date(record.lastLogin()))); addPlayerStatement.addBatch(); if (record.isActiveNickname()) { @@ -565,14 +591,14 @@ public boolean batchInsertNicknames(@NotNull List records) { savedNicknameStatement.executeBatch(); connection.commit(); } catch (SQLException e) { - logger.error("Error migrating nickname records", e); + logger().error("Error migrating nickname records", e); return false; } return true; } /** - * Closes the database connection pool. Used during reloads or shutdown + * Closes the database connection pool. Used during reloads or shutdown. */ public void closeDatabase() { if (dataSource != null && !dataSource.isClosed()) dataSource.close(); @@ -584,7 +610,7 @@ public void closeDatabase() { */ public void setupConfig() { if (!ConfigHandler.getInstance().isMySql()) { - hikariConfig.setJdbcUrl("jdbc:sqlite:" + SimpleNicks.getInstance().getDataFolder() + "/simplenicks.db?foreign_keys=on"); + hikariConfig.setJdbcUrl("jdbc:sqlite:" + SimpleNicksCore.get().platform().getDataDirectory().resolve("simplenicks.db") + "?foreign_keys=on"); hikariConfig.setMaximumPoolSize(10); hikariConfig.setConnectionTestQuery("PRAGMA journal_mode = WAL;"); dataSource = new HikariDataSource(hikariConfig); @@ -612,7 +638,7 @@ public int getSchemaVersion() { return resultSet.getInt("version"); } } catch (SQLException e) { - logger.warn("Failed to get schema version", e); + logger().warn("Failed to get schema version", e); } return -1; } @@ -625,7 +651,7 @@ private void runMigrations(Connection connection, int fromVersion) { updateVersion.executeUpdate(); debug("Schema upgraded to version {}", SCHEMA_VERSION); } catch (SQLException e) { - logger.error("Error running schema migrations", e); + logger().error("Error running schema migrations", e); } } @@ -636,9 +662,7 @@ private static Connection getConnection() throws SQLException { private void debug(String message, Object... args) { if (ConfigHandler.getInstance().isDebugMode()) { - logger.info("[SQL DEBUG] {}, {}", message, args); + logger().info("[SQL DEBUG] " + message, args); } } - - } diff --git a/core/src/main/java/simplexity/simplenicks/util/ColorTag.java b/core/src/main/java/simplexity/simplenicks/util/ColorTag.java new file mode 100644 index 0000000..afd0ea3 --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/util/ColorTag.java @@ -0,0 +1,37 @@ +package simplexity.simplenicks.util; + +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; +import org.jetbrains.annotations.NotNull; + +public enum ColorTag { + HEX_COLOR("simplenick.color.basic", "op", StandardTags.color()), + GRADIENT("simplenick.color.gradient", "op", StandardTags.gradient()), + RAINBOW("simplenick.color.rainbow", "op", StandardTags.rainbow()), + RESET("simplenick.color.reset", "op", StandardTags.reset()); + + private final String permissionKey; + private final String permissionDefault; + private final TagResolver resolver; + + ColorTag(@NotNull String permissionKey, @NotNull String permissionDefault, @NotNull TagResolver resolver) { + this.permissionKey = permissionKey; + this.permissionDefault = permissionDefault; + this.resolver = resolver; + } + + @NotNull + public String getPermissionKey() { + return permissionKey; + } + + @NotNull + public String getPermissionDefault() { + return permissionDefault; + } + + @NotNull + public TagResolver getTagResolver() { + return resolver; + } +} diff --git a/core/src/main/java/simplexity/simplenicks/util/FormatTag.java b/core/src/main/java/simplexity/simplenicks/util/FormatTag.java new file mode 100644 index 0000000..44e1eb8 --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/util/FormatTag.java @@ -0,0 +1,41 @@ +package simplexity.simplenicks.util; + +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; +import org.jetbrains.annotations.NotNull; + +public enum FormatTag { + UNDERLINE("simplenick.format.underline", "op", StandardTags.decorations(TextDecoration.UNDERLINED)), + ITALIC("simplenick.format.italic", "op", StandardTags.decorations(TextDecoration.ITALIC)), + STRIKETHROUGH("simplenick.format.strikethrough", "op", StandardTags.decorations(TextDecoration.STRIKETHROUGH)), + BOLD("simplenick.format.bold", "op", StandardTags.decorations(TextDecoration.BOLD)), + OBFUSCATED("simplenick.format.obfuscated", "op", StandardTags.decorations(TextDecoration.OBFUSCATED)), + HOVER("simplenick.format.hover", "false", StandardTags.hoverEvent()), + FONT("simplenick.format.font", "false", StandardTags.font()); + + private final String permissionKey; + private final String permissionDefault; + private final TagResolver resolver; + + FormatTag(@NotNull String permissionKey, @NotNull String permissionDefault, @NotNull TagResolver resolver) { + this.permissionKey = permissionKey; + this.permissionDefault = permissionDefault; + this.resolver = resolver; + } + + @NotNull + public String getPermissionKey() { + return permissionKey; + } + + @NotNull + public String getPermissionDefault() { + return permissionDefault; + } + + @NotNull + public TagResolver getTagResolver() { + return resolver; + } +} diff --git a/core/src/main/java/simplexity/simplenicks/util/NickPermission.java b/core/src/main/java/simplexity/simplenicks/util/NickPermission.java new file mode 100644 index 0000000..4a94655 --- /dev/null +++ b/core/src/main/java/simplexity/simplenicks/util/NickPermission.java @@ -0,0 +1,44 @@ +package simplexity.simplenicks.util; + +import org.jetbrains.annotations.NotNull; + +public enum NickPermission { + NICK_ADMIN("simplenick.admin", "op"), + NICK_ADMIN_SET("simplenick.admin.set", "op"), + NICK_ADMIN_RESET("simplenick.admin.reset", "op"), + NICK_ADMIN_DELETE("simplenick.admin.delete", "op"), + NICK_ADMIN_LOOKUP("simplenick.admin.lookup", "op"), + NICK_COMMAND("simplenick.nick", "true"), + NICK_SET("simplenick.nick.set", "op"), + NICK_SAVE("simplenick.nick.save", "op"), + NICK_WHO("simplenick.nick.who", "true"), + NICK_HELP("simplenick.nick.help", "true"), + NICK_BYPASS_USERNAME("simplenick.bypass.username", "false"), + NICK_BYPASS_LENGTH("simplenick.bypass.length", "false"), + NICK_BYPASS_REGEX("simplenick.bypass.regex", "false"), + NICK_BYPASS_NICK_PROTECTION("simplenick.bypass.nick-protection", "false"), + NICK_RELOAD("simplenick.reload", "op"); + + private final String permissionKey; + private final String permissionDefault; + + NickPermission(@NotNull String permissionKey, @NotNull String permissionDefault) { + this.permissionKey = permissionKey; + this.permissionDefault = permissionDefault; + } + + @NotNull + public String getPermissionKey() { + return permissionKey; + } + + /** + * Returns the default grant level: {@code "op"}, {@code "true"}, or {@code "false"}. + * + * @return the permission default string + */ + @NotNull + public String getPermissionDefault() { + return permissionDefault; + } +} diff --git a/fabric/build.gradle b/fabric/build.gradle new file mode 100644 index 0000000..5b4572a --- /dev/null +++ b/fabric/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'net.fabricmc.fabric-loom' version '1.16-SNAPSHOT' +} + +java { + targetCompatibility = JavaVersion.VERSION_25 + sourceCompatibility = JavaVersion.VERSION_25 +} + +tasks.withType(JavaCompile).configureEach { + options.release = 25 +} + +tasks.named('jar') { + archiveFileName = "SimpleNicks-fabric-${project.version}.jar" +} + +dependencies { + minecraft "com.mojang:minecraft:${fabricMinecraftVersion}" + implementation "net.fabricmc:fabric-loader:${fabricLoaderVersion}" + implementation "net.fabricmc.fabric-api:fabric-api:${fabricApiVersion}" + + include(implementation(project(':core'))) + + include(implementation("net.kyori:adventure-platform-fabric:${adventureFabricVersion}")) + include(implementation("net.kyori:adventure-text-minimessage:${fabricMiniMessageVersion}")) + include(implementation("com.zaxxer:HikariCP:${hikariVersion}")) + include(implementation("org.xerial:sqlite-jdbc:3.47.+")) + include(implementation("org.yaml:snakeyaml:2.2")) + + // fabric-permissions-api: softdepend permissions with op-level fallback + include(implementation("me.lucko:fabric-permissions-api:${fabricPermissionsVersion}")) +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/SimpleNicksFabric.java b/fabric/src/main/java/simplexity/simplenicks/fabric/SimpleNicksFabric.java new file mode 100644 index 0000000..9a2ef53 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/SimpleNicksFabric.java @@ -0,0 +1,44 @@ +package simplexity.simplenicks.fabric; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.fabric.commands.FabricNicknameCommand; +import simplexity.simplenicks.fabric.events.FabricLeaveHandler; +import simplexity.simplenicks.fabric.events.FabricLoginHandler; +import simplexity.simplenicks.fabric.platform.FabricPlatformAdapter; +import simplexity.simplenicks.saving.SqlHandler; + +import java.nio.file.Path; + +/** + * Fabric mod entry point for SimpleNicks. + */ +public class SimpleNicksFabric implements ModInitializer { + + @Override + public void onInitialize() { + Path dataDir = FabricLoader.getInstance().getConfigDir().resolve("simplenicks"); + + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + FabricPlatformAdapter adapter = new FabricPlatformAdapter(server, dataDir); + SimpleNicksCore.initialize(adapter); + ConfigHandler.getInstance().reloadConfig(); + SqlHandler.getInstance().init(); + }); + + ServerLifecycleEvents.SERVER_STOPPED.register(server -> { + SqlHandler.getInstance().closeDatabase(); + SimpleNicksCore.shutdown(); + }); + + FabricLoginHandler.register(); + FabricLeaveHandler.register(); + + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> + dispatcher.register(FabricNicknameCommand.createCommand())); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/FabricNicknameCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/FabricNicknameCommand.java new file mode 100644 index 0000000..d5370ed --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/FabricNicknameCommand.java @@ -0,0 +1,41 @@ +package simplexity.simplenicks.fabric.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.fabric.commands.subcommands.FabricDeleteSubCommand; +import simplexity.simplenicks.fabric.commands.subcommands.FabricHelpSubCommand; +import simplexity.simplenicks.fabric.commands.subcommands.FabricReloadSubCommand; +import simplexity.simplenicks.fabric.commands.subcommands.FabricResetSubCommand; +import simplexity.simplenicks.fabric.commands.subcommands.FabricSaveSubCommand; +import simplexity.simplenicks.fabric.commands.subcommands.FabricSetSubCommand; +import simplexity.simplenicks.fabric.commands.subcommands.FabricWhoSubCommand; +import simplexity.simplenicks.fabric.commands.subcommands.admin.FabricAdminSubCommand; +import simplexity.simplenicks.util.NickPermission; + +/** + * Builds the {@code /nick} command tree for the Fabric platform. + * Registered via {@code CommandRegistrationCallback} in + * {@link simplexity.simplenicks.fabric.SimpleNicksFabric}. + */ +public class FabricNicknameCommand { + + @NotNull + public static LiteralArgumentBuilder createCommand() { + LiteralArgumentBuilder builder = Commands.literal("nick") + .requires(src -> !ConfigHandler.getInstance().isNickRequiresPermission() + || FabricPermissions.check(src, NickPermission.NICK_COMMAND)); + new FabricHelpSubCommand().subcommandTo(builder); + new FabricSetSubCommand().subcommandTo(builder); + new FabricSaveSubCommand().subcommandTo(builder); + new FabricResetSubCommand().subcommandTo(builder); + new FabricDeleteSubCommand().subcommandTo(builder); + new FabricAdminSubCommand().subcommandTo(builder); + new FabricReloadSubCommand().subcommandTo(builder); + new FabricWhoSubCommand().subcommandTo(builder); + return builder; + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricDeleteSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricDeleteSubCommand.java new file mode 100644 index 0000000..c6eceb9 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricDeleteSubCommand.java @@ -0,0 +1,67 @@ +package simplexity.simplenicks.fabric.commands.subcommands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.commands.NicknameProcessor; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.saving.Nickname; +import simplexity.simplenicks.util.NickPermission; + +public class FabricDeleteSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("delete").requires(this::canExecute) + .then(Commands.argument("nickname", StringArgumentType.greedyString()) + .suggests((ctx, builder) -> { + ServerPlayer player = ctx.getSource().getPlayer(); + if (player == null) return builder.buildFuture(); + NicknameProcessor.getInstance() + .getSavedNicknames(player.getUUID(), true) + .forEach(n -> builder.suggest(n.getNickname())); + return builder.buildFuture(); + }) + .executes(this::execute) + ) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { + ServerPlayer player = ctx.getSource().getPlayer(); + if (player == null) return Command.SINGLE_SUCCESS; + String raw = StringArgumentType.getString(ctx, "nickname"); + Nickname nickname = new Nickname(raw, NickUtils.normalizeNickname(raw)); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance() + .deleteNickname(player.getUUID(), nickname.getNickname()); + if (success) { + SimpleNicksCore.get().platform().runSync(() -> { + NickUtils.refreshDisplayName(player.getUUID()); + sendToPlayer(player, LocaleMessage.DELETE_SELF, nickname); + }); + } else { + SimpleNicksCore.get().platform().runSync(() -> + sendToPlayer(player, LocaleMessage.ERROR_DELETE_FAILURE, nickname)); + } + }); + return Command.SINGLE_SUCCESS; + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + if (source.getPlayer() == null) return false; + return permissionNotRequired() + || FabricPermissions.check(source, NickPermission.NICK_SAVE); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricHelpSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricHelpSubCommand.java new file mode 100644 index 0000000..89d5dde --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricHelpSubCommand.java @@ -0,0 +1,85 @@ +package simplexity.simplenicks.fabric.commands.subcommands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.util.NickPermission; + +public class FabricHelpSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("help").requires(this::canExecute) + .executes(this::execute) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) { + sendToSource(ctx.getSource(), buildHelpMessage(ctx.getSource())); + return Command.SINGLE_SUCCESS; + } + + @NotNull + private Component buildHelpMessage(@NotNull CommandSourceStack source) { + ConfigHandler config = ConfigHandler.getInstance(); + Component help = line(LocaleMessage.HELP_HEADER); + + ServerPlayer player = source.getPlayer(); + if (player != null) { + boolean permNotRequired = !config.isNickRequiresPermission(); + + if (permNotRequired || FabricPermissions.check(source, NickPermission.NICK_SET)) { + help = help.appendNewline().append(line(LocaleMessage.HELP_SET)); + help = help.appendNewline().append(line(LocaleMessage.HELP_RESET)); + } + + if (permNotRequired || FabricPermissions.check(source, NickPermission.NICK_SAVE)) { + help = help.appendNewline().append(line(LocaleMessage.HELP_SAVE)); + help = help.appendNewline().append(line(LocaleMessage.HELP_DELETE)); + } + } + + if (!config.isWhoRequiresPermission() + || FabricPermissions.check(source, NickPermission.NICK_WHO)) { + help = help.appendNewline().append(line(LocaleMessage.HELP_WHO)); + } + + if (FabricPermissions.check(source, NickPermission.NICK_ADMIN_SET)) { + help = help.appendNewline().append(line(LocaleMessage.HELP_ADMIN_SET)); + } + if (FabricPermissions.check(source, NickPermission.NICK_ADMIN_RESET)) { + help = help.appendNewline().append(line(LocaleMessage.HELP_ADMIN_RESET)); + } + if (FabricPermissions.check(source, NickPermission.NICK_ADMIN_DELETE)) { + help = help.appendNewline().append(line(LocaleMessage.HELP_ADMIN_DELETE)); + } + if (FabricPermissions.check(source, NickPermission.NICK_ADMIN_LOOKUP)) { + help = help.appendNewline().append(line(LocaleMessage.HELP_ADMIN_LOOKUP)); + } + if (FabricPermissions.check(source, NickPermission.NICK_RELOAD)) { + help = help.appendNewline().append(line(LocaleMessage.HELP_RELOAD)); + } + + return help; + } + + @NotNull + private static Component line(@NotNull LocaleMessage msg) { + return SimpleNicksCore.get().miniMessage().deserialize(msg.getMessage()); + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + return FabricPermissions.check(source, NickPermission.NICK_HELP); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricReloadSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricReloadSubCommand.java new file mode 100644 index 0000000..dc36234 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricReloadSubCommand.java @@ -0,0 +1,41 @@ +package simplexity.simplenicks.fabric.commands.subcommands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.saving.SqlHandler; +import simplexity.simplenicks.util.NickPermission; + +public class FabricReloadSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("reload").requires(this::canExecute) + .executes(this::execute) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) { + ConfigHandler.getInstance().reloadConfig(); + SqlHandler.getInstance().closeDatabase(); + SqlHandler.getInstance().init(); + SimpleNicksCore.get().platform().getOnlinePlayers() + .forEach(NickUtils::refreshDisplayName); + sendFeedback(ctx.getSource(), LocaleMessage.CONFIG_RELOADED, null); + return Command.SINGLE_SUCCESS; + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + return FabricPermissions.check(source, NickPermission.NICK_RELOAD); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricResetSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricResetSubCommand.java new file mode 100644 index 0000000..7fdf7e8 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricResetSubCommand.java @@ -0,0 +1,51 @@ +package simplexity.simplenicks.fabric.commands.subcommands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.commands.NicknameProcessor; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.util.NickPermission; + +public class FabricResetSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("reset").requires(this::canExecute) + .executes(this::execute) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) { + ServerPlayer player = ctx.getSource().getPlayer(); + if (player == null) return Command.SINGLE_SUCCESS; + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance().resetNickname(player.getUUID()); + if (success) { + SimpleNicksCore.get().platform().runSync(() -> { + NickUtils.refreshDisplayName(player.getUUID()); + sendToPlayer(player, LocaleMessage.RESET_SELF, null); + }); + } else { + SimpleNicksCore.get().platform().runSync(() -> + sendToPlayer(player, LocaleMessage.ERROR_RESET_FAILURE, null)); + } + }); + return Command.SINGLE_SUCCESS; + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + if (source.getPlayer() == null) return false; + return permissionNotRequired() + || FabricPermissions.check(source, NickPermission.NICK_SET); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricSaveSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricSaveSubCommand.java new file mode 100644 index 0000000..a10f2e3 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricSaveSubCommand.java @@ -0,0 +1,101 @@ +package simplexity.simplenicks.fabric.commands.subcommands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.commands.NicknameProcessor; +import simplexity.simplenicks.commands.subcommands.Exceptions; +import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.fabric.platform.FabricSenderContext; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.saving.Nickname; +import simplexity.simplenicks.util.NickPermission; + +public class FabricSaveSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("save").requires(this::canExecute) + .executes(this::execute) + .then(Commands.argument("nickname", StringArgumentType.greedyString()) + .suggests((ctx, builder) -> { + ServerPlayer player = ctx.getSource().getPlayer(); + if (player == null) return builder.buildFuture(); + NicknameProcessor.getInstance() + .getSavedNicknames(player.getUUID(), true) + .forEach(n -> builder.suggest(n.getNickname())); + return builder.buildFuture(); + }) + .executes(this::executeWithArgument) + ) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { + ServerPlayer player = ctx.getSource().getPlayer(); + if (player == null) return Command.SINGLE_SUCCESS; + Nickname nickname = NicknameProcessor.getInstance().getCurrentNickname(player.getUUID(), true); + if (nickname == null) throw Exceptions.cannotSave(); + checkSaveSlots(player); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean saved = NicknameProcessor.getInstance() + .saveNickname(player.getUUID(), player.getGameProfile().name(), nickname.getNickname()); + if (saved) { + SimpleNicksCore.get().platform().runSync(() -> { + NickUtils.refreshDisplayName(player.getUUID()); + sendToPlayer(player, LocaleMessage.SAVE_NICK, nickname); + }); + } else { + SimpleNicksCore.get().platform().runSync(() -> + sendToPlayer(player, LocaleMessage.ERROR_SAVE_FAILURE, nickname)); + } + }); + return Command.SINGLE_SUCCESS; + } + + private int executeWithArgument(@NotNull CommandContext ctx) throws CommandSyntaxException { + ServerPlayer player = ctx.getSource().getPlayer(); + if (player == null) return Command.SINGLE_SUCCESS; + String raw = StringArgumentType.getString(ctx, "nickname"); + Nickname nickname = new Nickname(raw, NickUtils.normalizeNickname(raw)); + NickUtils.nicknameChecks(new FabricSenderContext(ctx.getSource()), nickname); + checkSaveSlots(player); + if (NicknameProcessor.getInstance().playerAlreadySavedThis(player.getUUID(), nickname.getNickname())) { + throw Exceptions.alreadySaved(); + } + SimpleNicksCore.get().platform().runAsync(() -> { + boolean saved = NicknameProcessor.getInstance() + .saveNickname(player.getUUID(), player.getGameProfile().name(), nickname.getNickname()); + if (saved) { + SimpleNicksCore.get().platform().runSync(() -> + sendToPlayer(player, LocaleMessage.SAVE_NICK, nickname)); + } else { + SimpleNicksCore.get().platform().runSync(() -> + sendToPlayer(player, LocaleMessage.ERROR_SAVE_FAILURE, nickname)); + } + }); + return Command.SINGLE_SUCCESS; + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + if (source.getPlayer() == null) return false; + return permissionNotRequired() + || FabricPermissions.check(source, NickPermission.NICK_SAVE); + } + + private void checkSaveSlots(@NotNull ServerPlayer player) throws CommandSyntaxException { + int count = NicknameProcessor.getInstance().getCurrentSavedNickCount(player.getUUID(), true); + if (count >= ConfigHandler.getInstance().getMaxSaves()) throw Exceptions.tooManySavedNames(); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricSetSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricSetSubCommand.java new file mode 100644 index 0000000..5dc2dd6 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricSetSubCommand.java @@ -0,0 +1,73 @@ +package simplexity.simplenicks.fabric.commands.subcommands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.commands.NicknameProcessor; +import simplexity.simplenicks.commands.subcommands.Exceptions; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.fabric.platform.FabricSenderContext; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.saving.Nickname; +import simplexity.simplenicks.util.NickPermission; + +public class FabricSetSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("set").requires(this::canExecute) + .then(Commands.argument("nickname", StringArgumentType.greedyString()) + .suggests((ctx, builder) -> { + ServerPlayer player = ctx.getSource().getPlayer(); + if (player == null) return builder.buildFuture(); + NicknameProcessor.getInstance() + .getSavedNicknames(player.getUUID(), true) + .forEach(n -> builder.suggest(n.getNickname())); + return builder.buildFuture(); + }) + .executes(this::execute) + ) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { + ServerPlayer player = ctx.getSource().getPlayer(); + if (player == null) throw Exceptions.invalidPlayerSpecified(null); + String raw = StringArgumentType.getString(ctx, "nickname"); + Nickname nickname = new Nickname(raw, NickUtils.normalizeNickname(raw)); + if (nickname.getNormalizedNickname().isEmpty()) throw Exceptions.nickIsNull(); + FabricSenderContext sender = new FabricSenderContext(ctx.getSource()); + if (!NickUtils.isValidTags(sender, nickname.getNickname())) throw Exceptions.tagsNotPermitted(); + NickUtils.nicknameChecks(sender, nickname); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance() + .setNickname(player.getUUID(), player.getGameProfile().name(), nickname.getNickname()); + if (success) { + SimpleNicksCore.get().platform().runSync(() -> { + NickUtils.refreshDisplayName(player.getUUID()); + sendToPlayer(player, LocaleMessage.SET_SELF, nickname); + }); + } else { + SimpleNicksCore.get().platform().runSync(() -> + sendToPlayer(player, LocaleMessage.ERROR_SET_FAILURE, nickname)); + } + }); + return Command.SINGLE_SUCCESS; + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + if (source.getPlayer() == null) return false; + return permissionNotRequired() + || FabricPermissions.check(source, NickPermission.NICK_SET); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricSubCommand.java new file mode 100644 index 0000000..689ab7a --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricSubCommand.java @@ -0,0 +1,110 @@ +package simplexity.simplenicks.fabric.commands.subcommands; + +import net.minecraft.server.players.NameAndId; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.fabric.platform.FabricPlatformAdapter; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.saving.Cache; +import simplexity.simplenicks.saving.Nickname; + +import java.util.UUID; + +/** + * Interface shared by all Fabric /nick subcommands, mirroring the Paper {@code SubCommand} + * but typed to Fabric's {@link CommandSourceStack}. + */ +public interface FabricSubCommand { + + void subcommandTo(@NotNull LiteralArgumentBuilder parent); + + int execute(@NotNull CommandContext ctx) throws CommandSyntaxException; + + boolean canExecute(@NotNull CommandSourceStack source); + + default boolean permissionNotRequired() { + return !ConfigHandler.getInstance().isNickRequiresPermission(); + } + + default void refreshName(@NotNull UUID uuid, boolean isOnline) { + if (!isOnline) return; + SimpleNicksCore.get().platform().runSync(() -> NickUtils.refreshDisplayName(uuid)); + } + + /** + * Sends a MiniMessage-rendered feedback message to the command source. + */ + default void sendFeedback(@NotNull CommandSourceStack source, + @Nullable LocaleMessage localeMessage, + @Nullable Nickname nickname) { + if (nickname == null) nickname = new Nickname("", ""); + if (localeMessage == null || localeMessage.getMessage().isEmpty()) return; + Component message = SimpleNicksCore.get().miniMessage().deserialize( + localeMessage.getMessage(), Placeholder.parsed("value", nickname.getNickname())); + sendToSource(source, message); + } + + /** + * Sends a MiniMessage-rendered message to a specific {@link ServerPlayer}. + */ + default void sendToPlayer(@NotNull ServerPlayer player, @Nullable LocaleMessage localeMessage, + @Nullable Nickname nickname) { + if (nickname == null) nickname = new Nickname("", ""); + if (localeMessage == null || localeMessage.getMessage().isEmpty()) return; + Component message = SimpleNicksCore.get().miniMessage().deserialize( + localeMessage.getMessage(), Placeholder.parsed("value", nickname.getNickname())); + FabricPlatformAdapter adapter = (FabricPlatformAdapter) SimpleNicksCore.get().platform(); + player.sendSystemMessage(adapter.getAudiences().asNative(message)); + } + + /** + * Sends an Adventure component to the command source using NMS conversion. + */ + default void sendToSource(@NotNull CommandSourceStack source, @NotNull Component message) { + FabricPlatformAdapter adapter = (FabricPlatformAdapter) SimpleNicksCore.get().platform(); + net.minecraft.network.chat.Component nms = adapter.getAudiences().asNative(message); + source.sendSuccess(() -> nms, false); + } + + /** + * Sends an already-built Adventure component directly to a {@link ServerPlayer}. + */ + default void sendComponentToPlayer(@NotNull ServerPlayer player, @NotNull Component message) { + FabricPlatformAdapter adapter = (FabricPlatformAdapter) SimpleNicksCore.get().platform(); + player.sendSystemMessage(adapter.getAudiences().asNative(message)); + } + + /** + * Builds the admin feedback message that includes the initiator name, target name, and value. + * Mirrors the Paper {@code parseAdminMessage} helper. + */ + default @NotNull Component parseAdminMessage(@NotNull String template, + @NotNull String value, + @NotNull CommandSourceStack initiatorSource, + @NotNull NameAndId target) { + Component initiatorName; + ServerPlayer initiatorPlayer = initiatorSource.getPlayer(); + if (initiatorPlayer != null) { + Nickname nick = Cache.getInstance().getActiveNickname(initiatorPlayer.getUUID()); + String nameStr = nick != null ? nick.getNickname() : initiatorPlayer.getGameProfile().name(); + initiatorName = SimpleNicksCore.get().miniMessage().deserialize(nameStr); + } else { + initiatorName = SimpleNicksCore.get().miniMessage() + .deserialize(LocaleMessage.SERVER_DISPLAY_NAME.getMessage()); + } + return SimpleNicksCore.get().miniMessage().deserialize(template, + Placeholder.parsed("value", value), + Placeholder.component("initiator", initiatorName), + Placeholder.parsed("target", target.name())); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricWhoSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricWhoSubCommand.java new file mode 100644 index 0000000..8ec65d2 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/FabricWhoSubCommand.java @@ -0,0 +1,89 @@ +package simplexity.simplenicks.fabric.commands.subcommands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.config.MessageUtils; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.platform.PlayerInfo; +import simplexity.simplenicks.saving.Nickname; +import simplexity.simplenicks.util.NickPermission; + +import java.util.List; + +public class FabricWhoSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("who").requires(this::canExecute) + .then(Commands.argument("nickname", StringArgumentType.greedyString()) + .suggests((ctx, builder) -> { + SimpleNicksCore.get().platform().getOnlinePlayers().forEach(uuid -> { + Nickname nick = simplexity.simplenicks.saving.Cache.getInstance().getActiveNickname(uuid); + if (nick != null) builder.suggest(nick.getNormalizedNickname()); + }); + return builder.buildFuture(); + }) + .executes(this::execute) + ) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) { + CommandSourceStack source = ctx.getSource(); + String raw = StringArgumentType.getString(ctx, "nickname"); + String normalized = NickUtils.normalizeNickname(raw); + SimpleNicksCore.get().platform().runAsync(() -> { + List players = NickUtils.getPlayersByNickname(normalized); + if (players.isEmpty()) { + sendFeedback(source, LocaleMessage.ERROR_NO_PLAYERS_WITH_THIS_NAME, null); + return; + } + Nickname nickname = new Nickname(raw, normalized); + sendToSource(source, buildWhoMessage(nickname, players)); + }); + return Command.SINGLE_SUCCESS; + } + + @NotNull + private Component buildWhoMessage(@NotNull Nickname nickname, @NotNull List players) { + Component header = SimpleNicksCore.get().miniMessage().deserialize( + LocaleMessage.WHO_HEADER.getMessage(), + Placeholder.parsed("value", nickname.getNormalizedNickname())); + + if (players.isEmpty()) { + return header.append(SimpleNicksCore.get().miniMessage() + .deserialize(LocaleMessage.INSERT_NONE.getMessage())); + } + + Component result = header; + long now = System.currentTimeMillis(); + for (PlayerInfo player : players) { + long timeDiffSeconds = player.lastLoginMillis() > 0 + ? (now - player.lastLoginMillis()) / 1000 + : 0; + result = result.append(SimpleNicksCore.get().miniMessage().deserialize( + LocaleMessage.WHO_INFO.getMessage(), + Placeholder.parsed("name", player.username()), + MessageUtils.getTimeFormat(timeDiffSeconds))); + } + return result; + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + return !ConfigHandler.getInstance().isWhoRequiresPermission() + || FabricPermissions.check(source, NickPermission.NICK_WHO); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminDeleteSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminDeleteSubCommand.java new file mode 100644 index 0000000..a3dc21d --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminDeleteSubCommand.java @@ -0,0 +1,95 @@ +package simplexity.simplenicks.fabric.commands.subcommands.admin; + +import net.minecraft.server.players.NameAndId; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.commands.NicknameProcessor; +import simplexity.simplenicks.commands.subcommands.Exceptions; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.fabric.commands.subcommands.FabricSubCommand; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.saving.Nickname; +import simplexity.simplenicks.util.NickPermission; + +import java.util.Collection; + +public class FabricAdminDeleteSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("delete").requires(this::canExecute) + .then(Commands.argument("player", GameProfileArgument.gameProfile()) + .suggests((ctx, builder) -> { + ctx.getSource().getServer().getPlayerList().getPlayers() + .forEach(p -> builder.suggest(p.getGameProfile().name())); + return builder.buildFuture(); + }) + .then(Commands.argument("nickname", StringArgumentType.greedyString()) + .suggests((ctx, builder) -> { + Collection profiles; + try { + profiles = GameProfileArgument.getGameProfiles(ctx, "player"); + } catch (CommandSyntaxException e) { + return builder.buildFuture(); + } + profiles.stream().findFirst().ifPresent(profile -> { + boolean online = ctx.getSource().getServer() + .getPlayerList().getPlayer(profile.id()) != null; + NicknameProcessor.getInstance() + .getSavedNicknames(profile.id(), online) + .forEach(n -> builder.suggest(n.getNickname())); + }); + return builder.buildFuture(); + }) + .executes(this::execute) + ) + ) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { + Collection profiles = GameProfileArgument.getGameProfiles(ctx, "player"); + NameAndId target = profiles.stream().findFirst().orElseThrow( + () -> Exceptions.invalidPlayerSpecified(null)); + String raw = StringArgumentType.getString(ctx, "nickname"); + Nickname nickname = new Nickname(raw, NickUtils.normalizeNickname(raw)); + CommandSourceStack source = ctx.getSource(); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance() + .deleteNickname(target.id(), nickname.getNickname()); + if (success) { + SimpleNicksCore.get().platform().runSync(() -> { + ServerPlayer onlineTarget = source.getServer().getPlayerList().getPlayer(target.id()); + if (onlineTarget != null) { + NickUtils.refreshDisplayName(target.id()); + sendComponentToPlayer(onlineTarget, parseAdminMessage( + LocaleMessage.DELETED_BY_INITIATOR.getMessage(), + nickname.getNickname(), source, target)); + } + sendToSource(source, parseAdminMessage(LocaleMessage.DELETED_TARGET.getMessage(), + nickname.getNickname(), source, target)); + }); + } else { + SimpleNicksCore.get().platform().runSync(() -> + sendFeedback(source, LocaleMessage.ERROR_DELETE_FAILURE, nickname)); + } + }); + return Command.SINGLE_SUCCESS; + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + return FabricPermissions.check(source, NickPermission.NICK_ADMIN_DELETE); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminLookupSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminLookupSubCommand.java new file mode 100644 index 0000000..4d5a007 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminLookupSubCommand.java @@ -0,0 +1,86 @@ +package simplexity.simplenicks.fabric.commands.subcommands.admin; + +import net.minecraft.server.players.NameAndId; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.commands.NicknameProcessor; +import simplexity.simplenicks.commands.subcommands.Exceptions; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.config.MessageUtils; +import simplexity.simplenicks.fabric.commands.subcommands.FabricSubCommand; +import simplexity.simplenicks.saving.Nickname; +import simplexity.simplenicks.util.NickPermission; + +import java.util.Collection; +import java.util.List; + +public class FabricAdminLookupSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("lookup").requires(this::canExecute) + .then(Commands.argument("player", GameProfileArgument.gameProfile()) + .suggests((ctx, builder) -> { + ctx.getSource().getServer().getPlayerList().getPlayers() + .forEach(p -> builder.suggest(p.getGameProfile().name())); + return builder.buildFuture(); + }) + .executes(this::execute) + ) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { + Collection profiles = GameProfileArgument.getGameProfiles(ctx, "player"); + NameAndId target = profiles.stream().findFirst().orElseThrow( + () -> Exceptions.invalidPlayerSpecified(null)); + CommandSourceStack source = ctx.getSource(); + boolean isOnline = source.getServer().getPlayerList().getPlayer(target.id()) != null; + SimpleNicksCore.get().platform().runAsync(() -> { + Nickname current = NicknameProcessor.getInstance().getCurrentNickname(target.id(), isOnline); + List saved = NicknameProcessor.getInstance().getSavedNicknames(target.id(), isOnline); + sendToSource(source, buildLookupMessage(target.name(), current, saved)); + }); + return Command.SINGLE_SUCCESS; + } + + @NotNull + private Component buildLookupMessage(@NotNull String username, + @Nullable Nickname current, + @Nullable List saved) { + String nickname; + if (current == null) { + if (saved == null || saved.isEmpty()) { + return SimpleNicksCore.get().miniMessage() + .deserialize(LocaleMessage.ERROR_NO_PLAYERS_WITH_THIS_NAME.getMessage()); + } + nickname = LocaleMessage.INSERT_NONE.getMessage(); + } else { + nickname = current.getNickname(); + } + String template = LocaleMessage.LOOKUP_HEADER.getMessage() + + LocaleMessage.LOOKUP_CURRENT.getMessage() + + LocaleMessage.LOOKUP_SAVED.getMessage(); + return SimpleNicksCore.get().miniMessage().deserialize(template, + Placeholder.unparsed("username", username), + Placeholder.parsed("name", nickname), + MessageUtils.savedNickListResolver(saved)); + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + return FabricPermissions.check(source, NickPermission.NICK_ADMIN_LOOKUP); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminResetSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminResetSubCommand.java new file mode 100644 index 0000000..2323c0b --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminResetSubCommand.java @@ -0,0 +1,71 @@ +package simplexity.simplenicks.fabric.commands.subcommands.admin; + +import net.minecraft.server.players.NameAndId; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.commands.NicknameProcessor; +import simplexity.simplenicks.commands.subcommands.Exceptions; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.fabric.commands.subcommands.FabricSubCommand; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.util.NickPermission; + +import java.util.Collection; + +public class FabricAdminResetSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("reset").requires(this::canExecute) + .then(Commands.argument("player", GameProfileArgument.gameProfile()) + .suggests((ctx, builder) -> { + ctx.getSource().getServer().getPlayerList().getPlayers() + .forEach(p -> builder.suggest(p.getGameProfile().name())); + return builder.buildFuture(); + }) + .executes(this::execute) + ) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { + Collection profiles = GameProfileArgument.getGameProfiles(ctx, "player"); + NameAndId target = profiles.stream().findFirst().orElseThrow( + () -> Exceptions.invalidPlayerSpecified(null)); + CommandSourceStack source = ctx.getSource(); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance().resetNickname(target.id()); + if (success) { + SimpleNicksCore.get().platform().runSync(() -> { + ServerPlayer onlineTarget = source.getServer().getPlayerList().getPlayer(target.id()); + if (onlineTarget != null) { + NickUtils.refreshDisplayName(target.id()); + sendComponentToPlayer(onlineTarget, parseAdminMessage( + LocaleMessage.RESET_BY_INITIATOR.getMessage(), "", source, target)); + } + sendToSource(source, parseAdminMessage( + LocaleMessage.RESET_TARGET.getMessage(), "", source, target)); + }); + } else { + SimpleNicksCore.get().platform().runSync(() -> + sendFeedback(source, LocaleMessage.ERROR_RESET_FAILURE, null)); + } + }); + return Command.SINGLE_SUCCESS; + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + return FabricPermissions.check(source, NickPermission.NICK_ADMIN_RESET); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminSetSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminSetSubCommand.java new file mode 100644 index 0000000..a07d2cf --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminSetSubCommand.java @@ -0,0 +1,84 @@ +package simplexity.simplenicks.fabric.commands.subcommands.admin; + +import net.minecraft.server.players.NameAndId; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.GameProfileArgument; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.commands.NicknameProcessor; +import simplexity.simplenicks.commands.subcommands.Exceptions; +import simplexity.simplenicks.config.LocaleMessage; +import simplexity.simplenicks.fabric.commands.subcommands.FabricSubCommand; +import simplexity.simplenicks.fabric.platform.FabricSenderContext; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.saving.Nickname; +import simplexity.simplenicks.util.NickPermission; + +import java.util.Collection; + +public class FabricAdminSetSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + parent.then(Commands.literal("set").requires(this::canExecute) + .then(Commands.argument("player", GameProfileArgument.gameProfile()) + .suggests((ctx, builder) -> { + ctx.getSource().getServer().getPlayerList().getPlayers() + .forEach(p -> builder.suggest(p.getGameProfile().name())); + return builder.buildFuture(); + }) + .then(Commands.argument("nickname", StringArgumentType.greedyString()) + .executes(this::execute) + ) + ) + ); + } + + @Override + public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { + Collection profiles = GameProfileArgument.getGameProfiles(ctx, "player"); + NameAndId target = profiles.stream().findFirst().orElseThrow( + () -> Exceptions.invalidPlayerSpecified(null)); + String raw = StringArgumentType.getString(ctx, "nickname"); + Nickname nickname = new Nickname(raw, NickUtils.normalizeNickname(raw)); + FabricSenderContext senderCtx = new FabricSenderContext(ctx.getSource()); + if (!NickUtils.isValidTags(senderCtx, nickname.getNickname())) throw Exceptions.tagsNotPermitted(); + NickUtils.nicknameChecks(senderCtx, nickname); + CommandSourceStack source = ctx.getSource(); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance() + .setNickname(target.id(), target.name(), nickname.getNickname()); + if (success) { + SimpleNicksCore.get().platform().runSync(() -> { + ServerPlayer onlineTarget = source.getServer().getPlayerList().getPlayer(target.id()); + if (onlineTarget != null) { + NickUtils.refreshDisplayName(target.id()); + sendComponentToPlayer(onlineTarget, parseAdminMessage( + LocaleMessage.SET_BY_INITIATOR.getMessage(), + nickname.getNickname(), source, target)); + } + sendToSource(source, parseAdminMessage(LocaleMessage.SET_TARGET.getMessage(), + nickname.getNickname(), source, target)); + }); + } else { + SimpleNicksCore.get().platform().runSync(() -> + sendFeedback(source, LocaleMessage.ERROR_SET_FAILURE, nickname)); + } + }); + return Command.SINGLE_SUCCESS; + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + return FabricPermissions.check(source, NickPermission.NICK_ADMIN_SET); + } + +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminSubCommand.java b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminSubCommand.java new file mode 100644 index 0000000..83d97f4 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/commands/subcommands/admin/FabricAdminSubCommand.java @@ -0,0 +1,34 @@ +package simplexity.simplenicks.fabric.commands.subcommands.admin; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.fabric.commands.subcommands.FabricSubCommand; +import simplexity.simplenicks.util.NickPermission; + +public class FabricAdminSubCommand implements FabricSubCommand { + + @Override + public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { + LiteralArgumentBuilder admin = + Commands.literal("admin").requires(this::canExecute); + new FabricAdminSetSubCommand().subcommandTo(admin); + new FabricAdminResetSubCommand().subcommandTo(admin); + new FabricAdminLookupSubCommand().subcommandTo(admin); + new FabricAdminDeleteSubCommand().subcommandTo(admin); + parent.then(admin); + } + + @Override + public int execute(@NotNull CommandContext ctx) { + throw new UnsupportedOperationException("FabricAdminSubCommand::execute should never be called directly."); + } + + @Override + public boolean canExecute(@NotNull CommandSourceStack source) { + return FabricPermissions.check(source, NickPermission.NICK_ADMIN); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/events/FabricLeaveHandler.java b/fabric/src/main/java/simplexity/simplenicks/fabric/events/FabricLeaveHandler.java new file mode 100644 index 0000000..04586e2 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/events/FabricLeaveHandler.java @@ -0,0 +1,25 @@ +package simplexity.simplenicks.fabric.events; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.fabric.platform.FabricPlatformAdapter; +import simplexity.simplenicks.fabric.storage.FabricNicknameStorage; +import simplexity.simplenicks.saving.Cache; + +import java.util.UUID; + +/** + * Handles player disconnect events on Fabric, mirroring {@code LeaveListener} on Paper. + */ +public class FabricLeaveHandler { + + public static void register() { + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { + UUID uuid = handler.player.getUUID(); + Cache.getInstance().removePlayerFromCache(uuid); + FabricNicknameStorage.clearDisplayName(uuid); + FabricNicknameStorage.clearTabName(uuid); + ((FabricPlatformAdapter) SimpleNicksCore.get().platform()).markOffline(uuid); + }); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/events/FabricLoginHandler.java b/fabric/src/main/java/simplexity/simplenicks/fabric/events/FabricLoginHandler.java new file mode 100644 index 0000000..74ffc05 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/events/FabricLoginHandler.java @@ -0,0 +1,66 @@ +package simplexity.simplenicks.fabric.events; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.config.ConfigHandler; +import simplexity.simplenicks.fabric.platform.FabricPlatformAdapter; +import simplexity.simplenicks.fabric.storage.FabricNicknameStorage; +import simplexity.simplenicks.saving.Cache; +import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.saving.Cache; +import simplexity.simplenicks.saving.Nickname; +import simplexity.simplenicks.saving.SqlHandler; + +import java.util.UUID; + +/** + * Handles player join events on Fabric, mirroring {@code LoginListener} on Paper. + */ +public class FabricLoginHandler { + + public static void register() { + ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { + ServerGamePacketListenerImpl listener = handler; + UUID playerUuid = listener.player.getUUID(); + String username = listener.player.getGameProfile().name(); + // Mark online immediately (main thread, before async task) so Cache.playerIsOffline() + // returns false when called from the virtual thread. + ((FabricPlatformAdapter) SimpleNicksCore.get().platform()).markOnline(playerUuid); + SimpleNicksCore.get().platform().runAsync(() -> { + SqlHandler.getInstance().updatePlayerTable(playerUuid, username); + Cache.getInstance().loadCurrentNickname(playerUuid); + Cache.getInstance().loadSavedNicknames(playerUuid); + // Write to FabricNicknameStorage immediately on this thread. + // ConcurrentHashMap writes and pure Adventure→NMS conversion are both + // thread-safe, so any chat message that arrives before the runSync below + // executes will already see the correct display name via the mixin. + preloadNicknameStorage(playerUuid); + // Full refresh on the main thread: safe player-list access + tab-list packet. + SimpleNicksCore.get().platform().runSync(() -> NickUtils.refreshDisplayName(playerUuid)); + }); + }); + } + + /** + * Writes the player's active nickname directly into {@link FabricNicknameStorage} without + * going through {@code NickUtils.refreshDisplayName}, which requires the main thread for its + * {@code isPlayerOnline} check. Safe to call from any thread. + */ + private static void preloadNicknameStorage(@NotNull UUID playerUuid) { + Nickname nick = Cache.getInstance().getActiveNickname(playerUuid); + if (nick == null) return; + FabricPlatformAdapter adapter = (FabricPlatformAdapter) SimpleNicksCore.get().platform(); + MiniMessage mm = SimpleNicksCore.get().miniMessage(); + Component displayName = mm.deserialize(ConfigHandler.getInstance().getNickPrefix()) + .append(mm.deserialize(nick.getNickname())); + FabricNicknameStorage.setDisplayName(playerUuid, adapter.getAudiences().asNative(displayName)); + if (ConfigHandler.getInstance().shouldNickTablist()) { + FabricNicknameStorage.setTabName(playerUuid, + adapter.getAudiences().asNative(mm.deserialize(nick.getNickname()))); + } + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/mixin/PlayerMixin.java b/fabric/src/main/java/simplexity/simplenicks/fabric/mixin/PlayerMixin.java new file mode 100644 index 0000000..04adde3 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/mixin/PlayerMixin.java @@ -0,0 +1,29 @@ +package simplexity.simplenicks.fabric.mixin; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import simplexity.simplenicks.fabric.storage.FabricNicknameStorage; + +/** + * Intercepts {@link Player#getDisplayName()} so that SimpleNicks nicknames appear + * in chat-formatted messages. Only modifies the return value for {@link ServerPlayer} + * instances; client-side players are unaffected. + */ +@Mixin(Player.class) +public abstract class PlayerMixin { + + @Inject(method = "getDisplayName()Lnet/minecraft/network/chat/Component;", + at = @At("HEAD"), cancellable = true) + private void simplenicks$getDisplayName(CallbackInfoReturnable cir) { + if (!((Object) this instanceof ServerPlayer sp)) return; + Component stored = FabricNicknameStorage.getDisplayName(sp.getUUID()); + if (stored != null) { + cir.setReturnValue(stored); + } + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/mixin/ServerLoginPacketListenerAccessor.java b/fabric/src/main/java/simplexity/simplenicks/fabric/mixin/ServerLoginPacketListenerAccessor.java new file mode 100644 index 0000000..47bf7d0 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/mixin/ServerLoginPacketListenerAccessor.java @@ -0,0 +1,14 @@ +package simplexity.simplenicks.fabric.mixin; + +import com.mojang.authlib.GameProfile; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ServerLoginPacketListenerImpl.class) +public interface ServerLoginPacketListenerAccessor { + + @Accessor("authenticatedProfile") + @Nullable GameProfile getAuthenticatedProfile(); +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/mixin/ServerPlayerMixin.java b/fabric/src/main/java/simplexity/simplenicks/fabric/mixin/ServerPlayerMixin.java new file mode 100644 index 0000000..c097093 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/mixin/ServerPlayerMixin.java @@ -0,0 +1,31 @@ +package simplexity.simplenicks.fabric.mixin; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import simplexity.simplenicks.fabric.storage.FabricNicknameStorage; + +/** + * Intercepts {@link ServerPlayer#getTabListDisplayName()} so that SimpleNicks + * nicknames appear in the tab list without manual packet construction. + *

+ * Chat display name is handled in {@link PlayerMixin} since {@code getDisplayName()} + * is defined on {@link net.minecraft.world.entity.player.Player}, not {@link ServerPlayer}. + *

+ */ +@Mixin(ServerPlayer.class) +public abstract class ServerPlayerMixin { + + @Inject(method = "getTabListDisplayName()Lnet/minecraft/network/chat/Component;", + at = @At("HEAD"), cancellable = true) + private void simplenicks$getTabListDisplayName(CallbackInfoReturnable<@Nullable Component> cir) { + Component stored = FabricNicknameStorage.getTabName(((ServerPlayer) (Object) this).getUUID()); + if (stored != null) { + cir.setReturnValue(stored); + } + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/platform/FabricConfigProvider.java b/fabric/src/main/java/simplexity/simplenicks/fabric/platform/FabricConfigProvider.java new file mode 100644 index 0000000..a492a58 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/platform/FabricConfigProvider.java @@ -0,0 +1,139 @@ +package simplexity.simplenicks.fabric.platform; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import simplexity.simplenicks.platform.ConfigProvider; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * {@link ConfigProvider} backed by a plain YAML file via SnakeYAML. + * Used for both {@code config.yml} and {@code locale.yml} on Fabric. + */ +@SuppressWarnings("unchecked") +public class FabricConfigProvider implements ConfigProvider { + + private static final Logger LOGGER = LoggerFactory.getLogger(FabricConfigProvider.class); + + private final Path file; + private Map data = new LinkedHashMap<>(); + private final Yaml yaml; + + public FabricConfigProvider(@NotNull Path file) { + this.file = file; + DumperOptions opts = new DumperOptions(); + opts.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + opts.setPrettyFlow(true); + this.yaml = new Yaml(opts); + } + + @Override + public void reload() { + try { + Files.createDirectories(file.getParent()); + if (!Files.exists(file) || Files.size(file) == 0) { + copyDefaultResource(); + } + try (FileReader reader = new FileReader(file.toFile())) { + Map loaded = yaml.load(reader); + data = loaded != null ? loaded : new LinkedHashMap<>(); + } + } catch (IOException e) { + LOGGER.warn("Failed to load config file '{}': {}", file, e.getMessage(), e); + } + } + + /** + * Copies the bundled default resource matching this file's name into the config directory. + * Only called when the destination file is absent or empty. + */ + private void copyDefaultResource() throws IOException { + String resourceName = file.getFileName().toString(); + try (InputStream in = FabricConfigProvider.class.getResourceAsStream("/" + resourceName)) { + if (in != null) { + Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING); + } else { + Files.createFile(file); + } + } + } + + @Override + public @Nullable String getString(@NotNull String key, @Nullable String defaultValue) { + Object value = resolve(key); + if (value == null) return defaultValue; + return value.toString(); + } + + @Override + public int getInt(@NotNull String key, int defaultValue) { + Object value = resolve(key); + if (value instanceof Number n) return n.intValue(); + return defaultValue; + } + + @Override + public boolean getBoolean(@NotNull String key, boolean defaultValue) { + Object value = resolve(key); + if (value instanceof Boolean b) return b; + return defaultValue; + } + + @Override + public long getLong(@NotNull String key, long defaultValue) { + Object value = resolve(key); + if (value instanceof Number n) return n.longValue(); + return defaultValue; + } + + @Override + public void set(@NotNull String key, @Nullable Object value) { + String[] parts = key.split("\\."); + Map current = data; + for (int i = 0; i < parts.length - 1; i++) { + current = (Map) current.computeIfAbsent(parts[i], k -> new LinkedHashMap<>()); + } + if (value == null) { + current.remove(parts[parts.length - 1]); + } else { + current.put(parts[parts.length - 1], value); + } + } + + @Override + public void save() { + try (FileWriter writer = new FileWriter(file.toFile())) { + yaml.dump(data, writer); + } catch (IOException e) { + LOGGER.warn("Failed to save config file '{}': {}", file, e.getMessage(), e); + } + } + + @Override + public boolean contains(@NotNull String key) { + return resolve(key) != null; + } + + @Nullable + private Object resolve(@NotNull String key) { + String[] parts = key.split("\\."); + Object current = data; + for (String part : parts) { + if (!(current instanceof Map)) return null; + current = ((Map) current).get(part); + } + return current; + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/platform/FabricPlatformAdapter.java b/fabric/src/main/java/simplexity/simplenicks/fabric/platform/FabricPlatformAdapter.java new file mode 100644 index 0000000..2e7ab47 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/platform/FabricPlatformAdapter.java @@ -0,0 +1,200 @@ +package simplexity.simplenicks.fabric.platform; + +import net.kyori.adventure.platform.modcommon.MinecraftServerAudiences; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import simplexity.simplenicks.fabric.storage.FabricNicknameStorage; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import simplexity.simplenicks.platform.ConfigProvider; +import simplexity.simplenicks.platform.PlatformAdapter; +import simplexity.simplenicks.util.ColorTag; +import simplexity.simplenicks.util.FormatTag; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * {@link PlatformAdapter} implementation for the Fabric platform. + */ +public class FabricPlatformAdapter implements PlatformAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger("SimpleNicks"); + + private final MinecraftServer server; + private final MiniMessage miniMessage; + private final FabricConfigProvider configProvider; + private final FabricConfigProvider localeProvider; + private final Path dataDirectory; + private final MinecraftServerAudiences audiences; + // Thread-safe mirror of online players so isPlayerOnline() is safe to call from async tasks. + private final Set onlinePlayerUuids = ConcurrentHashMap.newKeySet(); + + public FabricPlatformAdapter(@NotNull MinecraftServer server, @NotNull Path dataDirectory) { + this.server = server; + this.dataDirectory = dataDirectory; + this.miniMessage = buildMiniMessage(); + this.configProvider = new FabricConfigProvider(dataDirectory.resolve("config.yml")); + this.localeProvider = new FabricConfigProvider(dataDirectory.resolve("locale.yml")); + this.audiences = MinecraftServerAudiences.of(server); + } + + @Override + public void runAsync(@NotNull Runnable task) { + Thread.ofVirtual() + .uncaughtExceptionHandler((t, e) -> LOGGER.warn("Uncaught exception in async task", e)) + .start(task); + } + + @Override + public void runSync(@NotNull Runnable task) { + server.execute(task); + } + + @Override + public boolean isPlayerOnline(@NotNull UUID uuid) { + return onlinePlayerUuids.contains(uuid); + } + + /** + * Marks a player as online. Must be called on join before any async task reads + * {@link #isPlayerOnline(UUID)}. + */ + public void markOnline(@NotNull UUID uuid) { + onlinePlayerUuids.add(uuid); + } + + /** + * Marks a player as offline. Called on disconnect. + */ + public void markOffline(@NotNull UUID uuid) { + onlinePlayerUuids.remove(uuid); + } + + @Override + public @NotNull Optional getPlayerUsername(@NotNull UUID uuid) { + ServerPlayer player = server.getPlayerList().getPlayer(uuid); + if (player == null) return Optional.empty(); + return Optional.of(player.getGameProfile().name()); + } + + @Override + public @NotNull Collection getOnlinePlayers() { + return server.getPlayerList().getPlayers().stream() + .map(ServerPlayer::getUUID) + .collect(Collectors.toList()); + } + + @Override + public void setDisplayName(@NotNull UUID uuid, @NotNull Component displayName) { + FabricNicknameStorage.setDisplayName(uuid, audiences.asNative(displayName)); + } + + @Override + public void setTablistName(@NotNull UUID uuid, @NotNull Component tablistName) { + FabricNicknameStorage.setTabName(uuid, audiences.asNative(tablistName)); + server.execute(() -> broadcastTabListUpdate(uuid)); + } + + @Override + public void clearDisplayName(@NotNull UUID uuid) { + FabricNicknameStorage.clearDisplayName(uuid); + } + + @Override + public void clearTablistName(@NotNull UUID uuid) { + FabricNicknameStorage.clearTabName(uuid); + server.execute(() -> broadcastTabListUpdate(uuid)); + } + + private void broadcastTabListUpdate(@NotNull UUID uuid) { + ServerPlayer player = server.getPlayerList().getPlayer(uuid); + if (player == null) return; + ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket( + EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), + List.of(player) + ); + server.getPlayerList().broadcastAll(packet); + } + + @Override + public boolean hasPermission(@NotNull UUID uuid, @NotNull String permission) { + ServerPlayer player = server.getPlayerList().getPlayer(uuid); + if (player == null) return false; + return FabricPermissions.check(player, permission); + } + + @Override + public @NotNull Path getDataDirectory() { + return dataDirectory; + } + + @Override + public @NotNull Logger getLogger() { + return LOGGER; + } + + @Override + public @NotNull MiniMessage getMiniMessage() { + return miniMessage; + } + + @Override + public @NotNull ConfigProvider getConfigProvider() { + return configProvider; + } + + @Override + public @NotNull ConfigProvider getLocaleProvider() { + return localeProvider; + } + + /** + * Returns the underlying {@link MinecraftServer}. Used by Fabric-specific event handlers. + * + * @return the server instance + */ + @NotNull + public MinecraftServer getServer() { + return server; + } + + /** + * Returns the {@link MinecraftServerAudiences} instance used for Adventure ↔ NMS component + * conversion and message delivery. + * + * @return the audiences instance + */ + @NotNull + public MinecraftServerAudiences getAudiences() { + return audiences; + } + + @NotNull + private static MiniMessage buildMiniMessage() { + TagResolver.Builder tagResolver = TagResolver.builder(); + for (ColorTag colorTag : ColorTag.values()) { + tagResolver.resolver(colorTag.getTagResolver()); + } + for (FormatTag formatTag : FormatTag.values()) { + tagResolver.resolver(formatTag.getTagResolver()); + } + return MiniMessage.builder() + .strict(false) + .tags(tagResolver.build()) + .build(); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/platform/FabricSenderContext.java b/fabric/src/main/java/simplexity/simplenicks/fabric/platform/FabricSenderContext.java new file mode 100644 index 0000000..3999b4d --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/platform/FabricSenderContext.java @@ -0,0 +1,61 @@ +package simplexity.simplenicks.fabric.platform; + +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; +import simplexity.simplenicks.fabric.platform.FabricPlatformAdapter; +import simplexity.simplenicks.fabric.util.FabricPermissions; +import simplexity.simplenicks.platform.SenderContext; + +import java.util.Optional; +import java.util.UUID; + +/** + * {@link SenderContext} backed by a Minecraft {@link CommandSourceStack}. + * Permission checks delegate to fabric-permissions-api with an op-level fallback. + *

+ * Message delivery uses plain-text serialization into Minecraft's native text component. + * Full MiniMessage rendering can be added once a suitable Adventure bridge is available. + *

+ */ +public class FabricSenderContext implements SenderContext { + + private final CommandSourceStack source; + + public FabricSenderContext(@NotNull CommandSourceStack source) { + this.source = source; + } + + @Override + public boolean hasPermission(@NotNull String permission) { + return FabricPermissions.check(source, permission); + } + + @Override + public @NotNull Optional getUuid() { + ServerPlayer player = source.getPlayer(); + if (player == null) return Optional.empty(); + return Optional.of(player.getUUID()); + } + + @Override + public void sendMessage(@NotNull Component message) { + FabricPlatformAdapter adapter = (FabricPlatformAdapter) SimpleNicksCore.get().platform(); + net.minecraft.network.chat.Component nms = adapter.getAudiences().asNative(message); + source.sendSuccess(() -> nms, false); + } + + @Override + public boolean isPlayer() { + return source.getPlayer() != null; + } + + @Override + public @NotNull String getDisplayName() { + ServerPlayer player = source.getPlayer(); + if (player == null) return "[Console]"; + return player.getGameProfile().name(); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/storage/FabricNicknameStorage.java b/fabric/src/main/java/simplexity/simplenicks/fabric/storage/FabricNicknameStorage.java new file mode 100644 index 0000000..3149a32 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/storage/FabricNicknameStorage.java @@ -0,0 +1,61 @@ +package simplexity.simplenicks.fabric.storage; + +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Static storage for per-player display name components on the Fabric platform. + *

+ * The {@code ServerPlayerMixin} reads from these maps when the server queries + * {@code getDisplayName()} (chat) and {@code getTabListDisplayName()} (tab list). + * {@link simplexity.simplenicks.fabric.platform.FabricPlatformAdapter} writes to + * them after converting Adventure components to NMS components. + *

+ */ +public final class FabricNicknameStorage { + + private static final Map displayNames = new ConcurrentHashMap<>(); + private static final Map tabNames = new ConcurrentHashMap<>(); + + private FabricNicknameStorage() { + } + + public static void setDisplayName(@NotNull UUID uuid, @NotNull Component component) { + displayNames.put(uuid, component); + } + + public static void setTabName(@NotNull UUID uuid, @NotNull Component component) { + tabNames.put(uuid, component); + } + + public static void clearDisplayName(@NotNull UUID uuid) { + displayNames.remove(uuid); + } + + public static void clearTabName(@NotNull UUID uuid) { + tabNames.remove(uuid); + } + + /** + * Returns the stored display name for the given player, or {@code null} if none is set. + * A {@code null} return means the mixin should not intercept and vanilla behaviour applies. + */ + @Nullable + public static Component getDisplayName(@NotNull UUID uuid) { + return displayNames.get(uuid); + } + + /** + * Returns the stored tab-list name for the given player, or {@code null} if none is set. + * A {@code null} return means the mixin should not intercept and vanilla behaviour applies. + */ + @Nullable + public static Component getTabName(@NotNull UUID uuid) { + return tabNames.get(uuid); + } +} diff --git a/fabric/src/main/java/simplexity/simplenicks/fabric/util/FabricPermissions.java b/fabric/src/main/java/simplexity/simplenicks/fabric/util/FabricPermissions.java new file mode 100644 index 0000000..640f176 --- /dev/null +++ b/fabric/src/main/java/simplexity/simplenicks/fabric/util/FabricPermissions.java @@ -0,0 +1,60 @@ +package simplexity.simplenicks.fabric.util; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.util.NickPermission; + +/** + * Maps {@link NickPermission} defaults to the correct {@code Permissions.check} fallback. + *
    + *
  • {@code "true"} → granted to all players by default
  • + *
  • {@code "op"} → granted to op-level-2+ by default
  • + *
  • {@code "false"} → not granted to anyone by default
  • + *
+ */ +public final class FabricPermissions { + + private FabricPermissions() { + } + + public static boolean check(@NotNull CommandSourceStack source, @NotNull NickPermission permission) { + return switch (permission.getPermissionDefault()) { + case "true" -> Permissions.check(source, permission.getPermissionKey(), true); + case "false" -> Permissions.check(source, permission.getPermissionKey(), false); + default -> Permissions.check(source, permission.getPermissionKey(), 2); + }; + } + + public static boolean check(@NotNull ServerPlayer player, @NotNull NickPermission permission) { + return switch (permission.getPermissionDefault()) { + case "true" -> Permissions.check(player, permission.getPermissionKey(), true); + case "false" -> Permissions.check(player, permission.getPermissionKey(), false); + default -> Permissions.check(player, permission.getPermissionKey(), 2); + }; + } + + /** + * String-key variants used by platform adapters and sender contexts. Looks up the matching + * {@link NickPermission} to resolve the correct fallback; defaults to op-level-2 if the key + * is not a registered permission. + */ + public static boolean check(@NotNull CommandSourceStack source, @NotNull String permissionKey) { + for (NickPermission perm : NickPermission.values()) { + if (perm.getPermissionKey().equals(permissionKey)) { + return check(source, perm); + } + } + return Permissions.check(source, permissionKey, 2); + } + + public static boolean check(@NotNull ServerPlayer player, @NotNull String permissionKey) { + for (NickPermission perm : NickPermission.values()) { + if (perm.getPermissionKey().equals(permissionKey)) { + return check(player, perm); + } + } + return Permissions.check(player, permissionKey, 2); + } +} diff --git a/src/main/resources/config.yml b/fabric/src/main/resources/config.yml similarity index 100% rename from src/main/resources/config.yml rename to fabric/src/main/resources/config.yml diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..6e10f42 --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": 1, + "id": "simplenicks", + "version": "3.2.2", + "name": "SimpleNicks", + "description": "A simple mod to allow players to set and reset nicknames.", + "authors": ["Rhythmic", "Peashooter101"], + "license": "AGPL-3.0", + "environment": "server", + "entrypoints": { + "main": ["simplexity.simplenicks.fabric.SimpleNicksFabric"] + }, + "mixins": [ + "simplenicks.mixins.json" + ], + "depends": { + "fabricloader": ">=0.16.0", + "fabric-api": "*", + "minecraft": "~26.1" + } +} diff --git a/fabric/src/main/resources/simplenicks.mixins.json b/fabric/src/main/resources/simplenicks.mixins.json new file mode 100644 index 0000000..7c729e7 --- /dev/null +++ b/fabric/src/main/resources/simplenicks.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "simplexity.simplenicks.fabric.mixin", + "compatibilityLevel": "JAVA_25", + "mixins": [ + "PlayerMixin", + "ServerLoginPacketListenerAccessor", + "ServerPlayerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7a0f487 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,11 @@ +adventureVersion=4.17.0 +miniMessageVersion=4.17.0 +hikariVersion=6.3.0 +slf4jVersion=2.0.16 +paperVersion=1.21.5-R0.1-SNAPSHOT +fabricMinecraftVersion=26.1 +fabricLoaderVersion=0.19.2 +fabricApiVersion=0.145.1+26.1 +adventureFabricVersion=6.9.0 +fabricMiniMessageVersion=4.26.1 +fabricPermissionsVersion=0.7.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f8e1ee3 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..dbc3ce4 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e509b2d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/paper/build.gradle b/paper/build.gradle new file mode 100644 index 0000000..d207ff9 --- /dev/null +++ b/paper/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'io.papermc.paperweight.userdev' version '2.0.0-beta.17' + id 'com.gradleup.shadow' version '9.0.0-beta4' +} + +dependencies { + paperweight.paperDevBundle("${paperVersion}") + implementation project(':core') + compileOnly "me.clip:placeholderapi:2.11.5" + compileOnly "io.github.miniplaceholders:miniplaceholders-api:3.2.0" +} + +shadowJar { + relocate 'com.zaxxer.hikari', 'simplexity.simplenicks.shaded.hikari' + manifest { + attributes 'paperweight-mappings-namespace': 'mojang' + } +} + +tasks.withType(JavaCompile).configureEach { + options.release = 21 +} + +// options.release = 21 causes Gradle to request JVM 21-compatible deps, +// which conflicts with miniplaceholders-api:3.2.0 (requires JVM 25). +// Override the attribute to request JVM 25 deps while still producing Java 21 bytecode. +configurations.named('compileClasspath') { + attributes { + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25) + } +} + +tasks.named('reobfJar') { + outputJar = layout.buildDirectory.file("libs/SimpleNicks-paper-${project.version}.jar") +} + +assemble.dependsOn reobfJar diff --git a/src/main/java/simplexity/simplenicks/SimpleNicks.java b/paper/src/main/java/simplexity/simplenicks/SimpleNicks.java similarity index 58% rename from src/main/java/simplexity/simplenicks/SimpleNicks.java rename to paper/src/main/java/simplexity/simplenicks/SimpleNicks.java index 6925c02..14118d6 100644 --- a/src/main/java/simplexity/simplenicks/SimpleNicks.java +++ b/paper/src/main/java/simplexity/simplenicks/SimpleNicks.java @@ -1,9 +1,8 @@ package simplexity.simplenicks; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; -import org.bukkit.plugin.Plugin; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.java.JavaPlugin; import simplexity.simplenicks.commands.NicknameCommand; import simplexity.simplenicks.config.ConfigHandler; @@ -11,90 +10,76 @@ import simplexity.simplenicks.hooks.SNMiniExpansion; import simplexity.simplenicks.listener.LeaveListener; import simplexity.simplenicks.listener.LoginListener; +import simplexity.simplenicks.platform.PaperPlatformAdapter; import simplexity.simplenicks.saving.SaveMigrator; import simplexity.simplenicks.saving.SqlHandler; import simplexity.simplenicks.util.ColorTag; import simplexity.simplenicks.util.FormatTag; import simplexity.simplenicks.util.NickPermission; -import java.util.logging.Logger; - - @SuppressWarnings("UnstableApiUsage") public final class SimpleNicks extends JavaPlugin { - private static MiniMessage miniMessage; - private static Plugin instance; - @Override public void onEnable() { - instance = this; this.saveDefaultConfig(); getConfig().options().copyDefaults(true); saveConfig(); + + PaperPlatformAdapter adapter = new PaperPlatformAdapter(this); + SimpleNicksCore.initialize(adapter); + + ConfigHandler.getInstance().reloadConfig(); + SqlHandler.getInstance().init(); + SaveMigrator.migrateFromYml(); + if (this.getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) { new SNExpansion().register(); } if (this.getServer().getPluginManager().isPluginEnabled("MiniPlaceholders")) { SNMiniExpansion.register(); } + getServer().getPluginManager().registerEvents(new LoginListener(), this); getServer().getPluginManager().registerEvents(new LeaveListener(), this); - configReload(); - SqlHandler.getInstance().init(); - SaveMigrator.migrateFromYml(); + this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> { commands.registrar().register(NicknameCommand.createCommand().build()); }); - setUpMiniMessage(); + registerPermissions(); } + @Override + public void onDisable() { + SqlHandler.getInstance().closeDatabase(); + SimpleNicksCore.shutdown(); + } + private void registerPermissions() { for (NickPermission perm : NickPermission.values()) { - getServer().getPluginManager().addPermission(perm.getPermission()); + getServer().getPluginManager().addPermission( + new Permission(perm.getPermissionKey(), toDefault(perm.getPermissionDefault())) + ); } - for (ColorTag perm : ColorTag.values()) { - getServer().getPluginManager().addPermission(perm.getPermission()); + for (ColorTag tag : ColorTag.values()) { + getServer().getPluginManager().addPermission( + new Permission(tag.getPermissionKey(), toDefault(tag.getPermissionDefault())) + ); } - } - - private static void setUpMiniMessage() { - TagResolver.Builder tagResolver = TagResolver.builder(); - for (ColorTag colorTag : ColorTag.values()) { - tagResolver.resolver(colorTag.getTagResolver()); - } - for (FormatTag formatTag : FormatTag.values()) { - tagResolver.resolver(formatTag.getTagResolver()); + for (FormatTag tag : FormatTag.values()) { + getServer().getPluginManager().addPermission( + new Permission(tag.getPermissionKey(), toDefault(tag.getPermissionDefault())) + ); } - TagResolver resolver = tagResolver.build(); - miniMessage = MiniMessage.builder() - .strict(false) - .tags(resolver) - .build(); - } - - public static MiniMessage getMiniMessage() { - return miniMessage; - } - - public static Plugin getInstance() { - return instance; - } - - public static Logger getSimpleNicksLogger() { - return instance.getLogger(); } - - public static void configReload() { - ConfigHandler.getInstance().reloadConfig(); + @SuppressWarnings("deprecation") + private static PermissionDefault toDefault(String value) { + return switch (value.toLowerCase()) { + case "true" -> PermissionDefault.TRUE; + case "false" -> PermissionDefault.FALSE; + default -> PermissionDefault.OP; + }; } - - @Override - public void onDisable() { - SqlHandler.getInstance().closeDatabase(); - } - - } diff --git a/src/main/java/simplexity/simplenicks/commands/NicknameCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/NicknameCommand.java similarity index 89% rename from src/main/java/simplexity/simplenicks/commands/NicknameCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/NicknameCommand.java index 77910fc..ce0e4d4 100644 --- a/src/main/java/simplexity/simplenicks/commands/NicknameCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/NicknameCommand.java @@ -3,23 +3,26 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import org.jetbrains.annotations.NotNull; import simplexity.simplenicks.commands.subcommands.admin.AdminSubCommand; import simplexity.simplenicks.commands.subcommands.basic.DeleteSubCommand; import simplexity.simplenicks.commands.subcommands.basic.HelpSubCommand; -import simplexity.simplenicks.commands.subcommands.basic.WhoSubCommand; import simplexity.simplenicks.commands.subcommands.basic.ReloadSubCommand; import simplexity.simplenicks.commands.subcommands.basic.ResetSubCommand; import simplexity.simplenicks.commands.subcommands.basic.SaveSubCommand; import simplexity.simplenicks.commands.subcommands.basic.SetSubCommand; +import simplexity.simplenicks.commands.subcommands.basic.WhoSubCommand; import simplexity.simplenicks.config.ConfigHandler; import simplexity.simplenicks.util.NickPermission; @SuppressWarnings("UnstableApiUsage") public class NicknameCommand { + @NotNull public static LiteralArgumentBuilder createCommand() { LiteralArgumentBuilder builder = Commands.literal("nick") - .requires(src -> !ConfigHandler.getInstance().isNickRequiresPermission() || src.getSender().hasPermission(NickPermission.NICK_COMMAND.getPermission())); + .requires(src -> !ConfigHandler.getInstance().isNickRequiresPermission() + || src.getSender().hasPermission(NickPermission.NICK_COMMAND.getPermissionKey())); new HelpSubCommand().subcommandTo(builder); new SetSubCommand().subcommandTo(builder); new SaveSubCommand().subcommandTo(builder); @@ -30,7 +33,4 @@ public static LiteralArgumentBuilder createCommand() { new WhoSubCommand().subcommandTo(builder); return builder; } - - - } diff --git a/src/main/java/simplexity/simplenicks/commands/arguments/NicknameArgument.java b/paper/src/main/java/simplexity/simplenicks/commands/arguments/NicknameArgument.java similarity index 61% rename from src/main/java/simplexity/simplenicks/commands/arguments/NicknameArgument.java rename to paper/src/main/java/simplexity/simplenicks/commands/arguments/NicknameArgument.java index 4ee2a7c..6f2092f 100644 --- a/src/main/java/simplexity/simplenicks/commands/arguments/NicknameArgument.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/arguments/NicknameArgument.java @@ -13,7 +13,7 @@ import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.saving.Cache; import simplexity.simplenicks.saving.Nickname; @@ -31,44 +31,18 @@ @SuppressWarnings("UnstableApiUsage") public class NicknameArgument implements CustomArgumentType { - - /** - * Parses a nickname string from the command input into a {@link Nickname} object. - * The normalized nickname is generated by stripping MiniMessage tags and converting - * to lowercase. - * - * @param reader The Brigadier StringReader containing the command input - * @return A {@link Nickname} object representing the parsed nickname - * @throws CommandSyntaxException if parsing fails - */ @Override public @NotNull Nickname parse(@NotNull StringReader reader) throws CommandSyntaxException { String nickname = getNativeType().parse(reader); - String normalizedNickname = SimpleNicks.getMiniMessage().stripTags(nickname).toLowerCase(); + String normalizedNickname = SimpleNicksCore.get().miniMessage().stripTags(nickname).toLowerCase(); return new Nickname(nickname, normalizedNickname); } - /** - * Gets the native Brigadier type for this argument. - * Uses a greedy string to allow spaces in nicknames. - * - * @return The native {@link ArgumentType} for nicknames - */ @Override public @NotNull ArgumentType getNativeType() { return StringArgumentType.greedyString(); } - - /** - * Provides suggestions for nicknames saved by the command sender. - * - * @param context Command context - * @param builder SuggestionsBuilder for adding completions - * @param Typically {@link io.papermc.paper.command.brigadier.CommandSourceStack} - * @return A CompletableFuture containing the suggestions - */ - public @NotNull CompletableFuture suggestOwnNicknames(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) { CommandSourceStack css = (CommandSourceStack) context.getSource(); OfflinePlayer player = (OfflinePlayer) css.getSender(); @@ -76,15 +50,6 @@ public class NicknameArgument implements CustomArgumentType { return builder.buildFuture(); } - - /** - * Provides suggestions for nicknames saved by another player argument. - * - * @param context Command context - * @param builder SuggestionsBuilder for adding completions - * @param Typically {@link io.papermc.paper.command.brigadier.CommandSourceStack} - * @return A CompletableFuture containing the suggestions - */ public @NotNull CompletableFuture suggestOtherNicknames(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) { OfflinePlayer player = context.getArgument("player", OfflinePlayer.class); if (player == null) return builder.buildFuture(); @@ -92,16 +57,6 @@ public class NicknameArgument implements CustomArgumentType { return builder.buildFuture(); } - - - /** - * Provides combined suggestions for nicknames of both the command sender and another player. - * - * @param context Command context - * @param builder SuggestionsBuilder for adding completions - * @param Typically {@link io.papermc.paper.command.brigadier.CommandSourceStack} - * @return A CompletableFuture containing the suggestions - */ public @NotNull CompletableFuture suggestOwnAndOtherNicknames(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) { OfflinePlayer player = context.getArgument("player", OfflinePlayer.class); CommandSourceStack css = (CommandSourceStack) context.getSource(); @@ -111,15 +66,7 @@ public class NicknameArgument implements CustomArgumentType { return builder.buildFuture(); } - /** - * Provides suggestions for all online nicknames. - * - * @param context Command context - * @param builder SuggestionsBuilder for adding completions - * @param Typically {@link io.papermc.paper.command.brigadier.CommandSourceStack} - * @return A CompletableFuture containing the suggestions - */ - public @NotNull CompletableFuture suggestAllOnlineNicknames(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder){ + public @NotNull CompletableFuture suggestAllOnlineNicknames(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) { for (Nickname nickname : Cache.getInstance().getOnlineNicknames().values()) { builder.suggest(nickname.getNormalizedNickname()); } @@ -127,20 +74,18 @@ public class NicknameArgument implements CustomArgumentType { } private void addSuggestionsForPlayer(@NotNull SuggestionsBuilder builder, @NotNull OfflinePlayer player) { - for (Nickname nickname : NicknameProcessor.getInstance().getSavedNicknames(player)) { + for (Nickname nickname : NicknameProcessor.getInstance().getSavedNicknames(player.getUniqueId(), player.isOnline())) { String suggestion = nickname.getNickname(); String suggestionStripped = nickname.getNormalizedNickname(); - if (suggestionStripped.toLowerCase().contains(builder.getRemainingLowerCase()) || suggestion.toLowerCase().contains(builder.getRemainingLowerCase())) { + if (suggestionStripped.toLowerCase().contains(builder.getRemainingLowerCase()) + || suggestion.toLowerCase().contains(builder.getRemainingLowerCase())) { builder.suggest( suggestion, MessageComponentSerializer.message().serialize( - SimpleNicks.getMiniMessage().deserialize("Preview: " + nickname.getNickname()) + SimpleNicksCore.get().miniMessage().deserialize("Preview: " + nickname.getNickname()) ) ); } } } - - - } diff --git a/src/main/java/simplexity/simplenicks/commands/arguments/OfflinePlayerArgument.java b/paper/src/main/java/simplexity/simplenicks/commands/arguments/OfflinePlayerArgument.java similarity index 69% rename from src/main/java/simplexity/simplenicks/commands/arguments/OfflinePlayerArgument.java rename to paper/src/main/java/simplexity/simplenicks/commands/arguments/OfflinePlayerArgument.java index c28e9ef..42d8122 100644 --- a/src/main/java/simplexity/simplenicks/commands/arguments/OfflinePlayerArgument.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/arguments/OfflinePlayerArgument.java @@ -13,7 +13,7 @@ import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.commands.subcommands.Exceptions; import simplexity.simplenicks.saving.Nickname; @@ -31,13 +31,6 @@ @SuppressWarnings("UnstableApiUsage") public class OfflinePlayerArgument implements CustomArgumentType { - /** - * Parses a player name from the command input into an {@link OfflinePlayer} object. - * - * @param reader The Brigadier StringReader containing the command input - * @return The {@link OfflinePlayer} corresponding to the input - * @throws CommandSyntaxException if the player does not exist or has never joined - */ @Override public @NotNull OfflinePlayer parse(@NotNull StringReader reader) throws CommandSyntaxException { String playerName = reader.readString(); @@ -48,39 +41,24 @@ public class OfflinePlayerArgument implements CustomArgumentType getNativeType() { return StringArgumentType.word(); } - /** - * Provides suggestions for online players. - * Each suggestion includes a hoverable preview of their current nickname. - * - * @param ignoredContext Command context (unused) - * @param builder SuggestionsBuilder for adding completions - * @param Typically {@link io.papermc.paper.command.brigadier.CommandSourceStack} - * @return A CompletableFuture containing the suggestions - */ public @NotNull CompletableFuture suggestOnlinePlayers(@NotNull CommandContext ignoredContext, @NotNull SuggestionsBuilder builder) { for (Player player : Bukkit.getOnlinePlayers()) { String suggestion = player.getName(); if (suggestion.toLowerCase().contains(builder.getRemainingLowerCase())) { - Nickname currentNick = NicknameProcessor.getInstance().getCurrentNickname(player); + Nickname currentNick = NicknameProcessor.getInstance().getCurrentNickname(player.getUniqueId(), true); String nickDisplay = currentNick != null ? currentNick.getNickname() : player.getName(); builder.suggest( suggestion, MessageComponentSerializer.message().serialize( - SimpleNicks.getMiniMessage().deserialize("Current Nickname: " + nickDisplay) + SimpleNicksCore.get().miniMessage().deserialize("Current Nickname: " + nickDisplay) ) ); } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminDeleteSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminDeleteSubCommand.java similarity index 83% rename from src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminDeleteSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminDeleteSubCommand.java index 1dd63e5..6e16467 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminDeleteSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminDeleteSubCommand.java @@ -11,7 +11,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.commands.arguments.NicknameArgument; import simplexity.simplenicks.commands.arguments.OfflinePlayerArgument; @@ -29,8 +29,7 @@ public void subcommandTo(@NotNull LiteralArgumentBuilder par OfflinePlayerArgument offlinePlayerArg = new OfflinePlayerArgument(); NicknameArgument nicknameArg = new NicknameArgument(); - parent.then(Commands.literal("delete") - .requires(this::canExecute) + parent.then(Commands.literal("delete").requires(this::canExecute) .then(Commands.argument("player", offlinePlayerArg) .suggests(offlinePlayerArg::suggestOnlinePlayers) .then(Commands.argument("nickname", nicknameArg) @@ -43,11 +42,12 @@ public int execute(@NotNull CommandContext ctx) throws Comma CommandSender sender = ctx.getSource().getSender(); OfflinePlayer target = ctx.getArgument("player", OfflinePlayer.class); Nickname nickname = ctx.getArgument("nickname", Nickname.class); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - boolean success = NicknameProcessor.getInstance().deleteNickname(target, nickname.getNickname()); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance().deleteNickname(target.getUniqueId(), nickname.getNickname()); if (success) { - Bukkit.getScheduler().runTask(SimpleNicks.getInstance(), () -> { - if ((target instanceof Player onlineTarget)) { + SimpleNicksCore.get().platform().runSync(() -> { + Player onlineTarget = Bukkit.getPlayer(target.getUniqueId()); + if (onlineTarget != null) { NickUtils.refreshDisplayName(target.getUniqueId()); onlineTarget.sendMessage(parseAdminMessage(LocaleMessage.DELETED_BY_INITIATOR.getMessage(), nickname.getNickname(), sender, target)); } @@ -62,8 +62,6 @@ public int execute(@NotNull CommandContext ctx) throws Comma @Override public boolean canExecute(@NotNull CommandSourceStack css) { - CommandSender sender = css.getSender(); - return sender.hasPermission(NickPermission.NICK_ADMIN_DELETE.getPermission()); + return css.getSender().hasPermission(NickPermission.NICK_ADMIN_DELETE.getPermissionKey()); } - } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminLookupSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminLookupSubCommand.java similarity index 73% rename from src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminLookupSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminLookupSubCommand.java index 7fb16d0..4884d2b 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminLookupSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminLookupSubCommand.java @@ -7,14 +7,12 @@ import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.commands.arguments.OfflinePlayerArgument; import simplexity.simplenicks.commands.subcommands.Exceptions; @@ -29,18 +27,13 @@ @SuppressWarnings("UnstableApiUsage") public class AdminLookupSubCommand implements SubCommand { - private final MiniMessage miniMessage = SimpleNicks.getMiniMessage(); - - @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { OfflinePlayerArgument offlinePlayerArg = new OfflinePlayerArgument(); - parent.then(Commands.literal("lookup") - .requires(this::canExecute) + parent.then(Commands.literal("lookup").requires(this::canExecute) .then(Commands.argument("player", offlinePlayerArg) .suggests(offlinePlayerArg::suggestOnlinePlayers) .executes(this::execute))); - } @Override @@ -48,10 +41,11 @@ public int execute(@NotNull CommandContext ctx) throws Comma CommandSender sender = ctx.getSource().getSender(); OfflinePlayer lookupTarget = ctx.getArgument("player", OfflinePlayer.class); String username = lookupTarget.getName(); - if (username == null) throw Exceptions.INVALID_PLAYER_SPECIFIED.create(lookupTarget); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - Nickname currentNickname = NicknameProcessor.getInstance().getCurrentNickname(lookupTarget); - List savedNicknames = NicknameProcessor.getInstance().getSavedNicknames(lookupTarget); + if (username == null) throw Exceptions.invalidPlayerSpecified(lookupTarget); + boolean isOnline = lookupTarget.isOnline(); + SimpleNicksCore.get().platform().runAsync(() -> { + Nickname currentNickname = NicknameProcessor.getInstance().getCurrentNickname(lookupTarget.getUniqueId(), isOnline); + List savedNicknames = NicknameProcessor.getInstance().getSavedNicknames(lookupTarget.getUniqueId(), isOnline); sender.sendMessage(lookupInfoComponent(username, currentNickname, savedNicknames)); }); return Command.SINGLE_SUCCESS; @@ -59,23 +53,24 @@ public int execute(@NotNull CommandContext ctx) throws Comma @Override public boolean canExecute(@NotNull CommandSourceStack css) { - CommandSender sender = css.getSender(); - return sender.hasPermission(NickPermission.NICK_ADMIN_LOOKUP.getPermission()); + return css.getSender().hasPermission(NickPermission.NICK_ADMIN_LOOKUP.getPermissionKey()); } @NotNull public Component lookupInfoComponent(@NotNull String username, @Nullable Nickname currentNick, @Nullable List savedNames) { String nickname; if (currentNick == null) { - if (savedNames == null || savedNames.isEmpty()) return miniMessage.deserialize(LocaleMessage.ERROR_NO_PLAYERS_WITH_THIS_NAME.getMessage()); + if (savedNames == null || savedNames.isEmpty()) { + return SimpleNicksCore.get().miniMessage().deserialize(LocaleMessage.ERROR_NO_PLAYERS_WITH_THIS_NAME.getMessage()); + } nickname = LocaleMessage.INSERT_NONE.getMessage(); } else { nickname = currentNick.getNickname(); } - String infoString = LocaleMessage.LOOKUP_HEADER.getMessage() + - LocaleMessage.LOOKUP_CURRENT.getMessage() + - LocaleMessage.LOOKUP_SAVED.getMessage(); - return miniMessage.deserialize(infoString, + String infoString = LocaleMessage.LOOKUP_HEADER.getMessage() + + LocaleMessage.LOOKUP_CURRENT.getMessage() + + LocaleMessage.LOOKUP_SAVED.getMessage(); + return SimpleNicksCore.get().miniMessage().deserialize(infoString, Placeholder.unparsed("username", username), Placeholder.parsed("name", nickname), MessageUtils.savedNickListResolver(savedNames)); diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminResetSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminResetSubCommand.java similarity index 82% rename from src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminResetSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminResetSubCommand.java index c8b724b..66fc629 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminResetSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminResetSubCommand.java @@ -11,7 +11,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.commands.arguments.OfflinePlayerArgument; import simplexity.simplenicks.commands.subcommands.basic.SubCommand; @@ -21,28 +21,26 @@ @SuppressWarnings("UnstableApiUsage") public class AdminResetSubCommand implements SubCommand { + @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { - OfflinePlayerArgument offlinePlayerArgument = new OfflinePlayerArgument(); - - parent.then(Commands.literal("reset") - .requires(this::canExecute) + parent.then(Commands.literal("reset").requires(this::canExecute) .then(Commands.argument("player", offlinePlayerArgument) .suggests(offlinePlayerArgument::suggestOnlinePlayers) .executes(this::execute))); - } @Override public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { CommandSender sender = ctx.getSource().getSender(); OfflinePlayer target = ctx.getArgument("player", OfflinePlayer.class); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - boolean success = NicknameProcessor.getInstance().resetNickname(target); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance().resetNickname(target.getUniqueId()); if (success) { - Bukkit.getScheduler().runTask(SimpleNicks.getInstance(), () -> { - if ((target instanceof Player onlineTarget)) { + SimpleNicksCore.get().platform().runSync(() -> { + Player onlineTarget = Bukkit.getPlayer(target.getUniqueId()); + if (onlineTarget != null) { NickUtils.refreshDisplayName(target.getUniqueId()); onlineTarget.sendMessage(parseAdminMessage(LocaleMessage.RESET_BY_INITIATOR.getMessage(), "", sender, target)); } @@ -57,8 +55,6 @@ public int execute(@NotNull CommandContext ctx) throws Comma @Override public boolean canExecute(@NotNull CommandSourceStack css) { - CommandSender sender = css.getSender(); - return sender.hasPermission(NickPermission.NICK_ADMIN_RESET.getPermission()); + return css.getSender().hasPermission(NickPermission.NICK_ADMIN_RESET.getPermissionKey()); } - } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSetSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSetSubCommand.java similarity index 75% rename from src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSetSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSetSubCommand.java index 933ce2c..4e24775 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSetSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSetSubCommand.java @@ -11,7 +11,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.commands.arguments.NicknameArgument; import simplexity.simplenicks.commands.arguments.OfflinePlayerArgument; @@ -19,6 +19,7 @@ import simplexity.simplenicks.commands.subcommands.basic.SubCommand; import simplexity.simplenicks.config.LocaleMessage; import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.platform.PaperSenderContext; import simplexity.simplenicks.saving.Nickname; import simplexity.simplenicks.util.NickPermission; @@ -27,12 +28,10 @@ public class AdminSetSubCommand implements SubCommand { @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { - OfflinePlayerArgument offlinePlayerArgument = new OfflinePlayerArgument(); NicknameArgument nicknameArgument = new NicknameArgument(); - parent.then(Commands.literal("set") - .requires(this::canExecute) + parent.then(Commands.literal("set").requires(this::canExecute) .then(Commands.argument("player", offlinePlayerArgument) .suggests(offlinePlayerArgument::suggestOnlinePlayers) .then(Commands.argument("nickname", nicknameArgument) @@ -41,7 +40,6 @@ public void subcommandTo(@NotNull LiteralArgumentBuilder par ) ) ); - } @Override @@ -49,13 +47,17 @@ public int execute(@NotNull CommandContext ctx) throws Comma CommandSender sender = ctx.getSource().getSender(); OfflinePlayer target = ctx.getArgument("player", OfflinePlayer.class); Nickname nickname = ctx.getArgument("nickname", Nickname.class); - if (!NickUtils.isValidTags(sender, nickname.getNickname())) throw Exceptions.ERROR_TAGS_NOT_PERMITTED.create(); - NickUtils.nicknameChecks(sender, nickname); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - boolean success = NicknameProcessor.getInstance().setNickname(target, nickname.getNickname()); + String targetUsername = target.getName(); + if (targetUsername == null) throw Exceptions.invalidPlayerSpecified(target); + PaperSenderContext senderCtx = new PaperSenderContext(sender); + if (!NickUtils.isValidTags(senderCtx, nickname.getNickname())) throw Exceptions.tagsNotPermitted(); + NickUtils.nicknameChecks(senderCtx, nickname); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance().setNickname(target.getUniqueId(), targetUsername, nickname.getNickname()); if (success) { - Bukkit.getScheduler().runTask(SimpleNicks.getInstance(), () -> { - if ((target instanceof Player onlineTarget)) { + SimpleNicksCore.get().platform().runSync(() -> { + Player onlineTarget = Bukkit.getPlayer(target.getUniqueId()); + if (onlineTarget != null) { NickUtils.refreshDisplayName(target.getUniqueId()); onlineTarget.sendMessage(parseAdminMessage(LocaleMessage.SET_BY_INITIATOR.getMessage(), nickname.getNickname(), sender, target)); } @@ -70,8 +72,6 @@ public int execute(@NotNull CommandContext ctx) throws Comma @Override public boolean canExecute(@NotNull CommandSourceStack css) { - CommandSender sender = css.getSender(); - return sender.hasPermission(NickPermission.NICK_ADMIN_SET.getPermission()); + return css.getSender().hasPermission(NickPermission.NICK_ADMIN_SET.getPermissionKey()); } - } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSubCommand.java similarity index 91% rename from src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSubCommand.java index 940177c..fb72a5b 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/admin/AdminSubCommand.java @@ -11,12 +11,11 @@ @SuppressWarnings("UnstableApiUsage") public class AdminSubCommand implements SubCommand { + @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { - LiteralArgumentBuilder admin = - Commands.literal("admin") - .requires(this::canExecute); + Commands.literal("admin").requires(this::canExecute); new AdminSetSubCommand().subcommandTo(admin); new AdminResetSubCommand().subcommandTo(admin); @@ -24,7 +23,6 @@ public void subcommandTo(@NotNull LiteralArgumentBuilder par new AdminDeleteSubCommand().subcommandTo(admin); parent.then(admin); - } @Override @@ -34,7 +32,6 @@ public int execute(@NotNull CommandContext ctx) { @Override public boolean canExecute(@NotNull CommandSourceStack css) { - return css.getSender().hasPermission(NickPermission.NICK_ADMIN.getPermission()); + return css.getSender().hasPermission(NickPermission.NICK_ADMIN.getPermissionKey()); } - } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/DeleteSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/DeleteSubCommand.java similarity index 87% rename from src/main/java/simplexity/simplenicks/commands/subcommands/basic/DeleteSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/DeleteSubCommand.java index b7d117b..f128fce 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/DeleteSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/DeleteSubCommand.java @@ -6,10 +6,9 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.commands.arguments.NicknameArgument; import simplexity.simplenicks.config.LocaleMessage; @@ -20,28 +19,24 @@ @SuppressWarnings("UnstableApiUsage") public class DeleteSubCommand implements SubCommand { - @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { - NicknameArgument argument = new NicknameArgument(); - parent.then(Commands.literal("delete").requires(this::canExecute) .then(Commands.argument("nickname", argument) .suggests(argument::suggestOwnNicknames) .executes(this::execute)) ); - } @Override public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { Player player = (Player) ctx.getSource().getSender(); Nickname nickname = ctx.getArgument("nickname", Nickname.class); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - boolean success = NicknameProcessor.getInstance().deleteNickname(player, nickname.getNickname()); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance().deleteNickname(player.getUniqueId(), nickname.getNickname()); if (success) { - Bukkit.getScheduler().runTask(SimpleNicks.getInstance(), () -> { + SimpleNicksCore.get().platform().runSync(() -> { NickUtils.refreshDisplayName(player.getUniqueId()); sendFeedback(player, LocaleMessage.DELETE_SELF, nickname); }); @@ -52,10 +47,9 @@ public int execute(@NotNull CommandContext ctx) throws Comma return Command.SINGLE_SUCCESS; } - @Override public boolean canExecute(@NotNull CommandSourceStack css) { if (!(css.getSender() instanceof Player player)) return false; - return permissionNotRequired() || player.hasPermission(NickPermission.NICK_SAVE.getPermission()); + return permissionNotRequired() || player.hasPermission(NickPermission.NICK_SAVE.getPermissionKey()); } } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/HelpSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/HelpSubCommand.java similarity index 80% rename from src/main/java/simplexity/simplenicks/commands/subcommands/basic/HelpSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/HelpSubCommand.java index af52d36..a54fc88 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/HelpSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/HelpSubCommand.java @@ -10,7 +10,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.config.ConfigHandler; import simplexity.simplenicks.config.LocaleMessage; import simplexity.simplenicks.util.NickPermission; @@ -30,53 +30,43 @@ public int execute(@NotNull CommandContext ctx) throws Comma return Command.SINGLE_SUCCESS; } - /** - * Builds a help message containing only the commands the given sender has access to. - * - * @param sender the command sender - * @return formatted help component - */ @NotNull private Component buildHelpMessage(@NotNull CommandSender sender) { ConfigHandler config = ConfigHandler.getInstance(); Component help = line(LocaleMessage.HELP_HEADER); - // Player-only commands — only shown to players who have the relevant permission if (sender instanceof Player player) { boolean permNotRequired = !config.isNickRequiresPermission(); - if (permNotRequired || player.hasPermission(NickPermission.NICK_SET.getPermission())) { + if (permNotRequired || player.hasPermission(NickPermission.NICK_SET.getPermissionKey())) { help = help.appendNewline().append(line(LocaleMessage.HELP_SET)); help = help.appendNewline().append(line(LocaleMessage.HELP_RESET)); } - if (permNotRequired || player.hasPermission(NickPermission.NICK_SAVE.getPermission())) { + if (permNotRequired || player.hasPermission(NickPermission.NICK_SAVE.getPermissionKey())) { help = help.appendNewline().append(line(LocaleMessage.HELP_SAVE)); help = help.appendNewline().append(line(LocaleMessage.HELP_DELETE)); } } - // /nick who — usable by any sender type - if (!config.isWhoRequiresPermission() || sender.hasPermission(NickPermission.NICK_WHO.getPermission())) { + if (!config.isWhoRequiresPermission() || sender.hasPermission(NickPermission.NICK_WHO.getPermissionKey())) { help = help.appendNewline().append(line(LocaleMessage.HELP_WHO)); } - // Admin commands — shown per individual permission, not just the parent node - if (sender.hasPermission(NickPermission.NICK_ADMIN_SET.getPermission())) { + if (sender.hasPermission(NickPermission.NICK_ADMIN_SET.getPermissionKey())) { help = help.appendNewline().append(line(LocaleMessage.HELP_ADMIN_SET)); } - if (sender.hasPermission(NickPermission.NICK_ADMIN_RESET.getPermission())) { + if (sender.hasPermission(NickPermission.NICK_ADMIN_RESET.getPermissionKey())) { help = help.appendNewline().append(line(LocaleMessage.HELP_ADMIN_RESET)); } - if (sender.hasPermission(NickPermission.NICK_ADMIN_DELETE.getPermission())) { + if (sender.hasPermission(NickPermission.NICK_ADMIN_DELETE.getPermissionKey())) { help = help.appendNewline().append(line(LocaleMessage.HELP_ADMIN_DELETE)); } - if (sender.hasPermission(NickPermission.NICK_ADMIN_LOOKUP.getPermission())) { + if (sender.hasPermission(NickPermission.NICK_ADMIN_LOOKUP.getPermissionKey())) { help = help.appendNewline().append(line(LocaleMessage.HELP_ADMIN_LOOKUP)); } - // /nick reload - if (sender.hasPermission(NickPermission.NICK_RELOAD.getPermission())) { + if (sender.hasPermission(NickPermission.NICK_RELOAD.getPermissionKey())) { help = help.appendNewline().append(line(LocaleMessage.HELP_RELOAD)); } @@ -85,11 +75,11 @@ private Component buildHelpMessage(@NotNull CommandSender sender) { @NotNull private static Component line(@NotNull LocaleMessage msg) { - return SimpleNicks.getMiniMessage().deserialize(msg.getMessage()); + return SimpleNicksCore.get().miniMessage().deserialize(msg.getMessage()); } @Override public boolean canExecute(@NotNull CommandSourceStack css) { - return css.getSender().hasPermission(NickPermission.NICK_HELP.getPermission()); + return css.getSender().hasPermission(NickPermission.NICK_HELP.getPermissionKey()); } } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ReloadSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ReloadSubCommand.java similarity index 82% rename from src/main/java/simplexity/simplenicks/commands/subcommands/basic/ReloadSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ReloadSubCommand.java index d975964..743e0d9 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ReloadSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ReloadSubCommand.java @@ -6,10 +6,9 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.config.ConfigHandler; import simplexity.simplenicks.config.LocaleMessage; import simplexity.simplenicks.logic.NickUtils; @@ -18,6 +17,7 @@ @SuppressWarnings("UnstableApiUsage") public class ReloadSubCommand implements SubCommand { + @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { parent.then(Commands.literal("reload").requires(this::canExecute) @@ -37,15 +37,12 @@ public int execute(@NotNull CommandContext ctx) throws Comma @Override public boolean canExecute(@NotNull CommandSourceStack css) { - CommandSender sender = css.getSender(); - return sender.hasPermission(NickPermission.NICK_RELOAD.getPermission()); + return css.getSender().hasPermission(NickPermission.NICK_RELOAD.getPermissionKey()); } - private void refreshTablist(){ - for (Player player : Bukkit.getOnlinePlayers()) { - NickUtils.refreshDisplayName(player.getUniqueId()); + private void refreshTablist() { + for (java.util.UUID uuid : SimpleNicksCore.get().platform().getOnlinePlayers()) { + NickUtils.refreshDisplayName(uuid); } - } - } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ResetSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ResetSubCommand.java similarity index 85% rename from src/main/java/simplexity/simplenicks/commands/subcommands/basic/ResetSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ResetSubCommand.java index 4072626..b0dbe04 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ResetSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/ResetSubCommand.java @@ -5,10 +5,9 @@ import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.config.LocaleMessage; import simplexity.simplenicks.logic.NickUtils; @@ -16,9 +15,9 @@ @SuppressWarnings("UnstableApiUsage") public class ResetSubCommand implements SubCommand { + @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { - parent.then(Commands.literal("reset").requires(this::canExecute) .executes(this::execute) ); @@ -27,10 +26,10 @@ public void subcommandTo(@NotNull LiteralArgumentBuilder par @Override public int execute(@NotNull CommandContext ctx) { Player player = (Player) ctx.getSource().getSender(); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - boolean success = NicknameProcessor.getInstance().resetNickname(player); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean success = NicknameProcessor.getInstance().resetNickname(player.getUniqueId()); if (success) { - Bukkit.getScheduler().runTask(SimpleNicks.getInstance(), () -> { + SimpleNicksCore.get().platform().runSync(() -> { NickUtils.refreshDisplayName(player.getUniqueId()); sendFeedback(player, LocaleMessage.RESET_SELF, null); }); @@ -44,6 +43,6 @@ public int execute(@NotNull CommandContext ctx) { @Override public boolean canExecute(@NotNull CommandSourceStack css) { if (!(css.getSender() instanceof Player player)) return false; - return permissionNotRequired() || player.hasPermission(NickPermission.NICK_SET.getPermission()); + return permissionNotRequired() || player.hasPermission(NickPermission.NICK_SET.getPermissionKey()); } } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SaveSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SaveSubCommand.java similarity index 79% rename from src/main/java/simplexity/simplenicks/commands/subcommands/basic/SaveSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SaveSubCommand.java index 094b345..1fe1ad8 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SaveSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SaveSubCommand.java @@ -6,49 +6,43 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.commands.arguments.NicknameArgument; import simplexity.simplenicks.commands.subcommands.Exceptions; import simplexity.simplenicks.config.ConfigHandler; import simplexity.simplenicks.config.LocaleMessage; import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.platform.PaperSenderContext; import simplexity.simplenicks.saving.Nickname; import simplexity.simplenicks.util.NickPermission; @SuppressWarnings("UnstableApiUsage") public class SaveSubCommand implements SubCommand { - @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { - NicknameArgument argument = new NicknameArgument(); - parent.then(Commands.literal("save").requires(this::canExecute) .executes(this::execute) .then(Commands.argument("nickname", argument) .suggests(argument::suggestOwnNicknames) .executes(this::executeWithArgument)) ); - } @Override public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { Player player = (Player) ctx.getSource().getSender(); - Nickname nickname = NicknameProcessor.getInstance().getCurrentNickname(player); - if (nickname == null) { - throw Exceptions.ERROR_CANNOT_SAVE.create(); - } + Nickname nickname = NicknameProcessor.getInstance().getCurrentNickname(player.getUniqueId(), true); + if (nickname == null) throw Exceptions.cannotSave(); checkSaveSlots(player); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - boolean saved = NicknameProcessor.getInstance().saveNickname(player, nickname.getNickname()); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean saved = NicknameProcessor.getInstance().saveNickname(player.getUniqueId(), player.getName(), nickname.getNickname()); if (saved) { - Bukkit.getScheduler().runTask(SimpleNicks.getInstance(), () -> { + SimpleNicksCore.get().platform().runSync(() -> { NickUtils.refreshDisplayName(player.getUniqueId()); sendFeedback(player, LocaleMessage.SAVE_NICK, nickname); }); @@ -62,13 +56,13 @@ public int execute(@NotNull CommandContext ctx) throws Comma public int executeWithArgument(@NotNull CommandContext ctx) throws CommandSyntaxException { Player player = (Player) ctx.getSource().getSender(); Nickname nickname = ctx.getArgument("nickname", Nickname.class); - NickUtils.nicknameChecks(player, nickname); + NickUtils.nicknameChecks(new PaperSenderContext(player), nickname); checkSaveSlots(player); - if (NicknameProcessor.getInstance().playerAlreadySavedThis(player, nickname.getNickname())) { - throw Exceptions.ERROR_ALREADY_SAVED.create(); + if (NicknameProcessor.getInstance().playerAlreadySavedThis(player.getUniqueId(), nickname.getNickname())) { + throw Exceptions.alreadySaved(); } - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - boolean saved = NicknameProcessor.getInstance().saveNickname(player, nickname.getNickname()); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean saved = NicknameProcessor.getInstance().saveNickname(player.getUniqueId(), player.getName(), nickname.getNickname()); if (saved) { sendFeedback(player, LocaleMessage.SAVE_NICK, nickname); } else { @@ -81,12 +75,11 @@ public int executeWithArgument(@NotNull CommandContext ctx) @Override public boolean canExecute(@NotNull CommandSourceStack css) { if (!(css.getSender() instanceof Player player)) return false; - return permissionNotRequired() || player.hasPermission(NickPermission.NICK_SAVE.getPermission()); + return permissionNotRequired() || player.hasPermission(NickPermission.NICK_SAVE.getPermissionKey()); } public void checkSaveSlots(@NotNull Player player) throws CommandSyntaxException { - int currentUsed = NicknameProcessor.getInstance().getCurrentSavedNickCount(player); - if (currentUsed >= ConfigHandler.getInstance().getMaxSaves()) - throw Exceptions.ERROR_TOO_MANY_SAVED_NAMES.create(); + int currentUsed = NicknameProcessor.getInstance().getCurrentSavedNickCount(player.getUniqueId(), true); + if (currentUsed >= ConfigHandler.getInstance().getMaxSaves()) throw Exceptions.tooManySavedNames(); } } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SetSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SetSubCommand.java similarity index 75% rename from src/main/java/simplexity/simplenicks/commands/subcommands/basic/SetSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SetSubCommand.java index ae75128..758c008 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SetSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SetSubCommand.java @@ -6,50 +6,45 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.NicknameProcessor; import simplexity.simplenicks.commands.arguments.NicknameArgument; import simplexity.simplenicks.commands.subcommands.Exceptions; import simplexity.simplenicks.config.LocaleMessage; import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.platform.PaperSenderContext; import simplexity.simplenicks.saving.Nickname; import simplexity.simplenicks.util.NickPermission; @SuppressWarnings("UnstableApiUsage") public class SetSubCommand implements SubCommand { - @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { - NicknameArgument argument = new NicknameArgument(); - parent.then(Commands.literal("set").requires(this::canExecute) .then(Commands.argument("nickname", argument) .suggests(argument::suggestOwnNicknames) .executes(this::execute) ) ); - } @Override public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { Nickname nickname = ctx.getArgument("nickname", Nickname.class); - if (nickname.getNormalizedNickname().isEmpty()) { - throw Exceptions.ERROR_NICK_IS_NULL.create(); - } + if (nickname.getNormalizedNickname().isEmpty()) throw Exceptions.nickIsNull(); Player player = (Player) ctx.getSource().getSender(); - if (!NickUtils.isValidTags(player, nickname.getNickname())) throw Exceptions.ERROR_TAGS_NOT_PERMITTED.create(); - NickUtils.nicknameChecks(player, nickname); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - boolean succeeded = NicknameProcessor.getInstance().setNickname(player, nickname.getNickname()); + PaperSenderContext sender = new PaperSenderContext(player); + if (!NickUtils.isValidTags(sender, nickname.getNickname())) throw Exceptions.tagsNotPermitted(); + NickUtils.nicknameChecks(sender, nickname); + SimpleNicksCore.get().platform().runAsync(() -> { + boolean succeeded = NicknameProcessor.getInstance().setNickname(player.getUniqueId(), player.getName(), nickname.getNickname()); if (succeeded) { - Bukkit.getScheduler().runTask(SimpleNicks.getInstance(), () -> { - refreshName(player); + SimpleNicksCore.get().platform().runSync(() -> { + refreshName(player.getUniqueId(), true); sendFeedback(player, LocaleMessage.SET_SELF, nickname); }); } else { @@ -62,7 +57,6 @@ public int execute(@NotNull CommandContext ctx) throws Comma @Override public boolean canExecute(@NotNull CommandSourceStack css) { if (!(css.getSender() instanceof Player player)) return false; - return permissionNotRequired() || player.hasPermission(NickPermission.NICK_SET.getPermission()); + return permissionNotRequired() || player.hasPermission(NickPermission.NICK_SET.getPermissionKey()); } - } diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SubCommand.java similarity index 51% rename from src/main/java/simplexity/simplenicks/commands/subcommands/basic/SubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SubCommand.java index 1e2376f..e08b088 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/SubCommand.java @@ -7,58 +7,31 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.apache.commons.lang3.NotImplementedException; -import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.config.ConfigHandler; import simplexity.simplenicks.config.LocaleMessage; import simplexity.simplenicks.logic.NickUtils; import simplexity.simplenicks.saving.Nickname; +import java.util.UUID; import java.util.concurrent.CompletableFuture; @SuppressWarnings("UnstableApiUsage") public interface SubCommand { - /** - * Attaches this subcommand to the given parent. - * - * @param parent Parent node of the command builder - */ void subcommandTo(@NotNull LiteralArgumentBuilder parent); - /** - * Defines the execution logic for this command. - * - * @param ctx CommandSourceStack context - * @return 1 indicating Success - * @throws CommandSyntaxException On failure - */ int execute(@NotNull CommandContext ctx) throws CommandSyntaxException; - /** - * Defines the "can use" logic.
- * ie: Is a player? Has permission? - * - * @param css CommandSourceStack - * @return true if the command can executed, false otherwise - */ boolean canExecute(@NotNull CommandSourceStack css); - /** - * Sends a feedback message to the player, confirming the command went through properly - * - * @param player Player - * @param localeMessage Message - * @param nickname Nickname - */ default void sendFeedback(@NotNull Player player, @Nullable LocaleMessage localeMessage, @Nullable Nickname nickname) { if (nickname == null) nickname = new Nickname("", ""); if (localeMessage == null || localeMessage.getMessage().isEmpty()) return; @@ -68,53 +41,31 @@ default void sendFeedback(@NotNull Player player, @Nullable LocaleMessage locale ); } - /** - * Parses a message for admin commands. - * - * @param message Message to parse - * @param value Placeholder value from message, usually nickname, sometimes something else like a config value - * @param initiator CommandSender who initiated the command, the admin - * @param target OfflinePlayer who this command is being run on - * @return Component parsed message - */ - default Component parseAdminMessage(@NotNull String message, @NotNull String value, @NotNull CommandSender initiator, @NotNull OfflinePlayer target) { - MiniMessage miniMessage = SimpleNicks.getMiniMessage(); + default Component parseAdminMessage(@NotNull String message, @NotNull String value, + @NotNull CommandSender initiator, @NotNull OfflinePlayer target) { Component initiatorName; String targetUserName = target.getName(); - if (targetUserName == null) { - targetUserName = "[Username not found, idk how but you got this error.]"; - } + if (targetUserName == null) targetUserName = "[Username not found]"; if (initiator instanceof Player playerInitiator) { initiatorName = playerInitiator.displayName(); } else { - initiatorName = miniMessage.deserialize(LocaleMessage.SERVER_DISPLAY_NAME.getMessage()); + initiatorName = SimpleNicksCore.get().miniMessage().deserialize(LocaleMessage.SERVER_DISPLAY_NAME.getMessage()); } - return miniMessage.deserialize(message, + return SimpleNicksCore.get().miniMessage().deserialize(message, Placeholder.parsed("value", value), Placeholder.component("initiator", initiatorName), Placeholder.parsed("target", targetUserName) ); } - /** - * Defines suggestions that can be provided to the client.
- * This is not necessary for every command.
- * This function can be defined elsewhere and method referenced instead of using this interface.
- * ie: nicknameArg::suggestOwnNicknames - * - * @param context Command context - * @param builder SuggestionsBuilder object for adding suggestions to - * @param For Paper, generally CommandSourceStack - * @return Suggestions as a CompletableFuture - */ @SuppressWarnings("unused") default @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) { throw new NotImplementedException("listSuggestions was used, but not implemented."); } - default void refreshName(@NotNull OfflinePlayer player) { - if (!player.isOnline()) return; - Bukkit.getScheduler().runTask(SimpleNicks.getInstance(), () -> NickUtils.refreshDisplayName(player.getUniqueId())); + default void refreshName(@NotNull UUID uuid, boolean isOnline) { + if (!isOnline) return; + SimpleNicksCore.get().platform().runSync(() -> NickUtils.refreshDisplayName(uuid)); } default boolean permissionNotRequired() { diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/WhoSubCommand.java b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/WhoSubCommand.java similarity index 71% rename from src/main/java/simplexity/simplenicks/commands/subcommands/basic/WhoSubCommand.java rename to paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/WhoSubCommand.java index 096d3de..f571ed5 100644 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/basic/WhoSubCommand.java +++ b/paper/src/main/java/simplexity/simplenicks/commands/subcommands/basic/WhoSubCommand.java @@ -7,18 +7,16 @@ import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.commands.arguments.NicknameArgument; import simplexity.simplenicks.config.ConfigHandler; import simplexity.simplenicks.config.LocaleMessage; import simplexity.simplenicks.config.MessageUtils; import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.platform.PlayerInfo; import simplexity.simplenicks.saving.Nickname; import simplexity.simplenicks.util.NickPermission; @@ -27,8 +25,6 @@ @SuppressWarnings("UnstableApiUsage") public class WhoSubCommand implements SubCommand { - private static final MiniMessage miniMessage = SimpleNicks.getMiniMessage(); - @Override public void subcommandTo(@NotNull LiteralArgumentBuilder parent) { NicknameArgument argument = new NicknameArgument(); @@ -36,58 +32,52 @@ public void subcommandTo(@NotNull LiteralArgumentBuilder par .then(Commands.argument("nickname", argument) .suggests(argument::suggestAllOnlineNicknames) .executes(this::execute))); - } @Override public int execute(@NotNull CommandContext ctx) throws CommandSyntaxException { CommandSender sender = ctx.getSource().getSender(); Nickname nickname = ctx.getArgument("nickname", Nickname.class); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { - List playersWithNick = NickUtils.getOfflinePlayersByNickname(nickname.getNormalizedNickname()); - + SimpleNicksCore.get().platform().runAsync(() -> { + List playersWithNick = NickUtils.getPlayersByNickname(nickname.getNormalizedNickname()); if (playersWithNick.isEmpty()) { sender.sendRichMessage(LocaleMessage.ERROR_NO_PLAYERS_WITH_THIS_NAME.getMessage()); return; } - Component message = buildWhoMessage(nickname, playersWithNick); sender.sendMessage(message); }); - return Command.SINGLE_SUCCESS; } @NotNull - private Component buildWhoMessage(@NotNull Nickname nickname, @NotNull List players) { - Component header = miniMessage.deserialize(LocaleMessage.WHO_HEADER.getMessage(), + private Component buildWhoMessage(@NotNull Nickname nickname, @NotNull List players) { + Component header = SimpleNicksCore.get().miniMessage().deserialize(LocaleMessage.WHO_HEADER.getMessage(), Placeholder.parsed("value", nickname.getNormalizedNickname())); if (players.isEmpty()) { - return header.append(miniMessage.deserialize(LocaleMessage.INSERT_NONE.getMessage())); + return header.append(SimpleNicksCore.get().miniMessage().deserialize(LocaleMessage.INSERT_NONE.getMessage())); } Component result = header; long now = System.currentTimeMillis(); - for (OfflinePlayer player : players) { - String username = player.getName(); - if (username == null) continue; - - long timeDiffSeconds = (now - player.getLastSeen()) / 1000; - result = result.append(miniMessage.deserialize( + for (PlayerInfo player : players) { + long lastSeen = player.lastLoginMillis(); + long timeDiffSeconds = lastSeen > 0 ? (now - lastSeen) / 1000 : 0; + result = result.append(SimpleNicksCore.get().miniMessage().deserialize( LocaleMessage.WHO_INFO.getMessage(), - Placeholder.parsed("name", username), + Placeholder.parsed("name", player.username()), MessageUtils.getTimeFormat(timeDiffSeconds) )); } return result; } - @Override public boolean canExecute(@NotNull CommandSourceStack css) { CommandSender sender = css.getSender(); - return !ConfigHandler.getInstance().isWhoRequiresPermission() || sender.hasPermission(NickPermission.NICK_WHO.getPermission()); + return !ConfigHandler.getInstance().isWhoRequiresPermission() + || sender.hasPermission(NickPermission.NICK_WHO.getPermissionKey()); } } diff --git a/src/main/java/simplexity/simplenicks/hooks/SNExpansion.java b/paper/src/main/java/simplexity/simplenicks/hooks/SNExpansion.java similarity index 65% rename from src/main/java/simplexity/simplenicks/hooks/SNExpansion.java rename to paper/src/main/java/simplexity/simplenicks/hooks/SNExpansion.java index 8ff7f36..472c672 100644 --- a/src/main/java/simplexity/simplenicks/hooks/SNExpansion.java +++ b/paper/src/main/java/simplexity/simplenicks/hooks/SNExpansion.java @@ -4,12 +4,13 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.config.ConfigHandler; import simplexity.simplenicks.saving.Cache; import simplexity.simplenicks.saving.Nickname; public class SNExpansion extends PlaceholderExpansion { + @Override public @NotNull String getIdentifier() { return "simplenick"; @@ -41,26 +42,14 @@ public String onRequest(@NotNull OfflinePlayer player, @NotNull String params) { } else { nickname = nick.getNickname(); } - if (params.equalsIgnoreCase("mininick")) { - return nickname; - } - if (params.equalsIgnoreCase("prefixed-mininick")) { - return prefix + nickname; - } - if (params.equalsIgnoreCase("stripped")) { - return SimpleNicks.getMiniMessage().stripTags(nickname); - } - if (params.equalsIgnoreCase("prefixed-stripped")) { - return prefix + SimpleNicks.getMiniMessage().stripTags(nickname); - } + if (params.equalsIgnoreCase("mininick")) return nickname; + if (params.equalsIgnoreCase("prefixed-mininick")) return prefix + nickname; + if (params.equalsIgnoreCase("stripped")) return SimpleNicksCore.get().miniMessage().stripTags(nickname); + if (params.equalsIgnoreCase("prefixed-stripped")) return prefix + SimpleNicksCore.get().miniMessage().stripTags(nickname); String parsedNickname = LegacyComponentSerializer.legacySection() - .serialize(SimpleNicks.getMiniMessage().deserialize(nickname)); - if (params.equalsIgnoreCase("nickname")) { - return parsedNickname; - } - if (params.equalsIgnoreCase("prefixed-nickname")) { - return prefix + parsedNickname; - } + .serialize(SimpleNicksCore.get().miniMessage().deserialize(nickname)); + if (params.equalsIgnoreCase("nickname")) return parsedNickname; + if (params.equalsIgnoreCase("prefixed-nickname")) return prefix + parsedNickname; if (params.equalsIgnoreCase("normalized")) { if (nick == null) return null; return nick.getNormalizedNickname(); diff --git a/src/main/java/simplexity/simplenicks/hooks/SNMiniExpansion.java b/paper/src/main/java/simplexity/simplenicks/hooks/SNMiniExpansion.java similarity index 68% rename from src/main/java/simplexity/simplenicks/hooks/SNMiniExpansion.java rename to paper/src/main/java/simplexity/simplenicks/hooks/SNMiniExpansion.java index a35718d..8003991 100644 --- a/src/main/java/simplexity/simplenicks/hooks/SNMiniExpansion.java +++ b/paper/src/main/java/simplexity/simplenicks/hooks/SNMiniExpansion.java @@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.tag.Tag; import org.bukkit.entity.Player; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.config.ConfigHandler; import simplexity.simplenicks.saving.Cache; import simplexity.simplenicks.saving.Nickname; @@ -13,44 +13,33 @@ public class SNMiniExpansion { /** * Builds and registers the MiniPlaceholders expansion for SimpleNicks. - *

- * Registers the following audience-scoped tags (online players only): - *

    - *
  • {@code } — rendered nickname component (MiniMessage-parsed)
  • - *
  • {@code } — config prefix + rendered nickname
  • - *
  • {@code } — nickname with all MiniMessage tags removed
  • - *
  • {@code } — prefix + stripped nickname
  • - *
  • {@code } — normalized (lowercase, stripped) nickname
  • - *
- * Falls back to the player's username when no nickname is set. - *

*/ public static void register() { Expansion.builder("simplenick") .audiencePlaceholder(Player.class, "nick", (player, queue, ctx) -> { Nickname nick = Cache.getInstance().getActiveNickname(player.getUniqueId()); String rawNick = nick != null ? nick.getNickname() : player.getName(); - return Tag.inserting(SimpleNicks.getMiniMessage().deserialize(rawNick)); + return Tag.inserting(SimpleNicksCore.get().miniMessage().deserialize(rawNick)); }) .audiencePlaceholder(Player.class, "prefixed_nick", (player, queue, ctx) -> { Nickname nick = Cache.getInstance().getActiveNickname(player.getUniqueId()); String rawNick = nick != null ? nick.getNickname() : player.getName(); String prefix = ConfigHandler.getInstance().getNickPrefix(); return Tag.inserting( - Component.text(prefix).append(SimpleNicks.getMiniMessage().deserialize(rawNick)) + Component.text(prefix).append(SimpleNicksCore.get().miniMessage().deserialize(rawNick)) ); }) .audiencePlaceholder(Player.class, "stripped", (player, queue, ctx) -> { Nickname nick = Cache.getInstance().getActiveNickname(player.getUniqueId()); String rawNick = nick != null ? nick.getNickname() : player.getName(); - return Tag.inserting(Component.text(SimpleNicks.getMiniMessage().stripTags(rawNick))); + return Tag.inserting(Component.text(SimpleNicksCore.get().miniMessage().stripTags(rawNick))); }) .audiencePlaceholder(Player.class, "prefixed_stripped", (player, queue, ctx) -> { Nickname nick = Cache.getInstance().getActiveNickname(player.getUniqueId()); String rawNick = nick != null ? nick.getNickname() : player.getName(); String prefix = ConfigHandler.getInstance().getNickPrefix(); return Tag.inserting( - Component.text(prefix + SimpleNicks.getMiniMessage().stripTags(rawNick)) + Component.text(prefix + SimpleNicksCore.get().miniMessage().stripTags(rawNick)) ); }) .audiencePlaceholder(Player.class, "normalized", (player, queue, ctx) -> { diff --git a/src/main/java/simplexity/simplenicks/listener/LeaveListener.java b/paper/src/main/java/simplexity/simplenicks/listener/LeaveListener.java similarity index 100% rename from src/main/java/simplexity/simplenicks/listener/LeaveListener.java rename to paper/src/main/java/simplexity/simplenicks/listener/LeaveListener.java diff --git a/src/main/java/simplexity/simplenicks/listener/LoginListener.java b/paper/src/main/java/simplexity/simplenicks/listener/LoginListener.java similarity index 82% rename from src/main/java/simplexity/simplenicks/listener/LoginListener.java rename to paper/src/main/java/simplexity/simplenicks/listener/LoginListener.java index e5f4281..05bd112 100644 --- a/src/main/java/simplexity/simplenicks/listener/LoginListener.java +++ b/paper/src/main/java/simplexity/simplenicks/listener/LoginListener.java @@ -1,28 +1,28 @@ package simplexity.simplenicks.listener; -import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; -import simplexity.simplenicks.SimpleNicks; -import simplexity.simplenicks.saving.Cache; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.saving.Cache; import simplexity.simplenicks.saving.SaveMigrator; import simplexity.simplenicks.saving.SqlHandler; import java.util.UUID; public class LoginListener implements Listener { + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerJoin(PlayerJoinEvent joinEvent) { UUID playerUuid = joinEvent.getPlayer().getUniqueId(); String username = joinEvent.getPlayer().getName(); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { + SimpleNicksCore.get().platform().runAsync(() -> { SqlHandler.getInstance().updatePlayerTable(playerUuid, username); Cache.getInstance().loadCurrentNickname(playerUuid); Cache.getInstance().loadSavedNicknames(playerUuid); - Bukkit.getScheduler().runTask(SimpleNicks.getInstance(), () -> { + SimpleNicksCore.get().platform().runSync(() -> { SaveMigrator.migratePdcNickname(joinEvent.getPlayer()); NickUtils.refreshDisplayName(playerUuid); }); diff --git a/paper/src/main/java/simplexity/simplenicks/platform/BukkitConfigProvider.java b/paper/src/main/java/simplexity/simplenicks/platform/BukkitConfigProvider.java new file mode 100644 index 0000000..cefa4ba --- /dev/null +++ b/paper/src/main/java/simplexity/simplenicks/platform/BukkitConfigProvider.java @@ -0,0 +1,125 @@ +package simplexity.simplenicks.platform; + +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; + +/** + * {@link ConfigProvider} backed by Bukkit's {@link FileConfiguration}. + *

+ * Use {@link #forMainConfig(JavaPlugin)} for {@code config.yml} (delegates to + * {@link JavaPlugin#getConfig()}) and {@link #forFile(File)} for other YAML + * files such as {@code locale.yml}. + *

+ */ +@SuppressWarnings("CallToPrintStackTrace") +public class BukkitConfigProvider implements ConfigProvider { + + private final JavaPlugin plugin; + private final File file; + private YamlConfiguration yamlConfig; + + private BukkitConfigProvider(JavaPlugin plugin, File file) { + this.plugin = plugin; + this.file = file; + } + + /** + * Creates a provider backed by the plugin's main {@code config.yml}. + * + * @param plugin the plugin instance + * @return a config provider for the main config + */ + @NotNull + public static BukkitConfigProvider forMainConfig(@NotNull JavaPlugin plugin) { + return new BukkitConfigProvider(plugin, null); + } + + /** + * Creates a provider backed by the given YAML file. The file is created on + * first {@link #reload()} if it does not exist. + * + * @param file the YAML file to read/write + * @return a config provider for the given file + */ + @NotNull + public static BukkitConfigProvider forFile(@NotNull File file) { + return new BukkitConfigProvider(null, file); + } + + @Override + public void reload() { + if (isMainConfig()) { + plugin.saveDefaultConfig(); + plugin.reloadConfig(); + } else { + try { + if (file.getParentFile() != null) file.getParentFile().mkdirs(); + file.createNewFile(); + yamlConfig = new YamlConfiguration(); + yamlConfig.load(file); + } catch (IOException | InvalidConfigurationException e) { + e.printStackTrace(); + } + } + } + + @Override + public @Nullable String getString(@NotNull String key, @Nullable String defaultValue) { + return config().getString(key, defaultValue); + } + + @Override + public int getInt(@NotNull String key, int defaultValue) { + return config().getInt(key, defaultValue); + } + + @Override + public boolean getBoolean(@NotNull String key, boolean defaultValue) { + return config().getBoolean(key, defaultValue); + } + + @Override + public long getLong(@NotNull String key, long defaultValue) { + return config().getLong(key, defaultValue); + } + + @Override + public void set(@NotNull String key, @Nullable Object value) { + config().set(key, value); + } + + @Override + public void save() { + if (isMainConfig()) { + plugin.saveConfig(); + } else { + try { + yamlConfig.save(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public boolean contains(@NotNull String key) { + return config().contains(key); + } + + private boolean isMainConfig() { + return plugin != null; + } + + @NotNull + private FileConfiguration config() { + if (isMainConfig()) return plugin.getConfig(); + return yamlConfig; + } +} diff --git a/paper/src/main/java/simplexity/simplenicks/platform/PaperPlatformAdapter.java b/paper/src/main/java/simplexity/simplenicks/platform/PaperPlatformAdapter.java new file mode 100644 index 0000000..e1d1eb8 --- /dev/null +++ b/paper/src/main/java/simplexity/simplenicks/platform/PaperPlatformAdapter.java @@ -0,0 +1,153 @@ +package simplexity.simplenicks.platform; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import simplexity.simplenicks.util.ColorTag; +import simplexity.simplenicks.util.FormatTag; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * {@link PlatformAdapter} implementation for the Paper platform. + */ +public class PaperPlatformAdapter implements PlatformAdapter { + + private final JavaPlugin plugin; + private final MiniMessage miniMessage; + private final BukkitConfigProvider configProvider; + private final BukkitConfigProvider localeProvider; + + public PaperPlatformAdapter(@NotNull JavaPlugin plugin) { + this.plugin = plugin; + this.miniMessage = buildMiniMessage(); + this.configProvider = BukkitConfigProvider.forMainConfig(plugin); + this.localeProvider = BukkitConfigProvider.forFile( + new File(plugin.getDataFolder(), "locale.yml") + ); + } + + @Override + public void runAsync(@NotNull Runnable task) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, task); + } + + @Override + public void runSync(@NotNull Runnable task) { + Bukkit.getScheduler().runTask(plugin, task); + } + + @Override + public boolean isPlayerOnline(@NotNull UUID uuid) { + return Bukkit.getPlayer(uuid) != null; + } + + @Override + public @NotNull Optional getPlayerUsername(@NotNull UUID uuid) { + return Optional.ofNullable(Bukkit.getPlayer(uuid)).map(Player::getName); + } + + @Override + public @NotNull Collection getOnlinePlayers() { + return Bukkit.getOnlinePlayers().stream() + .map(Player::getUniqueId) + .collect(Collectors.toList()); + } + + @Override + public void setDisplayName(@NotNull UUID uuid, @NotNull Component displayName) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) return; + player.displayName(displayName); + } + + @Override + public void setTablistName(@NotNull UUID uuid, @NotNull Component tablistName) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) return; + player.playerListName(tablistName); + } + + @Override + public void clearDisplayName(@NotNull UUID uuid) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) return; + player.displayName(null); + } + + @Override + public void clearTablistName(@NotNull UUID uuid) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) return; + player.playerListName(null); + } + + @Override + public boolean hasPermission(@NotNull UUID uuid, @NotNull String permission) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) return false; + return player.hasPermission(permission); + } + + @Override + public @NotNull Path getDataDirectory() { + return plugin.getDataFolder().toPath(); + } + + @Override + public @NotNull Logger getLogger() { + return plugin.getSLF4JLogger(); + } + + @Override + public @NotNull MiniMessage getMiniMessage() { + return miniMessage; + } + + @Override + public @NotNull ConfigProvider getConfigProvider() { + return configProvider; + } + + @Override + public @NotNull ConfigProvider getLocaleProvider() { + return localeProvider; + } + + /** + * Returns the underlying Paper plugin instance. Used by Paper-specific classes + * such as {@link simplexity.simplenicks.saving.SaveMigrator} that need direct + * plugin API access. + * + * @return the plugin instance + */ + @NotNull + public JavaPlugin getPlugin() { + return plugin; + } + + @NotNull + private static MiniMessage buildMiniMessage() { + TagResolver.Builder tagResolver = TagResolver.builder(); + for (ColorTag colorTag : ColorTag.values()) { + tagResolver.resolver(colorTag.getTagResolver()); + } + for (FormatTag formatTag : FormatTag.values()) { + tagResolver.resolver(formatTag.getTagResolver()); + } + return MiniMessage.builder() + .strict(false) + .tags(tagResolver.build()) + .build(); + } +} diff --git a/paper/src/main/java/simplexity/simplenicks/platform/PaperSenderContext.java b/paper/src/main/java/simplexity/simplenicks/platform/PaperSenderContext.java new file mode 100644 index 0000000..9ceee3c --- /dev/null +++ b/paper/src/main/java/simplexity/simplenicks/platform/PaperSenderContext.java @@ -0,0 +1,51 @@ +package simplexity.simplenicks.platform; + +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import simplexity.simplenicks.SimpleNicksCore; + +import java.util.Optional; +import java.util.UUID; + +/** + * {@link SenderContext} backed by a Bukkit {@link CommandSender}. + */ +public class PaperSenderContext implements SenderContext { + + private final CommandSender sender; + + public PaperSenderContext(@NotNull CommandSender sender) { + this.sender = sender; + } + + @Override + public boolean hasPermission(@NotNull String permission) { + return sender.hasPermission(permission); + } + + @Override + public @NotNull Optional getUuid() { + if (sender instanceof Player player) return Optional.of(player.getUniqueId()); + return Optional.empty(); + } + + @Override + public void sendMessage(@NotNull Component message) { + sender.sendMessage(message); + } + + @Override + public boolean isPlayer() { + return sender instanceof Player; + } + + @Override + public @NotNull String getDisplayName() { + if (sender instanceof Player player) { + return SimpleNicksCore.get().miniMessage().serialize(player.displayName()); + } + return "[Console]"; + } +} diff --git a/src/main/java/simplexity/simplenicks/saving/SaveMigrator.java b/paper/src/main/java/simplexity/simplenicks/saving/SaveMigrator.java similarity index 53% rename from src/main/java/simplexity/simplenicks/saving/SaveMigrator.java rename to paper/src/main/java/simplexity/simplenicks/saving/SaveMigrator.java index 2bd3f95..6949f6b 100644 --- a/src/main/java/simplexity/simplenicks/saving/SaveMigrator.java +++ b/paper/src/main/java/simplexity/simplenicks/saving/SaveMigrator.java @@ -10,11 +10,13 @@ import org.bukkit.entity.Player; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; -import simplexity.simplenicks.SimpleNicks; +import simplexity.simplenicks.SimpleNicksCore; import simplexity.simplenicks.config.ConfigHandler; import simplexity.simplenicks.logic.NickUtils; +import simplexity.simplenicks.platform.PaperPlatformAdapter; import java.io.File; import java.io.IOException; @@ -26,76 +28,59 @@ /** * Utility class for migrating nickname save data from older storage formats into the current system. - *

- * This class is intended to be used only once, during the upgrade of the SimpleNicks plugin. - * It reads legacy data files or player PersistentDataContainers (PDC) and converts them into the - * current {@link Cache} and {@link SqlHandler} format. After migration, it renames the old data file - * to prevent duplicate migrations, while saving the old data if the migration didn't go as planned. - *

- *

- * While designed for internal use, this class can be adapted by other plugins to import custom - * nickname data, or for other types of one-time migrations. It should be called during plugin startup, - * and only if there is not already saved data from these users, to prevent overwriting current data. - *

*/ public class SaveMigrator { - /** - * Namespaced key used for storing nicknames in a player's PersistentDataContainer (PDC). - * There was no implementation of 'saved' nicknames with PDC, so only the current nickname is migrated. - */ - public static final NamespacedKey nickNameSave = new NamespacedKey(SimpleNicks.getInstance(), "nickname"); + public static final NamespacedKey nickNameSave = new NamespacedKey(plugin(), "nickname"); - private static final Logger logger = SimpleNicks.getInstance().getSLF4JLogger(); private static final List records = new ArrayList<>(); private static final AtomicInteger processed = new AtomicInteger(); private static final AtomicInteger failed = new AtomicInteger(); private static int taskId; + private static Logger logger() { + return SimpleNicksCore.get().platform().getLogger(); + } + + private static JavaPlugin plugin() { + return ((PaperPlatformAdapter) SimpleNicksCore.get().platform()).getPlugin(); + } + /** * Migrates all nickname data from the legacy YAML file ("nickname_data.yml") into the current database format. - *

- * This method performs the migration asynchronously to avoid blocking the server. It provides - * periodic console logs with progress updates. After migration, the legacy YAML file is renamed - * to "MIGRATED_nickname_data.yml" to prevent repeated attempts. - *

- *

- * If any UUID keys are invalid or cannot be processed, the migration continues with a warning. - * The method logs the number of successfully migrated and failed records. - *

*/ public static void migrateFromYml() { - File dataFile = new File(SimpleNicks.getInstance().getDataFolder(), "nickname_data.yml"); + File dataFile = SimpleNicksCore.get().platform().getDataDirectory().resolve("nickname_data.yml").toFile(); if (!dataFile.exists()) return; FileConfiguration nicknameData = new YamlConfiguration(); try { nicknameData.load(dataFile); } catch (IOException | InvalidConfigurationException e) { - logger.warn("Unable to migrate nicknames from YML: {}", e.getMessage(), e); + logger().warn("Unable to migrate nicknames from YML: {}", e.getMessage(), e); return; } - logger.info("Starting Save Migration"); + logger().info("Starting Save Migration"); Set savedUuids = nicknameData.getKeys(false); int totalUuids = savedUuids.size(); - taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(SimpleNicks.getInstance(), () -> consoleNotifier(totalUuids), 0L, 100L); - Bukkit.getScheduler().runTaskAsynchronously(SimpleNicks.getInstance(), () -> { + taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin(), () -> consoleNotifier(totalUuids), 0L, 100L); + SimpleNicksCore.get().platform().runAsync(() -> { for (String uuidKey : savedUuids) { saveChecks(uuidKey, nicknameData); } boolean success = SqlHandler.getInstance().batchInsertNicknames(records); if (success) { - logger.info("Save data migrated successfully! {} users' data migrated", processed.get()); + logger().info("Save data migrated successfully! {} users' data migrated", processed.get()); } else { - logger.error("Save data was not migrated properly! Please report this to the developers at https://github.com/Simplexity-Development/SimpleNicks!\nPlease be sure to attach any error logs that were created, and your config when you make a report!"); + logger().error("Save data was not migrated properly! Please report this to the developers."); } - if (failed.get() > 0) logger.warn("{} users' data was not successfully migrated", failed.get()); - File backupFile = new File(SimpleNicks.getInstance().getDataFolder(), "MIGRATED_nickname_data.yml"); + if (failed.get() > 0) logger().warn("{} users' data was not successfully migrated", failed.get()); + File backupFile = SimpleNicksCore.get().platform().getDataDirectory().resolve("MIGRATED_nickname_data.yml").toFile(); boolean renamed = dataFile.renameTo(backupFile); if (!renamed) { - logger.warn("Unable to rename 'nickname_data.yml' - if migration was successful, please remove or rename this file so as to not overwrite new save data."); + logger().warn("Unable to rename 'nickname_data.yml' - if migration was successful, please remove or rename this file."); } else { - logger.info("Successfully renamed 'nickname_data.yml' - this migration process will no longer be attempted (unless you rename it back, for some reason, I wouldn't recommend that)"); + logger().info("Successfully renamed 'nickname_data.yml' - this migration process will no longer be attempted."); } Bukkit.getScheduler().cancelTask(taskId); }); @@ -103,12 +88,8 @@ public static void migrateFromYml() { /** * Migrates the nickname stored in a player's PersistentDataContainer (PDC) into the current {@link Cache}. - *

- * After migration, the PDC entry is removed to prevent duplicate storage. This is safe to call - * on individual players, typically during player login events. - *

* - * @param player The player whose PDC nickname should be migrated + * @param player the player whose PDC nickname should be migrated */ public static void migratePdcNickname(@NotNull Player player) { PersistentDataContainer pdc = player.getPersistentDataContainer(); @@ -124,14 +105,6 @@ public static void migratePdcNickname(@NotNull Player player) { pdc.remove(nickNameSave); } - - /** - * Performs sanity checks on a UUID key from the YAML configuration and adds it to the migration - * batch if valid. Processes both the player's current nickname and saved nicknames. - * - * @param uuidKey UUID string of the player - * @param config YAML configuration containing the legacy nickname data - */ private static void saveChecks(@NotNull String uuidKey, @NotNull FileConfiguration config) { UUID uuid; try { @@ -170,35 +143,16 @@ private static void saveChecks(@NotNull String uuidKey, @NotNull FileConfigurati processed.incrementAndGet(); } - /** - * Periodically logs the migration progress to the console. - *

- * Shows the percentage complete and reminds server admins not to restart the server during migration. - *

- * - * @param total Total number of UUIDs being migrated - */ private static void consoleNotifier(int total) { int done = processed.get(); double percent = (done / (double) total) * 100.0; - logger.info("[MIGRATION] {}% complete ({} / {})", String.format("%.1f", percent), done, total); - logger.info("[MIGRATION] ⚠ Do NOT restart the server until migration completes!"); + logger().info("[MIGRATION] {}% complete ({} / {})", String.format("%.1f", percent), done, total); + logger().info("[MIGRATION] ⚠ Do NOT restart the server until migration completes!"); } - - /** - * Debug logging method that only logs messages if {@link ConfigHandler#isDebugMode()} is enabled. - *

- * Accepts printf-style formatting with arguments. - *

- * - * @param message The message format string - * @param args Arguments for the format string - */ private static void debug(@NotNull String message, @NotNull Object... args) { if (ConfigHandler.getInstance().isDebugMode()) { - message = String.format(message, args); - logger.info("[MIGRATION DEBUG] {}", message); + logger().info("[MIGRATION DEBUG] " + String.format(message, args)); } } } diff --git a/paper/src/main/resources/config.yml b/paper/src/main/resources/config.yml new file mode 100644 index 0000000..6c9482a --- /dev/null +++ b/paper/src/main/resources/config.yml @@ -0,0 +1,47 @@ +mysql: + enabled: false + ip: "localhost:3306" + name: simplenicks + username: username1 + password: badpassword! + +# The max amount of characters a nickname should be, not including formatting +# (so a name like BillyBob would only count 'BillyBob' - and would be 8 characters) +# Setting this number to any number below "3" could cause unintended side effects +max-nickname-length: 30 + +# The regex of valid final nickname characters. +# Be warned that putting non-alphanumeric characters may result in issues with other plugins and other unintended side effects +nickname-regex: "[A-Za-z0-9_]+" + +# What should require permission set by a permission plugin? +require-permission: + nick: true + color: true + format: true + who: false + +# Blocks certain names from being used as nicknames +# Expiration times are in days, setting to -1 will make it so it never expires +nickname-protection: + username: + enabled: true + expires: 30 + online: + enabled: false + offline: + enabled: false + expires: 30 + +# How many nicknames can be saved? +max-saves: 5 + +# Should names be changed in tablist? +# (Keep this false if you use any other tablist plugin, there are placeholder API placeholders to use on those) +tablist-nick: false + +# What prefix should be given for players who have a nickname? put "" if you want no prefix +nickname-prefix: "" + +# Development option, this will flood your logs, discouraged from enabling unless asked +debug-mode: false diff --git a/src/main/resources/paper-plugin.yml b/paper/src/main/resources/paper-plugin.yml similarity index 100% rename from src/main/resources/paper-plugin.yml rename to paper/src/main/resources/paper-plugin.yml diff --git a/pom.xml b/pom.xml deleted file mode 100644 index aa43fb2..0000000 --- a/pom.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - 4.0.0 - - simplexity - SimpleNicks - 3.2.2 - jar - - SimpleNicks - - - 21 - UTF-8 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 21 - 21 - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.4 - - - package - - shade - - - false - - - - mojang - - - - - - - - - - - src/main/resources - true - - - - - - - papermc-repo - https://repo.papermc.io/repository/maven-public/ - - - sonatype - https://oss.sonatype.org/content/groups/public/ - - - placeholderapi - https://repo.extendedclip.com/content/repositories/placeholderapi/ - - - - - - io.papermc.paper - paper-api - 1.21.5-R0.1-SNAPSHOT - provided - - - me.clip - placeholderapi - 2.11.5 - provided - - - com.zaxxer - HikariCP - 6.3.0 - - - io.github.miniplaceholders - miniplaceholders-api - 3.2.0 - provided - - - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..5f3ca94 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + maven { url 'https://maven.fabricmc.net/' } + gradlePluginPortal() + } +} + +rootProject.name = 'SimpleNicks' +include 'core', 'paper', 'fabric' diff --git a/src/main/java/simplexity/simplenicks/commands/NicknameProcessor.java b/src/main/java/simplexity/simplenicks/commands/NicknameProcessor.java deleted file mode 100644 index 97bc139..0000000 --- a/src/main/java/simplexity/simplenicks/commands/NicknameProcessor.java +++ /dev/null @@ -1,161 +0,0 @@ -package simplexity.simplenicks.commands; - -import org.bukkit.OfflinePlayer; -import org.jetbrains.annotations.NotNull; -import simplexity.simplenicks.saving.Cache; -import simplexity.simplenicks.saving.Nickname; -import simplexity.simplenicks.saving.SqlHandler; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * Handles the high-level logic for nickname management. - *

- * This class acts as the main entry point for commands or other - * external systems that want to interact with nicknames. - * It delegates persistence and caching to {@link Cache} and {@link SqlHandler}. - *

- */ -@SuppressWarnings("UnusedReturnValue") -public class NicknameProcessor { - private static NicknameProcessor instance; - - private NicknameProcessor() { - } - - public static NicknameProcessor getInstance() { - if (instance == null) instance = new NicknameProcessor(); - return instance; - } - - /** - * Sets a player's active nickname. - * - * @param player The player whose nickname is being set. - * @param nickname The nickname string to assign. - * @return {@code true} if the nickname was set successfully, - * {@code false} if it failed to persist or cache. - */ - public boolean setNickname(@NotNull OfflinePlayer player, @NotNull String nickname) { - UUID playerUuid = player.getUniqueId(); - String username = player.getName(); - if (username == null) return false; - return Cache.getInstance().setActiveNickname(playerUuid, username, nickname); - } - - /** - * Resets a player's nickname back to their original username. - * - * @param player The player whose nickname should be reset. - * @return {@code true} if the nickname was cleared successfully, - * {@code false} if the database update failed. - */ - public boolean resetNickname(@NotNull OfflinePlayer player) { - UUID playerUuid = player.getUniqueId(); - return Cache.getInstance().clearCurrentNickname(playerUuid); - } - - /** - * Saves a nickname to the player's list of saved nicknames. - * - * @param player The player saving the nickname. - * @param nickname The nickname string to save. - * @return {@code true} if the nickname was saved successfully, - * {@code false} if it failed to persist or cache. - */ - public boolean saveNickname(@NotNull OfflinePlayer player, @NotNull String nickname) { - UUID playerUuid = player.getUniqueId(); - String username = player.getName(); - if (username == null) return false; - return Cache.getInstance().saveNickname(playerUuid, username, nickname); - } - - /** - * Deletes a previously saved nickname for a player. - * - * @param player The player who owns the nickname. - * @param nickname The nickname string to delete. - * @return {@code true} if the nickname was deleted successfully, - * {@code false} if no such nickname was found or persistence failed. - */ - public boolean deleteNickname(@NotNull OfflinePlayer player, @NotNull String nickname) { - UUID playerUuid = player.getUniqueId(); - return Cache.getInstance().deleteSavedNickname(playerUuid, nickname); - } - - - /** - * Gets all saved nicknames for a player. - *

- * Uses the in-memory cache if the player is online, - * otherwise queries SQL directly. - *

- * - * @param player The player whose saved nicknames to retrieve. - * @return A non-null list of {@link Nickname}. Empty if none exist. - */ - @NotNull - public List getSavedNicknames(@NotNull OfflinePlayer player) { - UUID playerUuid = player.getUniqueId(); - boolean online = player.isOnline(); - if (online) return Cache.getInstance().getSavedNicknames(playerUuid); - List nicks = SqlHandler.getInstance().getSavedNicknamesForPlayer(playerUuid); - if (nicks == null) return new ArrayList<>(); - return nicks; - } - - /** - * Gets the currently active nickname for a player. - *

- * Uses the in-memory cache if the player is online, - * otherwise queries SQL directly. - *

- * - * @param player The player whose nickname to get. - * @return The current {@link Nickname}, or {@code null} if none is set. - */ - @Nullable - public Nickname getCurrentNickname(@NotNull OfflinePlayer player) { - UUID playerUuid = player.getUniqueId(); - boolean online = player.isOnline(); - if (online) return Cache.getInstance().getActiveNickname(playerUuid); - return SqlHandler.getInstance().getCurrentNicknameForPlayer(playerUuid); - } - - /** - * Gets the number of saved nicknames for a player. - *

- * Uses the in-memory cache if the player is online, - * otherwise queries SQL directly. - *

- * - * @param player The player whose saved nicknames to count. - * @return The number of saved nicknames. - */ - public int getCurrentSavedNickCount(@NotNull OfflinePlayer player) { - UUID playerUuid = player.getUniqueId(); - boolean online = player.isOnline(); - if (online) return Cache.getInstance().getSavedNickCount(playerUuid); - List savedNicks = SqlHandler.getInstance().getSavedNicknamesForPlayer(playerUuid); - if (savedNicks == null || savedNicks.isEmpty()) return 0; - return savedNicks.size(); - } - - - /** - * Checks if a player has already saved the given nickname. - * - * @param player The player to check. - * @param nickname The nickname string to search for. - * @return {@code true} if the player already saved this nickname, - * {@code false} otherwise. - */ - public boolean playerAlreadySavedThis(@NotNull OfflinePlayer player, @NotNull String nickname) { - UUID playerUuid = player.getUniqueId(); - return SqlHandler.getInstance().userAlreadySavedThisName(playerUuid, nickname); - } - -} diff --git a/src/main/java/simplexity/simplenicks/commands/subcommands/Exceptions.java b/src/main/java/simplexity/simplenicks/commands/subcommands/Exceptions.java deleted file mode 100644 index 8c03569..0000000 --- a/src/main/java/simplexity/simplenicks/commands/subcommands/Exceptions.java +++ /dev/null @@ -1,115 +0,0 @@ -package simplexity.simplenicks.commands.subcommands; - -import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; -import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -import io.papermc.paper.command.brigadier.MessageComponentSerializer; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import simplexity.simplenicks.SimpleNicks; -import simplexity.simplenicks.config.ConfigHandler; -import simplexity.simplenicks.config.LocaleMessage; - -@SuppressWarnings("UnstableApiUsage") -public class Exceptions { - - private static final MiniMessage miniMessage = SimpleNicks.getMiniMessage(); - - public static final SimpleCommandExceptionType ERROR_NICK_IS_NULL = new SimpleCommandExceptionType( - MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_NICK_IS_NULL.getMessage() - ) - ) - ); - - public static final SimpleCommandExceptionType ERROR_EMPTY_NICK_AFTER_PARSE = new SimpleCommandExceptionType( - MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_INVALID_NICK_EMPTY.getMessage() - ) - ) - ); - - public static final SimpleCommandExceptionType ERROR_CANNOT_SAVE = new SimpleCommandExceptionType( - MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_SAVE_FAILURE.getMessage() - ) - ) - ); - - - public static final SimpleCommandExceptionType ERROR_TOO_MANY_SAVED_NAMES = new SimpleCommandExceptionType( - MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_TOO_MANY_TO_SAVE.getMessage() - ) - ) - ); - - public static final DynamicCommandExceptionType ERROR_LENGTH = new DynamicCommandExceptionType( - nickname -> MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_INVALID_NICK_LENGTH.getMessage(), - Placeholder.unparsed("value", String.valueOf(ConfigHandler.getInstance().getMaxLength())), - Placeholder.unparsed("name", nickname.toString()) - ) - ) - ); - - public static final DynamicCommandExceptionType ERROR_REGEX = new DynamicCommandExceptionType( - nickname -> MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_INVALID_NICK.getMessage(), - Placeholder.unparsed("regex", ConfigHandler.getInstance().getRegexString()) - ) - ) - ); - - public static final DynamicCommandExceptionType INVALID_PLAYER_SPECIFIED = new DynamicCommandExceptionType( - playerName -> MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_INVALID_PLAYER.getMessage(), - Placeholder.unparsed("player_name", playerName.toString()) - ) - ) - ); - - public static final DynamicCommandExceptionType ERROR_NICKNAME_IS_SOMEONES_USERNAME = new DynamicCommandExceptionType( - nickname -> MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_INVALID_OTHER_PLAYERS_USERNAME.getMessage(), - Placeholder.unparsed("value", nickname.toString()) - ) - ) - ); - - - public static final DynamicCommandExceptionType ERROR_SOMEONE_USING_THAT_NICKNAME = new DynamicCommandExceptionType( - nickname -> MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_INVALID_OTHER_PLAYERS_NICKNAME.getMessage(), - Placeholder.unparsed("value", nickname.toString()) - ) - ) - ); - - public static final SimpleCommandExceptionType ERROR_TAGS_NOT_PERMITTED = new SimpleCommandExceptionType( - MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_INVALID_TAGS.getMessage() - ) - ) - ); - - public static final SimpleCommandExceptionType ERROR_ALREADY_SAVED = new SimpleCommandExceptionType( - MessageComponentSerializer.message().serialize( - miniMessage.deserialize( - LocaleMessage.ERROR_ALREADY_SAVED.getMessage() - ) - ) - ); - - - -} diff --git a/src/main/java/simplexity/simplenicks/config/LocaleHandler.java b/src/main/java/simplexity/simplenicks/config/LocaleHandler.java deleted file mode 100644 index 8e4be4a..0000000 --- a/src/main/java/simplexity/simplenicks/config/LocaleHandler.java +++ /dev/null @@ -1,88 +0,0 @@ -package simplexity.simplenicks.config; - -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import simplexity.simplenicks.SimpleNicks; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@SuppressWarnings({"CallToPrintStackTrace", "CollectionAddAllCanBeReplacedWithConstructor", "ResultOfMethodCallIgnored"}) -public class LocaleHandler { - private static LocaleHandler instance; - private final String fileName = "locale.yml"; - private final File dataFile = new File(SimpleNicks.getInstance().getDataFolder(), fileName); - private FileConfiguration locale = new YamlConfiguration(); - - private LocaleHandler() { - try { - dataFile.getParentFile().mkdirs(); - dataFile.createNewFile(); - reloadLocale(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static LocaleHandler getInstance() { - if (instance == null) { - instance = new LocaleHandler(); - } - return instance; - } - - public void reloadLocale() { - try { - locale.load(dataFile); - populateLocale(); - sortLocale(); - saveLocale(); - } catch (IOException | InvalidConfigurationException e) { - e.printStackTrace(); - } - } - - - private void populateLocale() { - Set missing = new HashSet<>(Arrays.asList(LocaleMessage.values())); - for (LocaleMessage localeMessage : LocaleMessage.values()) { - if (locale.contains(localeMessage.getPath())) { - localeMessage.setMessage(locale.getString(localeMessage.getPath())); - missing.remove(localeMessage); - } - } - - for (LocaleMessage localeMessage : missing) { - locale.set(localeMessage.getPath(), localeMessage.getMessage()); - } - - - } - - private void sortLocale() { - FileConfiguration newLocale = new YamlConfiguration(); - List keys = new ArrayList<>(); - keys.addAll(locale.getKeys(true)); - Collections.sort(keys); - for (String key : keys) { - newLocale.set(key, locale.getString(key)); - } - locale = newLocale; - } - - private void saveLocale() { - try { - locale.save(dataFile); - } catch (IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/src/main/java/simplexity/simplenicks/listener/QuitListener.java b/src/main/java/simplexity/simplenicks/listener/QuitListener.java deleted file mode 100644 index f76c9a3..0000000 --- a/src/main/java/simplexity/simplenicks/listener/QuitListener.java +++ /dev/null @@ -1,16 +0,0 @@ -package simplexity.simplenicks.listener; - -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerQuitEvent; -import simplexity.simplenicks.saving.Cache; - -public class QuitListener implements Listener { - - @EventHandler - public void onPlayerQuit(PlayerQuitEvent quitEvent) { - Player player = quitEvent.getPlayer(); - Cache.getInstance().removePlayerFromCache(player.getUniqueId()); - } -} diff --git a/src/main/java/simplexity/simplenicks/util/ColorTag.java b/src/main/java/simplexity/simplenicks/util/ColorTag.java deleted file mode 100644 index d626ee6..0000000 --- a/src/main/java/simplexity/simplenicks/util/ColorTag.java +++ /dev/null @@ -1,35 +0,0 @@ -package simplexity.simplenicks.util; - -import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; -import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionDefault; -import org.jetbrains.annotations.NotNull; - -public enum ColorTag { - //Nickname Perms - HEX_COLOR(new Permission("simplenick.color.basic", PermissionDefault.OP), StandardTags.color()), - GRADIENT(new Permission("simplenick.color.gradient", PermissionDefault.OP), StandardTags.gradient()), - RAINBOW(new Permission("simplenick.color.rainbow", PermissionDefault.OP), StandardTags.rainbow()), - RESET(new Permission("simplenick.color.reset", PermissionDefault.OP), StandardTags.reset()); - - - private final Permission permission; - private final TagResolver resolver; - - - ColorTag(Permission permission, TagResolver resolver) { - this.permission = permission; - this.resolver = resolver; - } - - @NotNull - public Permission getPermission() { - return permission; - } - - @NotNull - public TagResolver getTagResolver() { - return resolver; - } -} diff --git a/src/main/java/simplexity/simplenicks/util/FormatTag.java b/src/main/java/simplexity/simplenicks/util/FormatTag.java deleted file mode 100644 index 2ac9335..0000000 --- a/src/main/java/simplexity/simplenicks/util/FormatTag.java +++ /dev/null @@ -1,39 +0,0 @@ -package simplexity.simplenicks.util; - -import net.kyori.adventure.text.format.TextDecoration; -import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; -import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionDefault; -import org.jetbrains.annotations.NotNull; - -public enum FormatTag { - - UNDERLINE(new Permission("simplenick.format.underline", PermissionDefault.OP), StandardTags.decorations(TextDecoration.UNDERLINED)), - ITALIC(new Permission("simplenick.format.italic", PermissionDefault.OP), StandardTags.decorations(TextDecoration.ITALIC)), - STRIKETHROUGH(new Permission("simplenick.format.strikethrough", PermissionDefault.OP), StandardTags.decorations(TextDecoration.STRIKETHROUGH)), - BOLD(new Permission("simplenick.format.bold", PermissionDefault.OP), StandardTags.decorations(TextDecoration.BOLD)), - OBFUSCATED(new Permission("simplenick.format.obfuscated", PermissionDefault.OP), StandardTags.decorations(TextDecoration.OBFUSCATED)), - HOVER(new Permission("simplenick.format.hover", PermissionDefault.FALSE), StandardTags.hoverEvent()), - FONT(new Permission("simplenick.format.font", PermissionDefault.FALSE), StandardTags.font()),; - - - private final Permission permission; - private final TagResolver resolver; - - - FormatTag(Permission permission, TagResolver resolver) { - this.permission = permission; - this.resolver = resolver; - } - - @NotNull - public Permission getPermission() { - return permission; - } - - @NotNull - public TagResolver getTagResolver() { - return resolver; - } -} diff --git a/src/main/java/simplexity/simplenicks/util/NickPermission.java b/src/main/java/simplexity/simplenicks/util/NickPermission.java deleted file mode 100644 index 2f2436c..0000000 --- a/src/main/java/simplexity/simplenicks/util/NickPermission.java +++ /dev/null @@ -1,36 +0,0 @@ -package simplexity.simplenicks.util; - -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionDefault; -import org.jetbrains.annotations.NotNull; - -public enum NickPermission { - // name, description, default, children - NICK_ADMIN(new Permission("simplenick.admin", "Base permission for all admin commands", PermissionDefault.OP)), - NICK_ADMIN_SET(new Permission("simplenick.admin.set", "Allows an admin to set another user's nickname", PermissionDefault.OP)), - NICK_ADMIN_RESET(new Permission("simplenick.admin.reset", "Allows an admin to reset another user's nickname", PermissionDefault.OP)), - NICK_ADMIN_DELETE(new Permission("simplenick.admin.delete", "Allows an admin to delete another user's saved nickname", PermissionDefault.OP)), - NICK_ADMIN_LOOKUP(new Permission("simplenick.admin.lookup", "Allows an admin to look up someone's nickname and saved nicknames based off their username", PermissionDefault.OP)), - NICK_COMMAND(new Permission("simplenick.nick", "Base permission for all nickname commands", PermissionDefault.TRUE)), - NICK_SET(new Permission("simplenick.nick.set", "Allows someone to set their own nickname", PermissionDefault.OP)), - NICK_SAVE(new Permission("simplenick.nick.save", "Allows someone to save nicknames", PermissionDefault.OP)), - NICK_WHO(new Permission("simplenick.nick.who", "Allows someone to see the actual username of someone based on their nickname", PermissionDefault.TRUE)), - NICK_HELP(new Permission("simplenick.nick.help", "Shows the help messages", PermissionDefault.TRUE)), - NICK_BYPASS_USERNAME(new Permission("simplenick.bypass.username", "Allows a user to nickname themselves the same as someone else's username on this server", PermissionDefault.FALSE)), - NICK_BYPASS_LENGTH(new Permission("simplenick.bypass.length", "Allows a user to bypass the configured max length of a nickname", PermissionDefault.FALSE)), - NICK_BYPASS_REGEX(new Permission("simplenick.bypass.regex", "Allows a user to bypass the configured regex", PermissionDefault.FALSE)), - NICK_BYPASS_NICK_PROTECTION(new Permission("simplenick.bypass.nick-protection", "Allows a user to nickname themselves the same nickname as another user", PermissionDefault.FALSE)), - NICK_RELOAD(new Permission("simplenick.reload", "Allows a user to reload the config", PermissionDefault.OP)); - - private final Permission permission; - - NickPermission(Permission permission) { - this.permission = permission; - } - - @NotNull - public Permission getPermission() { - return permission; - } - -}