Fix Folia chat colors - use Component API properly - ChatListener: use MessageConfig.getComponent() for error messages - ChatManager: build format string then parse with MiniMessage.deserialize() - ChatFormatter.convertAllColors: made idempotent (skip if already has <color:#>) - Removed old formatGlobalMessage/formatLocalMessage methods - All chat messages now properly rendered with colors

This commit is contained in:
loki5512344 2025-12-30 22:17:45 +01:00
parent 8824ccd60f
commit 8c6fb2c92e
4 changed files with 88 additions and 67 deletions

BIN
src.zip Normal file

Binary file not shown.

View file

@ -5,6 +5,9 @@ import com.loki.lochat.managers.AntiSpamManager;
import com.loki.lochat.managers.FilterManager; import com.loki.lochat.managers.FilterManager;
import com.loki.lochat.utils.ChatFormatter; import com.loki.lochat.utils.ChatFormatter;
import io.papermc.paper.event.player.AsyncChatEvent; import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
@ -14,6 +17,7 @@ import org.bukkit.event.player.PlayerQuitEvent;
public class ChatListener implements Listener { public class ChatListener implements Listener {
private final LoChat plugin; private final LoChat plugin;
private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText();
public ChatListener(LoChat plugin) { public ChatListener(LoChat plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -24,12 +28,14 @@ public class ChatListener implements Listener {
event.setCancelled(true); event.setCancelled(true);
Player player = event.getPlayer(); Player player = event.getPlayer();
String message = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer Component message = event.message();
.plainText().serialize(event.message());
// Plain текст только для проверок
String plainMessage = PLAIN.serialize(message);
// Обработка через RegionScheduler для Folia // Обработка через RegionScheduler для Folia
player.getScheduler().run(plugin, task -> { player.getScheduler().run(plugin, task -> {
processChat(player, message); processChat(player, plainMessage);
}, null); }, null);
} }
@ -38,11 +44,9 @@ public class ChatListener implements Listener {
if (plugin.getMuteManager().isMuted(player.getUniqueId())) { if (plugin.getMuteManager().isMuted(player.getUniqueId())) {
String timeLeft = plugin.getMuteManager().formatRemainingTime(player.getUniqueId()); String timeLeft = plugin.getMuteManager().formatRemainingTime(player.getUniqueId());
if (timeLeft.equals("навсегда")) { if (timeLeft.equals("навсегда")) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("mute.permanent"))); player.sendMessage(plugin.getMessageConfig().getComponent("mute.permanent"));
} else { } else {
player.sendMessage(ChatFormatter.parse( player.sendMessage(plugin.getMessageConfig().getComponent("mute.you-muted", "{time}", timeLeft));
plugin.getMessageConfig().get("mute.you-muted", "{time}", timeLeft)
));
} }
return; return;
} }
@ -59,9 +63,7 @@ public class ChatListener implements Listener {
if (cooldown > 0 && !player.hasPermission("chat.bypass.cooldown")) { if (cooldown > 0 && !player.hasPermission("chat.bypass.cooldown")) {
if (plugin.getCooldownManager().isOnCooldown(player.getUniqueId(), chatType, cooldown)) { if (plugin.getCooldownManager().isOnCooldown(player.getUniqueId(), chatType, cooldown)) {
int remaining = plugin.getCooldownManager().getRemainingCooldown(player.getUniqueId(), chatType, cooldown); int remaining = plugin.getCooldownManager().getRemainingCooldown(player.getUniqueId(), chatType, cooldown);
player.sendMessage(ChatFormatter.parse( player.sendMessage(plugin.getMessageConfig().getComponent("cooldown.wait", "{time}", String.valueOf(remaining)));
plugin.getMessageConfig().get("cooldown.wait", "{time}", String.valueOf(remaining))
));
return; return;
} }
} }
@ -71,15 +73,15 @@ public class ChatListener implements Listener {
AntiSpamManager.SpamResult spamResult = plugin.getAntiSpamManager().checkMessage(player.getUniqueId(), processedMessage); AntiSpamManager.SpamResult spamResult = plugin.getAntiSpamManager().checkMessage(player.getUniqueId(), processedMessage);
switch (spamResult) { switch (spamResult) {
case TOO_MANY_CAPS -> { case TOO_MANY_CAPS -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("antispam.caps"))); player.sendMessage(plugin.getMessageConfig().getComponent("antispam.caps"));
return; return;
} }
case REPEAT_CHARS -> { case REPEAT_CHARS -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("antispam.repeat"))); player.sendMessage(plugin.getMessageConfig().getComponent("antispam.repeat"));
return; return;
} }
case SIMILAR_MESSAGE -> { case SIMILAR_MESSAGE -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("antispam.similar"))); player.sendMessage(plugin.getMessageConfig().getComponent("antispam.similar"));
return; return;
} }
} }
@ -90,12 +92,11 @@ public class ChatListener implements Listener {
FilterManager.FilterResult filterResult = plugin.getFilterManager().checkMessage(processedMessage); FilterManager.FilterResult filterResult = plugin.getFilterManager().checkMessage(processedMessage);
switch (filterResult) { switch (filterResult) {
case BLOCKED -> { case BLOCKED -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("filter.blocked"))); player.sendMessage(plugin.getMessageConfig().getComponent("filter.blocked"));
return; return;
} }
case WARNED -> { case WARNED -> {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("filter.warned"))); player.sendMessage(plugin.getMessageConfig().getComponent("filter.warned"));
// Продолжаем, но цензурим
processedMessage = plugin.getFilterManager().censorMessage(processedMessage); processedMessage = plugin.getFilterManager().censorMessage(processedMessage);
} }
case CENSORED -> processedMessage = plugin.getFilterManager().censorMessage(processedMessage); case CENSORED -> processedMessage = plugin.getFilterManager().censorMessage(processedMessage);

View file

@ -4,6 +4,7 @@ import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter; import com.loki.lochat.utils.ChatFormatter;
import com.loki.lochat.utils.DistanceUtil; import com.loki.lochat.utils.DistanceUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -16,6 +17,7 @@ public class ChatManager {
private final LoChat plugin; private final LoChat plugin;
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet(); private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
public ChatManager(LoChat plugin) { public ChatManager(LoChat plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -23,7 +25,7 @@ public class ChatManager {
public void sendGlobalMessage(Player sender, String message) { public void sendGlobalMessage(Player sender, String message) {
if (!plugin.getConfigManager().isGlobalEnabled()) { if (!plugin.getConfigManager().isGlobalEnabled()) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("global.disabled"))); sender.sendMessage(plugin.getMessageConfig().getComponent("global.disabled"));
return; return;
} }
@ -35,15 +37,28 @@ public class ChatManager {
String prefix = plugin.getConfigManager().getGlobalPrefix(); String prefix = plugin.getConfigManager().getGlobalPrefix();
boolean hasColor = sender.hasPermission("chat.global.color"); boolean hasColor = sender.hasPermission("chat.global.color");
// Получаем отображаемое имя игрока (с градиентом если есть)
String playerDisplay = getPlayerDisplay(sender);
// Обрабатываем сообщение игрока (смайлики, цвета если есть право)
String finalMessage = ChatFormatter.processPlayerMessage(processedMessage, sender, hasColor);
// Отправка каждому игроку персонализированное сообщение // Отправка каждому игроку персонализированное сообщение
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : Bukkit.getOnlinePlayers()) {
if (isGlobalChatDisabled(player.getUniqueId())) continue; if (isGlobalChatDisabled(player.getUniqueId())) continue;
// Персонализируем сообщение для этого игрока (выделяем его ник) // Персонализируем сообщение для этого игрока (выделяем его ник)
String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(processedMessage, player); String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(finalMessage, player);
Component formatted = ChatFormatter.formatGlobalMessage(format, prefix, sender, personalizedMsg, hasColor); // Формируем итоговую строку
player.sendMessage(formatted); String formatted = format
.replace("<prefix>", prefix)
.replace("<player>", playerDisplay)
.replace("<message>", personalizedMsg);
// Парсим в Component и отправляем
Component component = MINI_MESSAGE.deserialize(formatted);
player.sendMessage(component);
// Проверяем упоминание ника без @ и уведомляем // Проверяем упоминание ника без @ и уведомляем
plugin.getMentionManager().notifyIfMentioned(message, player, sender); plugin.getMentionManager().notifyIfMentioned(message, player, sender);
@ -69,16 +84,29 @@ public class ChatManager {
String prefix = plugin.getConfigManager().getLocalPrefix(); String prefix = plugin.getConfigManager().getLocalPrefix();
boolean hasColor = sender.hasPermission("chat.local.color"); boolean hasColor = sender.hasPermission("chat.local.color");
// Получаем отображаемое имя игрока (с градиентом если есть)
String playerDisplay = getPlayerDisplay(sender);
// Обрабатываем сообщение игрока (смайлики, цвета если есть право)
String finalMessage = ChatFormatter.processPlayerMessage(processedMessage, sender, hasColor);
boolean foundRecipient = false; boolean foundRecipient = false;
Set<Player> recipients = new HashSet<>(); Set<Player> recipients = new HashSet<>();
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : Bukkit.getOnlinePlayers()) {
if (DistanceUtil.isInRange(sender, player, radius)) { if (DistanceUtil.isInRange(sender, player, radius)) {
// Персонализируем сообщение для этого игрока // Персонализируем сообщение для этого игрока
String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(processedMessage, player); String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(finalMessage, player);
// Формируем итоговую строку
String formatted = prefix + " " + format
.replace("<player>", playerDisplay)
.replace("<message>", personalizedMsg);
// Парсим в Component и отправляем
Component component = MINI_MESSAGE.deserialize(formatted);
player.sendMessage(component);
Component formatted = ChatFormatter.formatLocalMessage(format, prefix, sender, personalizedMsg, hasColor);
player.sendMessage(formatted);
recipients.add(player); recipients.add(player);
// Проверяем упоминание ника без @ // Проверяем упоминание ника без @
@ -95,10 +123,23 @@ public class ChatManager {
plugin.getMentionManager().notifyMentioned(mentionedPlayers); plugin.getMentionManager().notifyMentioned(mentionedPlayers);
if (!foundRecipient) { if (!foundRecipient) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("local.no-players"))); sender.sendMessage(plugin.getMessageConfig().getComponent("local.no-players"));
} }
} }
/**
* Получает отображаемое имя игрока с градиентом (если включено)
* Возвращает строку в MiniMessage формате
*/
private String getPlayerDisplay(Player player) {
if (plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) {
String name = plugin.getGradientModule().getFormattedName(player);
// Конвертируем &#RRGGBB формат в MiniMessage
return ChatFormatter.convertAllColors(name);
}
return player.getName();
}
public boolean toggleGlobalChat(UUID player) { public boolean toggleGlobalChat(UUID player) {
if (globalChatDisabled.contains(player)) { if (globalChatDisabled.contains(player)) {
globalChatDisabled.remove(player); globalChatDisabled.remove(player);

View file

@ -26,23 +26,29 @@ public class ChatFormatter {
/** /**
* Конвертирует все форматы цветов в MiniMessage * Конвертирует все форматы цветов в MiniMessage
* Поддерживает: &коды, §коды, &#HEX, §x§...HEX, MiniMessage теги * Поддерживает: &коды, §коды, &#HEX, §x§...HEX
* Идемпотентный - не конвертирует уже конвертированные теги
*/ */
public static String convertAllColors(String message) { public static String convertAllColors(String message) {
if (message == null) return ""; if (message == null) return "";
// Если уже содержит MiniMessage теги <color:...> - не конвертируем повторно
if (message.contains("<color:#")) {
return message;
}
String result = message; String result = message;
// 1. Конвертируем §x§R§R§G§G§B§B -> <#RRGGBB> // 1. Конвертируем §x§R§R§G§G§B§B -> <color:#rrggbb>
result = convertSectionHexColors(result); result = convertSectionHexColors(result);
// 2. Конвертируем §коды -> &коды (для унификации) // 2. Конвертируем §коды -> &коды (для унификации)
result = convertSectionToAmpersand(result); result = convertSectionToAmpersand(result);
// 3. Нормализуем существующие MiniMessage HEX теги в lowercase // 3. Нормализуем существующие MiniMessage HEX теги <#RRGGBB> -> <color:#rrggbb>
result = normalizeMiniMessageHex(result); result = normalizeMiniMessageHex(result);
// 4. Конвертируем &#RRGGBB -> <#RRGGBB> (только если еще не в MiniMessage формате) // 4. Конвертируем &#RRGGBB -> <color:#rrggbb>
result = convertHexColors(result); result = convertHexColors(result);
// 5. Конвертируем &коды -> MiniMessage теги // 5. Конвертируем &коды -> MiniMessage теги
@ -232,10 +238,22 @@ public class ChatFormatter {
} }
public static Component parse(String message) { public static Component parse(String message) {
return MINI_MESSAGE.deserialize(message);
}
public static Component parseWithColors(String message) {
return MINI_MESSAGE.deserialize(convertAllColors(message)); return MINI_MESSAGE.deserialize(convertAllColors(message));
} }
public static Component parse(String message, String... replacements) { public static Component parse(String message, String... replacements) {
String result = message;
for (int i = 0; i < replacements.length - 1; i += 2) {
result = result.replace(replacements[i], replacements[i + 1]);
}
return MINI_MESSAGE.deserialize(result);
}
public static Component parseWithColors(String message, String... replacements) {
String result = message; String result = message;
for (int i = 0; i < replacements.length - 1; i += 2) { for (int i = 0; i < replacements.length - 1; i += 2) {
result = result.replace(replacements[i], replacements[i + 1]); result = result.replace(replacements[i], replacements[i + 1]);
@ -282,45 +300,6 @@ public class ChatFormatter {
return result; return result;
} }
/**
* Форматирует глобальное сообщение с поддержкой LoPreff
*/
public static Component formatGlobalMessage(String format, String prefix, Player player, String message, boolean hasColorPermission) {
String playerDisplay = getPlayerDisplay(player);
String finalMessage = processPlayerMessage(message, player, hasColorPermission);
String formatted = format
.replace("<prefix>", prefix)
.replace("<player>", playerDisplay)
.replace("<message>", finalMessage);
return parse(formatted);
}
/**
* Форматирует локальное сообщение с поддержкой LoPreff
*/
public static Component formatLocalMessage(String format, String prefix, Player player, String message, boolean hasColorPermission) {
String playerDisplay = getPlayerDisplay(player);
String finalMessage = processPlayerMessage(message, player, hasColorPermission);
String formatted = prefix + " " + format
.replace("<player>", playerDisplay)
.replace("<message>", finalMessage);
return parse(formatted);
}
private static String getPlayerDisplay(Player player) {
LoChat plugin = LoChat.getInstance();
if (plugin != null && plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) {
String name = plugin.getGradientModule().getFormattedName(player);
// Конвертируем §x§... формат в MiniMessage
return convertAllColors(name);
}
return player.getName();
}
public static Component formatPmSent(String format, String targetName, String message) { public static Component formatPmSent(String format, String targetName, String message) {
return parse(format return parse(format
.replace("{player}", targetName) .replace("{player}", targetName)