diff --git a/pom.xml b/pom.xml index d1c3b74..3871796 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.noximity remmychat - 1.3.0 + 1.4.0 jar remmychat @@ -63,6 +63,11 @@ placeholderapi https://repo.extendedclip.com/content/repositories/placeholderapi/ + + + luckperms + https://repo.lucko.me/ + @@ -78,6 +83,12 @@ 2.11.6 provided + + net.luckperms + api + 5.4 + provided + org.xerial sqlite-jdbc diff --git a/src/main/java/com/noximity/remmyChat/RemmyChat.java b/src/main/java/com/noximity/remmyChat/RemmyChat.java index 5deded4..a9847e6 100644 --- a/src/main/java/com/noximity/remmyChat/RemmyChat.java +++ b/src/main/java/com/noximity/remmyChat/RemmyChat.java @@ -11,6 +11,7 @@ import com.noximity.remmyChat.listeners.ChatListener; import com.noximity.remmyChat.services.ChatService; import com.noximity.remmyChat.services.FormatService; +import com.noximity.remmyChat.services.PermissionService; import com.noximity.remmyChat.utils.MessageUtils; import org.bukkit.plugin.java.JavaPlugin; @@ -22,6 +23,7 @@ public final class RemmyChat extends JavaPlugin { private ChatService chatService; private FormatService formatService; private DatabaseManager databaseManager; + private PermissionService permissionService; @Override public void onEnable() { @@ -31,6 +33,7 @@ public void onEnable() { this.messages = new Messages(this); this.databaseManager = new DatabaseManager(this); + this.permissionService = new PermissionService(this); this.formatService = new FormatService(this); this.chatService = new ChatService(this); @@ -54,12 +57,10 @@ public void onEnable() { @Override public void onDisable() { - // Save all user data before shutdown if (chatService != null) { chatService.saveAllUsers(); } - // Close database connection if (databaseManager != null) { databaseManager.close(); } @@ -90,4 +91,9 @@ public FormatService getFormatService() { public DatabaseManager getDatabaseManager() { return databaseManager; } -} \ No newline at end of file + + public PermissionService getPermissionService() { + return permissionService; + } +} + diff --git a/src/main/java/com/noximity/remmyChat/RemmyChatPlaceholders.java b/src/main/java/com/noximity/remmyChat/RemmyChatPlaceholders.java index 96ec391..fdfa4fa 100644 --- a/src/main/java/com/noximity/remmyChat/RemmyChatPlaceholders.java +++ b/src/main/java/com/noximity/remmyChat/RemmyChatPlaceholders.java @@ -51,8 +51,18 @@ public boolean persist() { } if (params.equalsIgnoreCase("channel")) { - ChatUser user = plugin.getChatService().getChatUser(player.getUniqueId()); - return user.getCurrentChannel(); + if (player.isOnline()) { + ChatUser user = plugin.getChatService().getChatUser(player.getUniqueId()); + return user.getCurrentChannel(); + } + return plugin.getConfigManager().getDefaultChannel().getName(); + } + + if (params.equalsIgnoreCase("group") && plugin.getPermissionService().isLuckPermsHooked()) { + if (player.isOnline()) { + return plugin.getPermissionService().getPrimaryGroup(player.getPlayer()); + } + return ""; } return null; diff --git a/src/main/java/com/noximity/remmyChat/config/ConfigManager.java b/src/main/java/com/noximity/remmyChat/config/ConfigManager.java index b4c885b..72c0d0b 100644 --- a/src/main/java/com/noximity/remmyChat/config/ConfigManager.java +++ b/src/main/java/com/noximity/remmyChat/config/ConfigManager.java @@ -2,6 +2,7 @@ import com.noximity.remmyChat.RemmyChat; import com.noximity.remmyChat.models.Channel; +import com.noximity.remmyChat.models.GroupFormat; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; @@ -13,7 +14,14 @@ public class ConfigManager { private final RemmyChat plugin; private FileConfiguration config; private final Map channels = new HashMap<>(); + private final Map groupFormats = new HashMap<>(); + private final Map hoverTemplates = new HashMap<>(); + private final Map channelPrefixTemplates = new HashMap<>(); + private final Map groupPrefixTemplates = new HashMap<>(); + private final Map nameStyleTemplates = new HashMap<>(); private boolean urlFormattingEnabled; + private boolean useGroupFormat; + private String chatFormat; public ConfigManager(RemmyChat plugin) { this.plugin = plugin; @@ -23,8 +31,62 @@ public ConfigManager(RemmyChat plugin) { private void loadConfig() { plugin.saveDefaultConfig(); this.config = plugin.getConfig(); + loadTemplates(); loadChannels(); + loadGroupFormats(); loadUrlFormatting(); + this.useGroupFormat = config.getBoolean("features.use-group-format", true); + this.chatFormat = config.getString("chat-format", "%channel_prefix% %group_prefix%%name%: %message%"); + } + + private void loadTemplates() { + // Load hover templates + ConfigurationSection hoversSection = config.getConfigurationSection("templates.hovers"); + if (hoversSection != null) { + for (String key : hoversSection.getKeys(false)) { + String template = hoversSection.getString(key); + if (template != null) { + hoverTemplates.put(key, template); + plugin.getLogger().info("Loaded hover template: " + key); + } + } + } + + // Load channel prefix templates + ConfigurationSection channelPrefixesSection = config.getConfigurationSection("templates.channel-prefixes"); + if (channelPrefixesSection != null) { + for (String key : channelPrefixesSection.getKeys(false)) { + String template = channelPrefixesSection.getString(key); + if (template != null) { + channelPrefixTemplates.put(key, template); + plugin.getLogger().info("Loaded channel prefix template: " + key); + } + } + } + + // Load group prefix templates + ConfigurationSection groupPrefixesSection = config.getConfigurationSection("templates.group-prefixes"); + if (groupPrefixesSection != null) { + for (String key : groupPrefixesSection.getKeys(false)) { + String template = groupPrefixesSection.getString(key); + if (template != null) { + groupPrefixTemplates.put(key, template); + plugin.getLogger().info("Loaded group prefix template: " + key); + } + } + } + + // Load name style templates + ConfigurationSection nameStylesSection = config.getConfigurationSection("templates.name-styles"); + if (nameStylesSection != null) { + for (String key : nameStylesSection.getKeys(false)) { + String template = nameStylesSection.getString(key); + if (template != null) { + nameStyleTemplates.put(key, template); + plugin.getLogger().info("Loaded name style template: " + key); + } + } + } } private void loadChannels() { @@ -35,45 +97,55 @@ private void loadChannels() { } for (String key : channelsSection.getKeys(false)) { - String format = channelsSection.getString(key + ".format"); - String permission = channelsSection.getString(key + ".permission"); + String permission = channelsSection.getString(key + ".permission", ""); double radius = channelsSection.getDouble(key + ".radius", -1); + String prefix = channelsSection.getString(key + ".prefix", ""); + String hover = channelsSection.getString(key + ".hover", "player-info"); - Channel channel = new Channel(key, format, permission, radius); + Channel channel = new Channel(key, permission, radius, prefix, hover); channels.put(key, channel); plugin.getLogger().info("Loaded channel: " + key); } } - private void loadUrlFormatting() { - this.urlFormattingEnabled = config.getBoolean("url-formatting.enabled", true); - - if (!config.isSet("url-formatting.color")) { - config.set("url-formatting.color", "#3498DB"); + private void loadGroupFormats() { + ConfigurationSection groupsSection = config.getConfigurationSection("groups"); + if (groupsSection == null) { + plugin.getLogger().info("No group formats configured, using default name styles only."); + return; } - if (!config.isSet("url-formatting.underline")) { - config.set("url-formatting.underline", true); - } + for (String key : groupsSection.getKeys(false)) { + String nameStyle = groupsSection.getString(key + ".name-style", "default"); + String prefix = groupsSection.getString(key + ".prefix", ""); - if (!config.isSet("url-formatting.hover")) { - config.set("url-formatting.hover", true); - } + GroupFormat groupFormat = new GroupFormat(key, nameStyle, prefix); + groupFormats.put(key, groupFormat); - if (!config.isSet("url-formatting.hover-text")) { - config.set("url-formatting.hover-text", "<#AAAAAA>Click to open"); + plugin.getLogger().info("Loaded group format: " + key); } + } - plugin.saveConfig(); + private void loadUrlFormatting() { + this.urlFormattingEnabled = config.getBoolean("url-formatting.enabled", true); } public void reloadConfig() { plugin.reloadConfig(); this.config = plugin.getConfig(); channels.clear(); + groupFormats.clear(); + hoverTemplates.clear(); + channelPrefixTemplates.clear(); + groupPrefixTemplates.clear(); + nameStyleTemplates.clear(); + loadTemplates(); loadChannels(); + loadGroupFormats(); loadUrlFormatting(); + this.useGroupFormat = config.getBoolean("features.use-group-format", true); + this.chatFormat = config.getString("chat-format", "%channel_prefix% %group_prefix%%name%: %message%"); } public boolean isPlayerFormattingAllowed() { @@ -88,6 +160,38 @@ public Channel getChannel(String name) { return channels.get(name); } + public GroupFormat getGroupFormat(String name) { + return groupFormats.get(name); + } + + public Map getGroupFormats() { + return groupFormats; + } + + public String getHoverTemplate(String name) { + return hoverTemplates.getOrDefault(name, ""); + } + + public String getChannelPrefixTemplate(String name) { + return channelPrefixTemplates.getOrDefault(name, ""); + } + + public String getGroupPrefixTemplate(String name) { + return groupPrefixTemplates.getOrDefault(name, ""); + } + + public String getNameStyleTemplate(String name) { + return nameStyleTemplates.getOrDefault(name, nameStyleTemplates.getOrDefault("default", "<#4A90E2>%player_name%")); + } + + public String getChatFormat() { + return chatFormat; + } + + public boolean isUseGroupFormat() { + return useGroupFormat; + } + public Channel getDefaultChannel() { String defaultChannel = config.getString("default-channel"); return channels.getOrDefault(defaultChannel, null); @@ -104,4 +208,5 @@ public boolean isLinkClickEnabled() { public int getCooldown() { return config.getInt("chat-cooldown", 0); } -} \ No newline at end of file +} + diff --git a/src/main/java/com/noximity/remmyChat/database/DatabaseManager.java b/src/main/java/com/noximity/remmyChat/database/DatabaseManager.java index 36c4220..1399dc9 100644 --- a/src/main/java/com/noximity/remmyChat/database/DatabaseManager.java +++ b/src/main/java/com/noximity/remmyChat/database/DatabaseManager.java @@ -14,6 +14,7 @@ public class DatabaseManager { private final RemmyChat plugin; private Connection connection; private final String dbName = "remmychat.db"; + private File databaseFile; public DatabaseManager(RemmyChat plugin) { this.plugin = plugin; @@ -21,23 +22,26 @@ public DatabaseManager(RemmyChat plugin) { } private void initialize() { - File dataFolder = new File(plugin.getDataFolder(), dbName); - if (!dataFolder.exists()) { - try { - plugin.getDataFolder().mkdir(); - } catch (Exception e) { - plugin.getLogger().log(Level.SEVERE, "Failed to create plugin data folder", e); - } + if (!plugin.getDataFolder().exists()) { + plugin.getDataFolder().mkdirs(); } + this.databaseFile = new File(plugin.getDataFolder(), dbName); + try { Class.forName("org.sqlite.JDBC"); - connection = DriverManager.getConnection("jdbc:sqlite:" + dataFolder); + String url = "jdbc:sqlite:" + databaseFile.getAbsolutePath(); + + connection = DriverManager.getConnection(url); + try (Statement stmt = connection.createStatement()) { + stmt.execute("PRAGMA journal_mode = DELETE;"); + stmt.execute("PRAGMA foreign_keys = ON;"); + } createTables(); - plugin.getLogger().info("SQLite database connection established!"); + plugin.getLogger().info("SQLite database connection established at: " + databaseFile.getAbsolutePath()); } catch (SQLException | ClassNotFoundException e) { - plugin.getLogger().log(Level.SEVERE, "Failed to initialize SQLite database", e); + plugin.getLogger().log(Level.SEVERE, "Failed to initialize SQLite database: " + e.getMessage(), e); } } @@ -46,7 +50,8 @@ private void createTables() { statement.execute("CREATE TABLE IF NOT EXISTS users (" + "uuid VARCHAR(36) PRIMARY KEY, " + "msg_toggle BOOLEAN DEFAULT 1, " + - "social_spy BOOLEAN DEFAULT 0)"); + "social_spy BOOLEAN DEFAULT 0, " + + "current_channel VARCHAR(32) DEFAULT 'global')"); plugin.getLogger().info("Database tables created or already exist"); } catch (SQLException e) { @@ -54,6 +59,21 @@ private void createTables() { } } + private boolean ensureConnection() { + try { + if (connection == null || connection.isClosed()) { + String url = "jdbc:sqlite:" + databaseFile.getAbsolutePath(); + connection = DriverManager.getConnection(url); + plugin.getLogger().info("Reconnected to database"); + return true; + } + return true; + } catch (SQLException e) { + plugin.getLogger().log(Level.SEVERE, "Failed to reconnect to database", e); + return false; + } + } + public void close() { try { if (connection != null && !connection.isClosed()) { @@ -66,45 +86,62 @@ public void close() { } public void saveUserPreferences(ChatUser user) { - // Run asynchronously if the plugin is enabled, otherwise run synchronously if (plugin.isEnabled()) { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { saveUserPreferencesSync(user); }); } else { - saveUserPreferencesSync(user); + try { + if (connection != null && !connection.isClosed()) { + connection.close(); + } + ensureConnection(); + saveUserPreferencesSync(user); + } catch (SQLException e) { + plugin.getLogger().log(Level.WARNING, "Could not refresh connection during shutdown: " + e.getMessage()); + } } } private void saveUserPreferencesSync(ChatUser user) { + if (!ensureConnection()) { + plugin.getLogger().warning("Cannot save user preferences - no database connection"); + return; + } + try (PreparedStatement ps = connection.prepareStatement( - "INSERT OR REPLACE INTO users (uuid, msg_toggle, social_spy) VALUES (?, ?, ?)")) { + "INSERT OR REPLACE INTO users (uuid, msg_toggle, social_spy, current_channel) VALUES (?, ?, ?, ?)")) { ps.setString(1, user.getUuid().toString()); ps.setBoolean(2, user.isMsgToggle()); ps.setBoolean(3, user.isSocialSpy()); + ps.setString(4, user.getCurrentChannel()); ps.executeUpdate(); } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "Failed to save user preferences", e); + plugin.getLogger().log(Level.SEVERE, "Failed to save user preferences: " + e.getMessage(), e); } } public ChatUser loadUserPreferences(UUID uuid, String defaultChannel) { try (PreparedStatement ps = connection.prepareStatement( - "SELECT msg_toggle, social_spy FROM users WHERE uuid = ?")) { + "SELECT msg_toggle, social_spy, current_channel FROM users WHERE uuid = ?")) { ps.setString(1, uuid.toString()); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { boolean msgToggle = rs.getBoolean("msg_toggle"); boolean socialSpy = rs.getBoolean("social_spy"); - return new ChatUser(uuid, defaultChannel, msgToggle, socialSpy); + String savedChannel = rs.getString("current_channel"); + + String channelToUse = savedChannel != null && !savedChannel.isEmpty() ? + savedChannel : defaultChannel; + + return new ChatUser(uuid, channelToUse, msgToggle, socialSpy); } } } catch (SQLException e) { plugin.getLogger().log(Level.SEVERE, "Failed to load user preferences", e); } - // Return default user if not found in database return new ChatUser(uuid, defaultChannel); } } diff --git a/src/main/java/com/noximity/remmyChat/listeners/ChatListener.java b/src/main/java/com/noximity/remmyChat/listeners/ChatListener.java index 52534ec..487c9d2 100644 --- a/src/main/java/com/noximity/remmyChat/listeners/ChatListener.java +++ b/src/main/java/com/noximity/remmyChat/listeners/ChatListener.java @@ -66,15 +66,19 @@ public void onChat(AsyncChatEvent event) { chatUser.setCurrentChannel(currentChannel.getName()); } + // Check permission for the channel if (currentChannel.getPermission() != null && !currentChannel.getPermission().isEmpty() && !player.hasPermission(currentChannel.getPermission())) { player.sendMessage(plugin.getFormatService().formatSystemMessage("error.no-permission")); return; } + // Format the message Component formattedMessage = plugin.getFormatService().formatChatMessage(player, currentChannel.getName(), rawMessage); + // Determine who should receive the message if (currentChannel.getRadius() > 0) { + // Local radius-based chat - only players within radius receive the message for (Player recipient : plugin.getServer().getOnlinePlayers()) { if (player.getWorld().equals(recipient.getWorld()) && player.getLocation().distance(recipient.getLocation()) <= currentChannel.getRadius()) { @@ -82,20 +86,34 @@ public void onChat(AsyncChatEvent event) { } } } else { - plugin.getServer().sendMessage(formattedMessage); + // Global chat - only send to players in the same channel + for (Player recipient : plugin.getServer().getOnlinePlayers()) { + ChatUser recipientUser = plugin.getChatService().getChatUser(recipient.getUniqueId()); + // Only send message if recipient is in the same channel + if (recipientUser.getCurrentChannel().equals(currentChannel.getName())) { + recipient.sendMessage(formattedMessage); + } + } } } @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); + // This will now load the saved channel from the database plugin.getChatService().createChatUser(player.getUniqueId()); } @EventHandler public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); + // Save user preferences including channel before removing from cache + ChatUser user = plugin.getChatService().getChatUser(player.getUniqueId()); + if (user != null) { + plugin.getDatabaseManager().saveUserPreferences(user); + } plugin.getChatService().removeChatUser(player.getUniqueId()); cooldowns.remove(player.getUniqueId()); } -} \ No newline at end of file +} + diff --git a/src/main/java/com/noximity/remmyChat/models/Channel.java b/src/main/java/com/noximity/remmyChat/models/Channel.java index 0473565..fcb000d 100644 --- a/src/main/java/com/noximity/remmyChat/models/Channel.java +++ b/src/main/java/com/noximity/remmyChat/models/Channel.java @@ -3,25 +3,23 @@ public class Channel { private final String name; - private final String format; private final String permission; private final double radius; + private final String prefix; + private final String hover; - public Channel(String name, String format, String permission, double radius) { + public Channel(String name, String permission, double radius, String prefix, String hover) { this.name = name; - this.format = format; this.permission = permission; this.radius = radius; + this.prefix = prefix; + this.hover = hover; } public String getName() { return name; } - public String getFormat() { - return format; - } - public String getPermission() { return permission; } @@ -29,4 +27,13 @@ public String getPermission() { public double getRadius() { return radius; } -} \ No newline at end of file + + public String getPrefix() { + return prefix; + } + + public String getHover() { + return hover; + } +} + diff --git a/src/main/java/com/noximity/remmyChat/models/GroupFormat.java b/src/main/java/com/noximity/remmyChat/models/GroupFormat.java new file mode 100644 index 0000000..cd4105f --- /dev/null +++ b/src/main/java/com/noximity/remmyChat/models/GroupFormat.java @@ -0,0 +1,26 @@ +package com.noximity.remmyChat.models; + +public class GroupFormat { + + private final String name; + private final String nameStyle; + private final String prefix; + + public GroupFormat(String name, String nameStyle, String prefix) { + this.name = name; + this.nameStyle = nameStyle; + this.prefix = prefix; + } + + public String getName() { + return name; + } + + public String getNameStyle() { + return nameStyle; + } + + public String getPrefix() { + return prefix; + } +} diff --git a/src/main/java/com/noximity/remmyChat/services/FormatService.java b/src/main/java/com/noximity/remmyChat/services/FormatService.java index 3311474..5ebe7f7 100644 --- a/src/main/java/com/noximity/remmyChat/services/FormatService.java +++ b/src/main/java/com/noximity/remmyChat/services/FormatService.java @@ -1,6 +1,8 @@ package com.noximity.remmyChat.services; import com.noximity.remmyChat.RemmyChat; +import com.noximity.remmyChat.models.Channel; +import com.noximity.remmyChat.models.GroupFormat; import me.clip.placeholderapi.PlaceholderAPI; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; @@ -29,7 +31,7 @@ public FormatService(RemmyChat plugin) { this.miniMessage = MiniMessage.miniMessage(); } - public Component formatChatMessage(Player player, String channel, String message) { + public Component formatChatMessage(Player player, String channelName, String message) { String playerName = player.getName(); String displayName = player.getDisplayName(); @@ -40,30 +42,80 @@ public Component formatChatMessage(Player player, String channel, String message Component messageComponent = formatMessageContent(player, message); - String format = plugin.getConfigManager().getChannel(channel).getFormat(); + Channel channel = plugin.getConfigManager().getChannel(channelName); + if (channel == null) { + channel = plugin.getConfigManager().getDefaultChannel(); + } + + String nameStyle = plugin.getConfigManager().getNameStyleTemplate("default"); + String channelPrefixRef = channel.getPrefix(); + String groupPrefixRef = ""; + String channelPrefix = ""; + String groupPrefix = ""; + + if (plugin.getConfigManager().isUseGroupFormat() && plugin.getPermissionService().isLuckPermsHooked()) { + GroupFormat groupFormat = plugin.getPermissionService().getHighestGroupFormat(player); + if (groupFormat != null) { + nameStyle = plugin.getConfigManager().getNameStyleTemplate(groupFormat.getNameStyle()); + groupPrefixRef = groupFormat.getPrefix(); + } + } + + if (!channelPrefixRef.isEmpty()) { + channelPrefix = plugin.getConfigManager().getChannelPrefixTemplate(channelPrefixRef); + if (channelPrefix.isEmpty()) { + channelPrefix = channelPrefixRef; + } + channelPrefix += " "; + } + + if (!groupPrefixRef.isEmpty()) { + groupPrefix = plugin.getConfigManager().getGroupPrefixTemplate(groupPrefixRef); + if (groupPrefix.isEmpty()) { + groupPrefix = groupPrefixRef; + } + groupPrefix += " "; + } + + String formattedName = nameStyle.replace("%player_name%", displayName); + + String hoverText = plugin.getConfigManager().getHoverTemplate(channel.getHover()); + if (hoverText.isEmpty()) { + hoverText = plugin.getConfigManager().getHoverTemplate("player-info"); + } + + hoverText = hoverText.replace("%player_name%", playerName); + + String chatFormat = plugin.getConfigManager().getChatFormat(); + String messageFormat = chatFormat + .replace("%channel_prefix%", channelPrefix) + .replace("%group_prefix%", groupPrefix) + .replace("%name%", formattedName) + .replace("%message%", ""); + + if (plugin.getConfigManager().isFormatHoverEnabled() && !hoverText.isEmpty()) { + String nameWithHover = "" + formattedName + ""; + messageFormat = chatFormat + .replace("%channel_prefix%", channelPrefix) + .replace("%group_prefix%", groupPrefix) + .replace("%name%", nameWithHover) + .replace("%message%", ""); + } + if (plugin.getServer().getPluginManager().getPlugin("PlaceholderAPI") != null) { - format = PlaceholderAPI.setPlaceholders(player, format); + messageFormat = PlaceholderAPI.setPlaceholders(player, messageFormat); } - TagResolver.Builder tagResolverBuilder = TagResolver.builder() - .resolver(Placeholder.parsed("player", playerName)) - .resolver(Placeholder.component("message", messageComponent)) - .resolver(Placeholder.parsed("displayname", displayName)); try { - return miniMessage.deserialize(format, tagResolverBuilder.build()); + return miniMessage.deserialize(messageFormat, TagResolver.builder() + .resolver(Placeholder.component("message", messageComponent)) + .build()); } catch (Exception e) { plugin.getLogger().warning("Error formatting message: " + e.getMessage()); return Component.text("Error in formatting: " + PlainTextComponentSerializer.plainText().serialize(messageComponent)); } } - /** - * Formats the message content, handling URLs and player formatting - * - * @param player The player sending the message - * @param message The raw message text - * @return A formatted Component with URLs properly handled - */ private Component formatMessageContent(Player player, String message) { List urls = new ArrayList<>(); List startPositions = new ArrayList<>(); @@ -123,7 +175,7 @@ private Component formatUrl(String url) { } if (plugin.getConfig().getBoolean("url-formatting.hover", true)) { - String hoverText = plugin.getConfig().getString("url-formatting.hover-text", "<#AAAAAA>Click to open"); + String hoverText = plugin.getConfig().getString("url-formatting.hover-text", "<#AAAAAA>Click to open"); Component hoverComponent = miniMessage.deserialize(hoverText); urlBuilder.hoverEvent(HoverEvent.showText(hoverComponent)); } @@ -150,14 +202,8 @@ public Component formatSystemMessage(String path, TagResolver... placeholders) { } } - /** - * Manually escape MiniMessage format characters to prevent players from using formatting - * when they don't have permission. - * - * @param input The input string to escape - * @return The escaped string - */ private String escapeMinimessage(String input) { return input.replace("<", "\\<").replace(">", "\\>"); } -} \ No newline at end of file +} + diff --git a/src/main/java/com/noximity/remmyChat/services/PermissionService.java b/src/main/java/com/noximity/remmyChat/services/PermissionService.java new file mode 100644 index 0000000..f41026d --- /dev/null +++ b/src/main/java/com/noximity/remmyChat/services/PermissionService.java @@ -0,0 +1,98 @@ +package com.noximity.remmyChat.services; + +import com.noximity.remmyChat.RemmyChat; +import com.noximity.remmyChat.models.GroupFormat; +import org.bukkit.entity.Player; + +public class PermissionService { + + private final RemmyChat plugin; + private Object luckPermsApi; + private boolean luckPermsHooked = false; + + public PermissionService(RemmyChat plugin) { + this.plugin = plugin; + hookLuckPerms(); + } + + private void hookLuckPerms() { + try { + if (plugin.getServer().getPluginManager().isPluginEnabled("LuckPerms")) { + // Use reflection to access LuckPerms API to prevent class loading issues when LP is not present + Class lpProviderClass = Class.forName("net.luckperms.api.LuckPermsProvider"); + luckPermsApi = lpProviderClass.getMethod("get").invoke(null); + luckPermsHooked = true; + plugin.getLogger().info("LuckPerms found and hooked successfully!"); + } else { + plugin.getLogger().info("LuckPerms not found, group-based formatting will be disabled."); + } + } catch (Exception e) { + plugin.getLogger().warning("Failed to hook into LuckPerms: " + e.getMessage()); + luckPermsHooked = false; + } + } + + public boolean isLuckPermsHooked() { + return luckPermsHooked; + } + + /** + * Gets the primary group name for a player + * @param player The player to check + * @return The primary group name or null if LuckPerms is not hooked + */ + public String getPrimaryGroup(Player player) { + if (!luckPermsHooked) return null; + + try { + // Get the User object using reflection + Object userManager = luckPermsApi.getClass().getMethod("getUserManager").invoke(luckPermsApi); + Object user = userManager.getClass().getMethod("getUser", java.util.UUID.class) + .invoke(userManager, player.getUniqueId()); + + if (user == null) return null; + + // Get the primary group from the User object + return (String) user.getClass().getMethod("getPrimaryGroup").invoke(user); + } catch (Exception e) { + plugin.getLogger().warning("Error getting primary group for " + player.getName() + ": " + e.getMessage()); + return null; + } + } + + /** + * Finds the highest priority group format that the player has permission for + * @param player The player to check + * @return The group format or null if no matching format found + */ + public GroupFormat getHighestGroupFormat(Player player) { + if (!luckPermsHooked || !plugin.getConfigManager().isUseGroupFormat()) { + return null; + } + + try { + // Get primary group using our method that handles reflection + String primaryGroup = getPrimaryGroup(player); + if (primaryGroup != null) { + GroupFormat primaryGroupFormat = plugin.getConfigManager().getGroupFormat(primaryGroup); + // If we have a format for the primary group, use that + if (primaryGroupFormat != null) { + return primaryGroupFormat; + } + } + + // Otherwise check all configured groups by permission + for (String groupName : plugin.getConfigManager().getGroupFormats().keySet()) { + if (player.hasPermission("group." + groupName)) { + return plugin.getConfigManager().getGroupFormat(groupName); + } + } + + // No matching group format found + return null; + } catch (Exception e) { + plugin.getLogger().warning("Error getting group format for " + player.getName() + ": " + e.getMessage()); + return null; + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index bd3f182..7c8f4ba 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -8,45 +8,91 @@ features: format-hover: true clickable-links: true player-formatting: false # Whether players can use MiniMessage formatting + use-group-format: true # Whether to use group-based formatting # Chat cooldown in seconds (0 to disable) chat-cooldown: 3 -# URL formatting options +# URL formatting optionsxw url-formatting: - # Whether URLs should be formatted as clickable links enabled: true - - # URL color (hex color code) color: "#3498DB" - - # Whether URLs should be underlined underline: true + hover-text: "<#AAAAAA>Click to open" + +# Templates for reuse across formats +templates: + # Hover templates that can be reused + hovers: + player-info: "<#778899>Player information\n<#F8F9FA>Name: <#E8E8E8>%player_name%\n<#F8F9FA>Click to message" + local-chat: "<#778899>Local Chat\n<#F8F9FA>Range: 100 blocks" + staff-chat: "<#778899>Staff Chat\n<#F8F9FA>Private staff communication" + trade-chat: "<#778899>Trade Chat\n<#F8F9FA>For buying and selling items" + + # Channel prefixes + channel-prefixes: + local: "<#5BC0DE>[Local]" + staff: "<#F5A623>[Staff]" + trade: "<#7ED321>[Trade]" + + # Group prefixes + group-prefixes: + owner: "[OWNER]" + admin: "<#FF5733>[ADMIN]" + mod: "<#33FF57>[MOD]" + vip: "<#FFCC00>[VIP]" - # Whether URLs should have hover text - hover: true + # Name styles that can be applied to usernames + name-styles: + default: "<#4A90E2>%player_name%" + owner: "%player_name%" + admin: "%player_name%" + mod: "%player_name%" + vip: "%player_name%" - # Hover text to show when hovering over URLs (supports MiniMessage format) - hover-text: "<#AAAAAA>Click to open" +# Chat message structure - used for final assembly +chat-format: "%channel_prefix%%group_prefix%%name%<#778899>: <#F8F9FA>%message%" # Channel configurations channels: global: - format: "Player information\n<#F8F9FA>Name: <#E8E8E8>\n<#F8F9FA>Click to message'> ><#4A90E2><#778899>: <#F8F9FA>" permission: "" # Empty means everyone can use radius: -1 # -1 means global chat + prefix: "" # No prefix for global + hover: "player-info" # References templates.hovers.player-info local: - format: "Local Chat\n<#F8F9FA>Range: 100 blocks'><#5BC0DE>[Local] Player information\n<#F8F9FA>Name: <#E8E8E8>\n<#F8F9FA>Click to message'> ><#4A90E2><#778899>: <#F8F9FA>" permission: "remmychat.channel.local" radius: 100 # Chat radius in blocks + prefix: "local" # References templates.channel-prefixes.local + hover: "local-chat" # References templates.hovers.local-chat staff: - format: "Staff Chat\n<#F8F9FA>Private staff communication'><#F5A623>[Staff] Player information\n<#F8F9FA>Name: <#E8E8E8>\n<#F8F9FA>Click to message'> ><#4A90E2><#778899>: <#F8F9FA>" permission: "remmychat.channel.staff" radius: -1 # Global chat + prefix: "staff" # References templates.channel-prefixes.staff + hover: "staff-chat" # References templates.hovers.staff-chat trade: - format: "Trade Chat\n<#F8F9FA>For buying and selling items'><#7ED321>[Trade] Player information\n<#F8F9FA>Name: <#E8E8E8>\n<#F8F9FA>Click to message'> ><#4A90E2><#778899>: <#F8F9FA>" permission: "remmychat.channel.trade" - radius: -1 # Global chat \ No newline at end of file + radius: -1 # Global chat + prefix: "trade" # References templates.channel-prefixes.trade + hover: "trade-chat" # References templates.hovers.trade-chat + +# Group-based name formatting - overrides default name style +groups: + owner: + name-style: "owner" # References templates.name-styles.owner + prefix: "owner" # References templates.group-prefixes.owner + + admin: + name-style: "admin" # References templates.name-styles.admin + prefix: "admin" # References templates.group-prefixes.admin + + mod: + name-style: "mod" # References templates.name-styles.mod + prefix: "mod" # References templates.group-prefixes.mod + + vip: + name-style: "vip" # References templates.name-styles.vip + prefix: "vip" # References templates.group-prefixes.vip diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 7f1e767..33321b5 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ version: '${project.version}' main: com.noximity.remmyChat.RemmyChat api-version: '1.21' depend: [] -softdepend: [PlaceholderAPI] +softdepend: [PlaceholderAPI, LuckPerms] authors: [matcldr] description: A professional chat plugin with MiniMessage support website: https://noximity.com #under construction