This commit is contained in:
loki5512344 2026-03-11 11:05:47 +01:00
parent 49744acc59
commit 0fea0f4537
19 changed files with 192 additions and 377 deletions

View file

@ -1,16 +0,0 @@
package com.loki.lochat.api;
import org.bukkit.entity.Player;
/**
* Сервис для отправки сообщений в чат
*/
public interface ChatService {
void sendGlobalMessage(Player sender, Object message);
void sendLocalMessage(Player sender, Object message);
boolean toggleGlobalChat(java.util.UUID player);
boolean isGlobalChatDisabled(java.util.UUID player);
}

View file

@ -1,16 +0,0 @@
package com.loki.lochat.api;
import java.util.UUID;
/**
* Сервис для управления кулдаунами
*/
public interface CooldownService {
boolean isOnCooldown(UUID player, String type, int cooldownSeconds);
int getRemainingCooldown(UUID player, String type, int cooldownSeconds);
void setCooldown(UUID player, String type);
void removeCooldown(UUID player);
}

View file

@ -1,15 +0,0 @@
package com.loki.lochat.api;
import org.bukkit.entity.Player;
/**
* Интерфейс для фильтрации сообщений
*/
public interface MessageFilter {
/**
* Применить фильтр к сообщению
*
* @return отфильтрованное сообщение или null если заблокировано
*/
Object apply(Player player, Object message, String plainMessage);
}

View file

@ -1,20 +0,0 @@
package com.loki.lochat.api;
import java.util.UUID;
/**
* Сервис для управления мутами
*/
public interface MuteService {
void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy);
boolean unmute(UUID uuid, String unmutedBy);
boolean isMuted(UUID uuid);
long getRemainingTime(UUID uuid);
String formatTime(long millis);
long parseTime(String timeStr);
}

View file

@ -47,7 +47,7 @@ public class MeCommand implements CommandExecutor {
return true;
}
} catch (Exception e) {
// Сервис не зарегистрирован, продолжаем
}
}

View file

@ -17,26 +17,12 @@ public class ServiceRegistry {
this.plugin = plugin;
}
public static ServiceRegistry create(JavaPlugin plugin) {
ServiceRegistry registry = new ServiceRegistry(plugin);
registry.registerServices();
return registry;
}
public static ServiceRegistry createWithDeps(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) {
ServiceRegistry registry = new ServiceRegistry(plugin);
registry.registerServicesWithDeps(configManager, messageConfig);
return registry;
}
private void registerServices() {
register(ChatService.class, new ChatServiceImpl(plugin, this));
register(MuteService.class, new MuteServiceImpl(plugin));
register(CooldownService.class, new CooldownServiceImpl());
register(MessageService.class, new MessageServiceImpl(plugin, this));
register(PlayerDataService.class, new PlayerDataServiceImpl(plugin));
}
private void registerServicesWithDeps(ConfigManager configManager, MessageConfig messageConfig) {
register(ChatService.class, new ChatServiceImpl(plugin, this));
register(MuteService.class, new MuteServiceImpl(plugin));

View file

@ -2,6 +2,7 @@ package com.loki.lochat.core.service;
import com.loki.lochat.api.service.ChatService;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.renderer.EnhancedChatRenderer;
import com.loki.lochat.util.DistanceUtil;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
@ -26,26 +27,17 @@ public class ChatServiceImpl implements ChatService {
@Override
public void sendGlobalMessage(Player sender, Object message) {
String format = plugin.getConfig().getString("chat.global.format",
"{prefix} {player}: {message}");
String prefix = plugin.getConfig().getString("chat.global.prefix", "[G]");
Component messageComponent = (message instanceof Component)
? (Component) message
: ChatFormatter.parse(message.toString());
// Используем displayName игрока (с градиентом если есть)
Component playerName = sender.displayName();
String formatted = format.replace("{prefix}", prefix);
Component finalComponent = ChatFormatter.parse(formatted)
.replaceText(builder -> builder.matchLiteral("{player}").replacement(playerName))
.replaceText(builder -> builder.matchLiteral("{message}").replacement(messageComponent));
// Используем тот же рендерер что и в ChatEventListener для единообразия
EnhancedChatRenderer renderer = new EnhancedChatRenderer(plugin, sender, messageComponent, true);
for (Player player : Bukkit.getOnlinePlayers()) {
if (!isGlobalChatDisabled(player.getUniqueId())) {
player.sendMessage(finalComponent);
Component renderedMessage = renderer.render(sender, sender.displayName(), messageComponent, player);
player.sendMessage(renderedMessage);
}
}
}
@ -53,23 +45,18 @@ public class ChatServiceImpl implements ChatService {
@Override
public void sendLocalMessage(Player sender, Object message) {
int radius = plugin.getConfig().getInt("chat.local.radius", 100);
String format = plugin.getConfig().getString("chat.local.format",
"{player}: {message}");
Component messageComponent = (message instanceof Component)
? (Component) message
: ChatFormatter.parse(message.toString());
// Используем displayName игрока (с градиентом если есть)
Component playerName = sender.displayName();
Component finalComponent = ChatFormatter.parse(format)
.replaceText(builder -> builder.matchLiteral("{player}").replacement(playerName))
.replaceText(builder -> builder.matchLiteral("{message}").replacement(messageComponent));
// Используем тот же рендерер что и в ChatEventListener для единообразия
EnhancedChatRenderer renderer = new EnhancedChatRenderer(plugin, sender, messageComponent, false);
for (Player player : Bukkit.getOnlinePlayers()) {
if (DistanceUtil.isInRange(sender, player, radius)) {
player.sendMessage(finalComponent);
Component renderedMessage = renderer.render(sender, sender.displayName(), messageComponent, player);
player.sendMessage(renderedMessage);
}
}
}

View file

@ -1,7 +1,7 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.util.FoliaUtil;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

View file

@ -5,7 +5,7 @@ import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.data.model.MuteData;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.util.FoliaUtil;
import com.loki.lochat.utils.TimeFormatter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

View file

@ -5,7 +5,7 @@ import com.loki.lochat.gradient.config.GradientConfig;
import com.loki.lochat.gradient.config.GradientMessages;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.util.FoliaUtil;
import com.loki.lochat.gradient.util.GradientUtil;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;

View file

@ -6,7 +6,7 @@ import com.loki.lochat.gradient.config.GradientMessages;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.gui.GradientConfirmGUI;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.util.FoliaUtil;
import com.loki.lochat.gradient.util.GradientConstants;
import com.loki.lochat.gradient.util.GradientUtil;
import org.bukkit.Bukkit;

View file

@ -6,7 +6,7 @@ import com.loki.lochat.gradient.config.GradientMessages;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.gui.GradientConfirmGUI;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.util.FoliaUtil;
import com.loki.lochat.gradient.util.GradientConstants;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;

View file

@ -5,7 +5,7 @@ import com.loki.lochat.gradient.config.GradientMessages;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.gui.GradientConfirmGUI;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.util.FoliaUtil;
import com.loki.lochat.gradient.util.GradientConstants;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;

View file

@ -1,7 +1,7 @@
package com.loki.lochat.gradient.listeners;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.util.FoliaUtil;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;

View file

@ -1,75 +0,0 @@
package com.loki.lochat.gradient.util;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import java.util.concurrent.TimeUnit;
/**
* Утилита для работы с Folia
*/
public final class FoliaUtil {
private static final boolean IS_FOLIA;
static {
IS_FOLIA = checkFolia();
}
private FoliaUtil() {
}
private static boolean checkFolia() {
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
public static boolean isFolia() {
return IS_FOLIA;
}
public static void runEntityTask(Plugin plugin, Entity entity, Runnable task) {
if (IS_FOLIA) {
entity.getScheduler().run(plugin, scheduledTask -> task.run(), null);
} else {
Bukkit.getScheduler().runTask(plugin, task);
}
}
public static void runEntityTaskLater(Plugin plugin, Entity entity, Runnable task, long delayTicks) {
if (IS_FOLIA) {
entity.getScheduler().runDelayed(plugin, scheduledTask -> task.run(), null, delayTicks);
} else {
Bukkit.getScheduler().runTaskLater(plugin, task, delayTicks);
}
}
public static void runAsync(Plugin plugin, Runnable task) {
if (IS_FOLIA) {
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> task.run());
} else {
Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
}
}
public static void runAsyncLater(Plugin plugin, Runnable task, long delayMs) {
if (IS_FOLIA) {
Bukkit.getAsyncScheduler().runDelayed(plugin, scheduledTask -> task.run(), delayMs, TimeUnit.MILLISECONDS);
} else {
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, task, delayMs / 50);
}
}
public static void runGlobalTask(Plugin plugin, Runnable task) {
if (IS_FOLIA) {
Bukkit.getGlobalRegionScheduler().run(plugin, scheduledTask -> task.run());
} else {
Bukkit.getScheduler().runTask(plugin, task);
}
}
}

View file

@ -3,16 +3,8 @@ package com.loki.lochat.listener;
import com.loki.lochat.api.service.MessageService;
import com.loki.lochat.core.filter.AdvancedMessageFilter;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.utils.MentionHandler;
import com.loki.lochat.utils.TextFormatter;
import io.papermc.paper.chat.ChatRenderer;
import com.loki.lochat.renderer.EnhancedChatRenderer;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.audience.Audience;
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.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -24,19 +16,21 @@ public class ChatEventListener implements Listener {
private final JavaPlugin plugin;
private final MessageService messageService;
private final AdvancedMessageFilter advancedFilter;
private final MentionHandler mentionHandler;
public ChatEventListener(JavaPlugin plugin, ServiceRegistry registry) {
this.plugin = plugin;
this.messageService = registry.get(MessageService.class);
this.advancedFilter = new AdvancedMessageFilter(plugin.getConfig());
this.mentionHandler = new MentionHandler(plugin);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onChat(AsyncChatEvent event) {
Player sender = event.getPlayer();
String message = PlainTextComponentSerializer.plainText().serialize(event.message());
String rawMessage = PlainTextComponentSerializer.plainText().serialize(event.message());
// Определяем тип чата и очищаем сообщение от !
boolean isGlobal = rawMessage.startsWith("!");
String message = isGlobal ? rawMessage.substring(1).stripLeading() : rawMessage;
boolean allowed = messageService.processMessage(sender, message);
@ -55,145 +49,16 @@ public class ChatEventListener implements Listener {
String filteredMessage = filterResult.filteredMessage();
// Определяем тип чата
boolean isGlobal = message.startsWith("!");
event.message(com.loki.lochat.utils.ChatFormatter.parse(filteredMessage));
event.renderer(new EnhancedChatRenderer(sender, event.message(), isGlobal));
}
private class EnhancedChatRenderer implements ChatRenderer {
private final Player sender;
private final Component originalMessage;
private final boolean isGlobal;
public EnhancedChatRenderer(Player sender, Component originalMessage, boolean isGlobal) {
this.sender = sender;
this.originalMessage = originalMessage;
this.isGlobal = isGlobal;
// Фильтруем получателей для локального чата
if (!isGlobal) {
int radius = plugin.getConfig().getInt("chat.local.radius", 100);
event.viewers().removeIf(viewer ->
viewer instanceof Player p && !com.loki.lochat.util.DistanceUtil.isInRange(sender, p, radius)
);
}
@Override
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
// Префикс в зависимости от типа чата
Component prefixComponent;
if (isGlobal) {
// [G] с желтой буквой G
prefixComponent = Component.text("[")
.color(net.kyori.adventure.text.format.NamedTextColor.GRAY)
.append(Component.text("G").color(net.kyori.adventure.text.format.NamedTextColor.GOLD))
.append(Component.text("]").color(net.kyori.adventure.text.format.NamedTextColor.GRAY));
} else {
// [L] с голубой буквой L
prefixComponent = Component.text("[")
.color(net.kyori.adventure.text.format.NamedTextColor.GRAY)
.append(Component.text("L").color(net.kyori.adventure.text.format.NamedTextColor.AQUA))
.append(Component.text("]").color(net.kyori.adventure.text.format.NamedTextColor.GRAY));
}
Component playerComponent = createPlayerComponent(source, sourceDisplayName);
Component processedMessage = message;
processedMessage = TextFormatter.formatMarkdown(processedMessage);
processedMessage = processUrls(processedMessage);
processedMessage = processEmojis(processedMessage);
if (viewer instanceof Player viewerPlayer) {
processedMessage = mentionHandler.processMentions(processedMessage, source, viewerPlayer);
}
return prefixComponent
.append(Component.text(" "))
.append(playerComponent)
.append(Component.text(": ").color(net.kyori.adventure.text.format.NamedTextColor.GRAY))
.append(processedMessage);
}
private Component createPlayerComponent(Player player, Component displayName) {
if (!plugin.getConfig().getBoolean("chat.hover.enabled", true)) {
return displayName.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
}
java.util.List<String> hoverLines = plugin.getConfig().getStringList("chat.hover.format");
if (hoverLines.isEmpty()) {
hoverLines = java.util.Arrays.asList(
"<gray>Игрок: <white>{player}",
"<gray>Мир: <green>{world}",
"",
"<yellow>▶ Клик: написать ЛС"
);
}
net.kyori.adventure.text.TextComponent.Builder hoverBuilder = Component.text();
for (int i = 0; i < hoverLines.size(); i++) {
String line = hoverLines.get(i);
line = line.replace("{player}", player.getName())
.replace("{world}", player.getWorld().getName())
.replace("{ping}", String.valueOf(player.getPing()))
.replace("{gamemode}", player.getGameMode().name())
.replace("{health}", String.valueOf(Math.round(player.getHealth())))
.replace("{food}", String.valueOf(player.getFoodLevel()));
Component lineComponent = com.loki.lochat.utils.ChatFormatter.parse(line);
hoverBuilder.append(lineComponent);
if (i < hoverLines.size() - 1) {
hoverBuilder.append(Component.newline());
}
}
return displayName
.hoverEvent(HoverEvent.showText(hoverBuilder.build()))
.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
}
private Component processUrls(Component message) {
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
// Fixed regex: escape the dash in character class to avoid illegal range
String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)";
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(urlPattern);
java.util.regex.Matcher matcher = pattern.matcher(plainText);
Component result = message;
while (matcher.find()) {
String url = matcher.group(1);
Component urlComponent = Component.text(url, NamedTextColor.AQUA, TextDecoration.UNDERLINED)
.hoverEvent(HoverEvent.showText(Component.text("Открыть: " + url, NamedTextColor.GRAY)))
.clickEvent(ClickEvent.openUrl(url));
result = result.replaceText(builder ->
builder.matchLiteral(url).replacement(urlComponent)
);
}
return result;
}
private Component processEmojis(Component message) {
Component result = message;
result = result.replaceText(builder -> builder.matchLiteral(":heart:").replacement("❤"));
result = result.replaceText(builder -> builder.matchLiteral(":star:").replacement("★"));
result = result.replaceText(builder -> builder.matchLiteral(":check:").replacement("✔"));
result = result.replaceText(builder -> builder.matchLiteral(":cross:").replacement("✖"));
result = result.replaceText(builder -> builder.matchLiteral(":arrow:").replacement("→"));
result = result.replaceText(builder -> builder.matchLiteral(":skull:").replacement("☠"));
result = result.replaceText(builder -> builder.matchLiteral(":note:").replacement("♪"));
result = result.replaceText(builder -> builder.matchLiteral(":sun:").replacement("☀"));
result = result.replaceText(builder -> builder.matchLiteral(":moon:").replacement("☽"));
result = result.replaceText(builder -> builder.matchLiteral(":snowflake:").replacement("❄"));
result = result.replaceText(builder -> builder.matchLiteral(":fire:").replacement("🔥"));
result = result.replaceText(builder -> builder.matchLiteral(":diamond:").replacement("◆"));
result = result.replaceText(builder -> builder.matchLiteral(":sword:").replacement("⚔"));
result = result.replaceText(builder -> builder.matchLiteral(":pickaxe:").replacement("⛏"));
result = result.replaceText(builder -> builder.matchLiteral(":bow:").replacement("🏹"));
result = result.replaceText(builder -> builder.matchLiteral(":shield:").replacement("🛡"));
result = result.replaceText(builder -> builder.matchLiteral(":potion:").replacement("🧪"));
result = result.replaceText(builder -> builder.matchLiteral(":book:").replacement("📖"));
result = result.replaceText(builder -> builder.matchLiteral(":crown:").replacement("👑"));
result = result.replaceText(builder -> builder.matchLiteral(":gem:").replacement("💎"));
return result;
}
event.renderer(new EnhancedChatRenderer(plugin, sender, event.message(), isGlobal));
}
}

View file

@ -71,10 +71,8 @@ public class CustomCommandManager {
private void registerCommand(CustomCommandData data) {
try {
// Получаем CommandMap через рефлексию
Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
commandMapField.setAccessible(true);
CommandMap commandMap = (CommandMap) commandMapField.get(Bukkit.getServer());
// Получаем CommandMap через Paper API (безопаснее чем рефлексия)
CommandMap commandMap = Bukkit.getCommandMap();
// Создаем и регистрируем команду
CustomCommand command = new CustomCommand(data.name, this);

View file

@ -0,0 +1,155 @@
package com.loki.lochat.renderer;
import com.loki.lochat.utils.MentionHandler;
import com.loki.lochat.utils.TextFormatter;
import io.papermc.paper.chat.ChatRenderer;
import net.kyori.adventure.audience.Audience;
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.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Рендерер чата с поддержкой глобального/локального режимов
*/
public class EnhancedChatRenderer implements ChatRenderer {
private final JavaPlugin plugin;
private final Player sender;
private final Component originalMessage;
private final boolean isGlobal;
private final MentionHandler mentionHandler;
public EnhancedChatRenderer(JavaPlugin plugin, Player sender, Component originalMessage, boolean isGlobal) {
this.plugin = plugin;
this.sender = sender;
this.originalMessage = originalMessage;
this.isGlobal = isGlobal;
this.mentionHandler = new MentionHandler(plugin);
}
@Override
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
// Префикс в зависимости от типа чата
Component prefixComponent;
if (isGlobal) {
// [G] с желтой буквой G
prefixComponent = Component.text("[")
.color(net.kyori.adventure.text.format.NamedTextColor.GRAY)
.append(Component.text("G").color(net.kyori.adventure.text.format.NamedTextColor.GOLD))
.append(Component.text("]").color(net.kyori.adventure.text.format.NamedTextColor.GRAY));
} else {
// [L] с голубой буквой L
prefixComponent = Component.text("[")
.color(net.kyori.adventure.text.format.NamedTextColor.GRAY)
.append(Component.text("L").color(net.kyori.adventure.text.format.NamedTextColor.AQUA))
.append(Component.text("]").color(net.kyori.adventure.text.format.NamedTextColor.GRAY));
}
Component playerComponent = createPlayerComponent(source, sourceDisplayName);
Component processedMessage = message;
processedMessage = TextFormatter.formatMarkdown(processedMessage);
processedMessage = processUrls(processedMessage);
processedMessage = processEmojis(processedMessage);
if (viewer instanceof Player viewerPlayer) {
processedMessage = mentionHandler.processMentions(processedMessage, source, viewerPlayer);
}
return prefixComponent
.append(Component.text(" "))
.append(playerComponent)
.append(Component.text(": ").color(net.kyori.adventure.text.format.NamedTextColor.GRAY))
.append(processedMessage);
}
private Component createPlayerComponent(Player player, Component displayName) {
if (!plugin.getConfig().getBoolean("chat.hover.enabled", true)) {
return displayName.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
}
java.util.List<String> hoverLines = plugin.getConfig().getStringList("chat.hover.format");
if (hoverLines.isEmpty()) {
hoverLines = java.util.Arrays.asList(
"<gray>Игрок: <white>{player}",
"<gray>Мир: <green>{world}",
"",
"<yellow>▶ Клик: написать ЛС"
);
}
net.kyori.adventure.text.TextComponent.Builder hoverBuilder = Component.text();
for (int i = 0; i < hoverLines.size(); i++) {
String line = hoverLines.get(i);
line = line.replace("{player}", player.getName())
.replace("{world}", player.getWorld().getName())
.replace("{ping}", String.valueOf(player.getPing()))
.replace("{gamemode}", player.getGameMode().name())
.replace("{health}", String.valueOf(Math.round(player.getHealth())))
.replace("{food}", String.valueOf(player.getFoodLevel()));
Component lineComponent = com.loki.lochat.utils.ChatFormatter.parse(line);
hoverBuilder.append(lineComponent);
if (i < hoverLines.size() - 1) {
hoverBuilder.append(Component.newline());
}
}
return displayName
.hoverEvent(HoverEvent.showText(hoverBuilder.build()))
.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
}
private Component processUrls(Component message) {
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
// Fixed regex: escape the dash in character class to avoid illegal range
String urlPattern = "(https?://[\\w\\-.~:/?#\\[\\]@!$&'()*+,;=%]+)";
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(urlPattern);
java.util.regex.Matcher matcher = pattern.matcher(plainText);
Component result = message;
while (matcher.find()) {
String url = matcher.group(1);
Component urlComponent = Component.text(url, NamedTextColor.AQUA, TextDecoration.UNDERLINED)
.hoverEvent(HoverEvent.showText(Component.text("Открыть: " + url, NamedTextColor.GRAY)))
.clickEvent(ClickEvent.openUrl(url));
result = result.replaceText(builder ->
builder.matchLiteral(url).replacement(urlComponent)
);
}
return result;
}
private Component processEmojis(Component message) {
Component result = message;
result = result.replaceText(builder -> builder.matchLiteral(":heart:").replacement("❤"));
result = result.replaceText(builder -> builder.matchLiteral(":star:").replacement("★"));
result = result.replaceText(builder -> builder.matchLiteral(":check:").replacement("✔"));
result = result.replaceText(builder -> builder.matchLiteral(":cross:").replacement("✖"));
result = result.replaceText(builder -> builder.matchLiteral(":arrow:").replacement("→"));
result = result.replaceText(builder -> builder.matchLiteral(":skull:").replacement("☠"));
result = result.replaceText(builder -> builder.matchLiteral(":note:").replacement("♪"));
result = result.replaceText(builder -> builder.matchLiteral(":sun:").replacement("☀"));
result = result.replaceText(builder -> builder.matchLiteral(":moon:").replacement("☽"));
result = result.replaceText(builder -> builder.matchLiteral(":snowflake:").replacement("❄"));
result = result.replaceText(builder -> builder.matchLiteral(":fire:").replacement("🔥"));
result = result.replaceText(builder -> builder.matchLiteral(":diamond:").replacement("◆"));
result = result.replaceText(builder -> builder.matchLiteral(":sword:").replacement("⚔"));
result = result.replaceText(builder -> builder.matchLiteral(":pickaxe:").replacement("⛏"));
result = result.replaceText(builder -> builder.matchLiteral(":bow:").replacement("🏹"));
result = result.replaceText(builder -> builder.matchLiteral(":shield:").replacement("🛡"));
result = result.replaceText(builder -> builder.matchLiteral(":potion:").replacement("🧪"));
result = result.replaceText(builder -> builder.matchLiteral(":book:").replacement("📖"));
result = result.replaceText(builder -> builder.matchLiteral(":crown:").replacement("👑"));
result = result.replaceText(builder -> builder.matchLiteral(":gem:").replacement("💎"));
return result;
}
}

View file

@ -1,34 +0,0 @@
package com.loki.lochat.utils;
import org.bukkit.Location;
import org.bukkit.entity.Player;
public class DistanceUtil {
private DistanceUtil() {
}
public static boolean isInRange(Player player1, Player player2, int radius) {
// Проверка на один мир
if (!player1.getWorld().equals(player2.getWorld())) {
return false;
}
Location loc1 = player1.getLocation();
Location loc2 = player2.getLocation();
// Быстрая проверка по квадрату (без sqrt)
double dx = loc1.getX() - loc2.getX();
double dz = loc1.getZ() - loc2.getZ();
double radiusSquared = (double) radius * radius;
return (dx * dx + dz * dz) <= radiusSquared;
}
public static double getDistance(Player player1, Player player2) {
if (!player1.getWorld().equals(player2.getWorld())) {
return Double.MAX_VALUE;
}
return player1.getLocation().distance(player2.getLocation());
}
}