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