diff --git a/src/main/java/ru/overwrite/chat/ChatManager.java b/src/main/java/ru/overwrite/chat/ChatManager.java index e96d78f..195b291 100644 --- a/src/main/java/ru/overwrite/chat/ChatManager.java +++ b/src/main/java/ru/overwrite/chat/ChatManager.java @@ -2,9 +2,11 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectList; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.hover.content.Text; import net.md_5.bungee.chat.ComponentSerializer; @@ -12,7 +14,6 @@ import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; -import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.plugin.messaging.Messenger; import ru.overwrite.chat.configuration.Config; import ru.overwrite.chat.configuration.data.ChatChannel; @@ -21,6 +22,8 @@ public class ChatManager { + private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.legacySection(); + private final PromisedChat plugin; private final Config pluginConfig; private final String[] searchList = {"%player%", "%prefix%", "%suffix%", "%dph%"}; @@ -41,24 +44,21 @@ private void setupProxy() { } } - public void processChat(Player p, String rawMessage, AsyncPlayerChatEvent e) { + public PreparedChatMessage prepareChat(Player p, String rawMessage) { ChatChannel channel = pluginConfig.findChannel(rawMessage); if (!channel.equals(pluginConfig.getDefaultChannel()) && !p.hasPermission(channel.permission())) { channel = pluginConfig.getDefaultChannel(); } - String message = (channel.prefix() != '\0' && rawMessage.charAt(0) == channel.prefix()) - ? rawMessage.substring(1).trim() : rawMessage; + String message = stripPrefix(channel, rawMessage); if (message.isEmpty()) { - e.setCancelled(true); - return; + return null; } if (channel.cooldownSettings().process(p)) { - e.setCancelled(true); - return; + return null; } String donatePlaceholder = plugin.getPerms() != null ? getDonatePlaceholder(p, channel) : ""; @@ -71,21 +71,62 @@ public void processChat(Player p, String rawMessage, AsyncPlayerChatEvent e) { String chatFormat = Utils.colorize(Utils.replacePlaceholders(p, Utils.replaceEach(channel.format(), searchList, replacementList))); - e.getRecipients().clear(); + String renderedMessage = chatFormat.replace("%message%", colorizedMessage); + + String hoverText = null; + String clickAction = null; + String clickActionValue = null; + + ChatChannel.HoverSettings hoverSettings = channel.hover(); + if (hoverSettings != null && hoverSettings.hoverEnabled()) { + hoverText = Utils.colorize(Utils.replacePlaceholders(p, Utils.replaceEach(hoverSettings.hoverMessage(), searchList, replacementList))); + if (hoverSettings.clickEventEnabled()) { + clickAction = hoverSettings.clickAction(); + clickActionValue = Utils.replacePlaceholders(p, Utils.replaceEach(hoverSettings.clickActionValue(), searchList, replacementList)); + } + } + + return new PreparedChatMessage( + p, + channel, + renderedMessage, + getRadius(p, channel), + hoverText, + clickAction, + clickActionValue + ); + } + + public Component createPaperComponent(PreparedChatMessage prepared) { + Component component = LEGACY_SERIALIZER.deserialize(prepared.renderedMessage()); + + if (!prepared.hoverEnabled()) { + return component; + } - ObjectList playersInRadius = getRadius(p, channel); + Component hoverComponent = LEGACY_SERIALIZER.deserialize(prepared.hoverText()); + Component wrapped = Component.empty().append(component).hoverEvent(HoverEvent.showText(hoverComponent)); - String formatWithMessage = getFormatWithMessage(chatFormat, colorizedMessage); + ClickEvent clickEvent = createPaperClickEvent(prepared); + return clickEvent != null ? wrapped.clickEvent(clickEvent) : wrapped; + } - if (sendHover(p, replacementList, playersInRadius, formatWithMessage, channel)) { - e.setCancelled(true); + public void forwardProxy(PreparedChatMessage prepared) { + if (pluginMessage == null || prepared.channel().radius() >= 0) { return; } - e.getRecipients().addAll(playersInRadius); - e.setFormat(formatWithMessage); - if (pluginMessage != null && channel.radius() < 0) { - pluginMessage.sendCrossProxy(p, formatWithMessage, channel.permission(), false); + + if (prepared.hoverEnabled()) { + pluginMessage.sendCrossProxy( + prepared.player(), + ComponentSerializer.toString(createProxyComponents(prepared)), + prepared.channel().permission(), + true + ); + return; } + + pluginMessage.sendCrossProxy(prepared.player(), prepared.renderedMessage(), prepared.channel().permission(), false); } public boolean checkNewbie(Player p, Cancellable e) { @@ -105,34 +146,6 @@ public boolean checkNewbie(Player p, Cancellable e) { return false; } - public boolean sendHover(Player p, String[] replacementList, ObjectList recipients, String formatWithMessage, ChatChannel channel) { - ChatChannel.HoverSettings hoverSettings = channel.hover(); - if (!hoverSettings.hoverEnabled()) { - return false; - } - String hoverText = Utils.colorize(Utils.replacePlaceholders(p, Utils.replaceEach(hoverSettings.hoverMessage(), searchList, replacementList))); - HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(TextComponent.fromLegacyText(hoverText))); - BaseComponent[] comp = TextComponent.fromLegacyText(formatWithMessage); - for (BaseComponent component : comp) { - component.setHoverEvent(hoverEvent); - } - if (hoverSettings.clickEventEnabled()) { - ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.valueOf(hoverSettings.clickAction()), Utils.replacePlaceholders(p, Utils.replaceEach(hoverSettings.clickActionValue(), searchList, replacementList))); - for (BaseComponent component : comp) { - component.setClickEvent(clickEvent); - } - } - for (Player recipient : recipients) { - recipient.spigot().sendMessage(comp); - } - if (pluginMessage != null && channel.radius() < 0) { - pluginMessage.sendCrossProxy(p, ComponentSerializer.toString(comp), channel.permission(), true); - } - // Костыли... костыли вечны. - Bukkit.getConsoleSender().sendMessage(formatWithMessage); - return true; - } - private ObjectList getRadius(Player p, ChatChannel chatChannel) { ObjectList plist = new ObjectArrayList<>(); double radius = chatChannel.radius(); @@ -155,14 +168,78 @@ private ObjectList getRadius(Player p, ChatChannel chatChannel) { return plist; } - private String getFormatWithMessage(String format, String chatMessage) { - return format - .replace("%message%", chatMessage) - .replace("%", "%%"); // Это надо чтобы PAPI не выёбывался + private String stripPrefix(ChatChannel channel, String rawMessage) { + return channel.prefix() != '\0' && !rawMessage.isEmpty() && rawMessage.charAt(0) == channel.prefix() + ? rawMessage.substring(1).trim() + : rawMessage; + } + + private BaseComponent[] createProxyComponents(PreparedChatMessage prepared) { + BaseComponent[] components = TextComponent.fromLegacyText(prepared.renderedMessage()); + net.md_5.bungee.api.chat.HoverEvent hoverEvent = new net.md_5.bungee.api.chat.HoverEvent( + net.md_5.bungee.api.chat.HoverEvent.Action.SHOW_TEXT, + new Text(TextComponent.fromLegacyText(prepared.hoverText())) + ); + + for (BaseComponent component : components) { + component.setHoverEvent(hoverEvent); + } + + net.md_5.bungee.api.chat.ClickEvent clickEvent = createProxyClickEvent(prepared); + if (clickEvent != null) { + for (BaseComponent component : components) { + component.setClickEvent(clickEvent); + } + } + + return components; + } + + private ClickEvent createPaperClickEvent(PreparedChatMessage prepared) { + if (prepared.clickAction() == null || prepared.clickActionValue() == null) { + return null; + } + + try { + return ClickEvent.clickEvent(ClickEvent.Action.valueOf(prepared.clickAction()), prepared.clickActionValue()); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Unknown click action for Paper chat: " + prepared.clickAction()); + return null; + } + } + + private net.md_5.bungee.api.chat.ClickEvent createProxyClickEvent(PreparedChatMessage prepared) { + if (prepared.clickAction() == null || prepared.clickActionValue() == null) { + return null; + } + + try { + return new net.md_5.bungee.api.chat.ClickEvent( + net.md_5.bungee.api.chat.ClickEvent.Action.valueOf(prepared.clickAction()), + prepared.clickActionValue() + ); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Unknown click action for proxy chat: " + prepared.clickAction()); + return null; + } } private String getDonatePlaceholder(Player p, ChatChannel chatChannel) { String primaryGroup = plugin.getPerms().getPrimaryGroup(p); return chatChannel.donatePlaceholders().getOrDefault(primaryGroup, ""); } + + public record PreparedChatMessage( + Player player, + ChatChannel channel, + String renderedMessage, + ObjectList recipients, + String hoverText, + String clickAction, + String clickActionValue + ) { + public boolean hoverEnabled() { + return hoverText != null && !hoverText.isEmpty(); + } + } } diff --git a/src/main/java/ru/overwrite/chat/listener/ChatListener.java b/src/main/java/ru/overwrite/chat/listener/ChatListener.java index 535f216..1362564 100644 --- a/src/main/java/ru/overwrite/chat/listener/ChatListener.java +++ b/src/main/java/ru/overwrite/chat/listener/ChatListener.java @@ -1,11 +1,16 @@ package ru.overwrite.chat.listener; +import io.papermc.paper.chat.ChatRenderer; +import io.papermc.paper.event.player.AsyncChatEvent; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; import ru.overwrite.chat.ChatManager; +import ru.overwrite.chat.ChatManager.PreparedChatMessage; import ru.overwrite.chat.PromisedChat; import ru.overwrite.chat.configuration.Config; @@ -23,7 +28,7 @@ public ChatListener(PromisedChat plugin) { // Не ну а хуле делать @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onChatLowest(AsyncPlayerChatEvent e) { + public void onChatLowest(AsyncChatEvent e) { if (pluginConfig.getChatPriority() != EventPriority.LOWEST) { return; } @@ -31,7 +36,7 @@ public void onChatLowest(AsyncPlayerChatEvent e) { } @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onChatLow(AsyncPlayerChatEvent e) { + public void onChatLow(AsyncChatEvent e) { if (pluginConfig.getChatPriority() != EventPriority.LOW) { return; } @@ -39,7 +44,7 @@ public void onChatLow(AsyncPlayerChatEvent e) { } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onChatNormal(AsyncPlayerChatEvent e) { + public void onChatNormal(AsyncChatEvent e) { if (pluginConfig.getChatPriority() != EventPriority.NORMAL) { return; } @@ -47,7 +52,7 @@ public void onChatNormal(AsyncPlayerChatEvent e) { } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void onChatHigh(AsyncPlayerChatEvent e) { + public void onChatHigh(AsyncChatEvent e) { if (pluginConfig.getChatPriority() != EventPriority.HIGH) { return; } @@ -55,7 +60,7 @@ public void onChatHigh(AsyncPlayerChatEvent e) { } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onChatHighest(AsyncPlayerChatEvent e) { + public void onChatHighest(AsyncChatEvent e) { if (pluginConfig.getChatPriority() != EventPriority.HIGHEST) { return; } @@ -63,23 +68,35 @@ public void onChatHighest(AsyncPlayerChatEvent e) { } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onChatMonitor(AsyncPlayerChatEvent e) { + public void onChatMonitor(AsyncChatEvent e) { if (pluginConfig.getChatPriority() != EventPriority.MONITOR) { return; } process(e); } - private void process(AsyncPlayerChatEvent e) { - Player p = e.getPlayer(); + private void process(AsyncChatEvent e) { + Player player = e.getPlayer(); - if (chatManager.checkNewbie(p, e)) { + if (chatManager.checkNewbie(player, e)) { return; } - String rawMessage = e.getMessage(); + PreparedChatMessage prepared = chatManager.prepareChat(player, PlainTextComponentSerializer.plainText().serialize(e.message())); + if (prepared == null) { + e.setCancelled(true); + return; + } + + e.viewers().removeIf(viewer -> shouldRemoveViewer(viewer, prepared)); - chatManager.processChat(p, rawMessage, e); + Component renderedMessage = chatManager.createPaperComponent(prepared); + e.renderer(ChatRenderer.viewerUnaware((source, sourceDisplayName, message) -> renderedMessage)); + + chatManager.forwardProxy(prepared); } + private boolean shouldRemoveViewer(Audience viewer, PreparedChatMessage prepared) { + return viewer instanceof Player player && !prepared.recipients().contains(player); + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f8c1923..33b564f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -11,7 +11,7 @@ chatFormats: radius: 100 # Если префикс указан пустым - канал становится дефолтным prefix: '' - # Кулдаун для чата в милисекундах + # Кулдаун для чата в миллисекундах cooldown: 500 # Право на канал, чтобы в него писать и видеть # Если канал дефолтный - право на него не нужно. Если не указано - используется формат pchat.channel.имя_из_конфига @@ -24,7 +24,7 @@ chatFormats: prefix: '!' cooldown: 1500 # Сообщение о кулдауне (может быть отдельно применено к каналу) - cooldownMessage: "&e[♻]&f Вы слишком быстро пишите! Подождите еще %time%" + cooldownMessage: "&e[♻]&f Вы слишком быстро пишите! Подождите ещё %time%" # Ховер текст (может быть отдельно применён к каналу) # При включении заменяет логику работы чата, что может сказаться на работе других плагинов, включающих в себя его форматирование. # Сообщения будут отправляться игрокам от сервера. @@ -40,7 +40,7 @@ chatFormats: enable: false actionType: SUGGEST_COMMAND actionValue: "/msg %player%" - # Плейсхолделы для донатов по группам (может быть применён отдельно к каналу) + # Плейсхолдеры для донатов по группам (может быть применён отдельно к каналу) donatePlaceholders: default: "&7" admin: "&f&l" @@ -56,9 +56,9 @@ newbieChat: # Кулдаун на чат для новичков в секундах newbieCooldown: 600 # Сообщение для чата для новичков - newbieChatMessage: "&c[★] &6Подожди! Что-бы написать в чат вы должны отыграть 10 минут на сервере, осталось &c%time%" + newbieChatMessage: "&c[★] &6Подожди! Чтобы написать в чат, вы должны отыграть 10 минут на сервере, осталось &c%time%" # Сообщение для команд для новичков - newbieCommandMessage: "&c[★] &6Подожди! Что-бы написать данную команды вы должны отыграть 10 минут на сервере, осталось &c%time%" + newbieCommandMessage: "&c[★] &6Подожди! Чтобы написать данную команду, вы должны отыграть 10 минут на сервере, осталось &c%time%" # Команды, которые будут попадать под newbieChat newbieCommands: - "/m" @@ -71,7 +71,7 @@ newbieChat: # Настройка автосообщений autoMessage: - # Включел ли autoMessage + # Включен ли autoMessage enable: true # Выдавать ли их рандомно random: true