Update to Minecraft 1.21.8, add checkstyle, fix compilation errors

- Update Paper API: 1.20.1 -> 1.21.4
- Update plugin version: 1.5.5-1.20.1 -> 1.5.5-1.21.8
- Add checkstyle plugin with reasonable rules
- Fix Enchantment.LUCK -> Enchantment.UNBREAKING
- Fix EntityType.FIREWORK -> EntityType.FIREWORK_ROCKET
- Remove unused imports
- Add temp_*/ to .gitignore
- 118 files changed, 1633 insertions(+), 1301 deletions(-)
This commit is contained in:
loki5512344 2026-02-24 00:04:03 +01:00
parent 6a4c8a7886
commit e78d8a2de4
121 changed files with 1792 additions and 1301 deletions

View file

@ -31,71 +31,76 @@ public final class LoChat extends JavaPlugin {
private AutoMessageManager autoMessageManager;
private CustomCommandManager customCommandManager;
// Геттеры
public static LoChat getInstance() {
return instance;
}
@Override
public void onEnable() {
instance = this;
getLogger().info("Загрузка LoChat v" + getPluginMeta().getVersion() + " для Paper/Folia " + Bukkit.getMinecraftVersion() + "...");
// Конфиги
configManager = new ConfigManager(this);
messageConfig = new MessageConfig(this);
// Service Registry (SOLID DI) - с зависимостями
serviceRegistry = new ServiceRegistry(this, configManager, messageConfig);
serviceRegistry = ServiceRegistry.createWithDeps(this, configManager, messageConfig);
getLogger().info("ServiceRegistry инициализирован (SOLID DI)");
// Менеджеры
customCommandManager = new CustomCommandManager(this);
customCommandManager = CustomCommandManager.create(this);
autoMessageManager = new AutoMessageManager(this);
// Интеграции
initIntegrations();
// Команды и слушатели
registerCommands();
registerListeners();
// Автосообщения
autoMessageManager.start();
getLogger().info("LoChat успешно запущен!");
}
@Override
public void onDisable() {
if (autoMessageManager != null) autoMessageManager.stop();
// Сохранение данных через сервисы
IgnoreService ignoreService = serviceRegistry.get(IgnoreService.class);
if (ignoreService != null) ignoreService.save();
MuteService muteService = serviceRegistry.get(MuteService.class);
if (muteService != null) muteService.save();
NickService nickService = serviceRegistry.get(NickService.class);
if (nickService != null) nickService.save();
if (gradientModule != null) gradientModule.shutdown();
getLogger().info("LoChat отключен!");
}
private void initIntegrations() {
// Gradient модуль
gradientModule = new GradientModule(this);
if (gradientModule.init()) {
getLogger().info("Gradient модуль (LoPreff) загружен!");
}
// LibertyBans
new LibertyBansHook(this);
// PlaceholderAPI
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
new PlaceholderAPIHook(this).register();
getLogger().info("PlaceholderAPI подключен!");
}
}
private void registerCommands() {
getCommand("g").setExecutor(new GlobalChatCommand(this));
getCommand("l").setExecutor(new LocalChatCommand(this));
@ -106,44 +111,44 @@ public final class LoChat extends JavaPlugin {
getCommand("announce").setExecutor(new AnnounceCommand(this));
getCommand("chatspy").setExecutor(new ChatSpyCommand(this));
getCommand("clearchat").setExecutor(new ClearChatCommand(this));
NickCommand nickCmd = new NickCommand(this);
getCommand("nick").setExecutor(nickCmd);
getCommand("nick").setTabCompleter(nickCmd);
LoChatCommand loChatCmd = new LoChatCommand(this);
getCommand("lochat").setExecutor(loChatCmd);
getCommand("lochat").setTabCompleter(loChatCmd);
MuteCommand muteCmd = new MuteCommand(this);
getCommand("lmute").setExecutor(muteCmd);
getCommand("lmute").setTabCompleter(muteCmd);
UnmuteCommand unmuteCmd = new UnmuteCommand(this);
getCommand("lunmute").setExecutor(unmuteCmd);
getCommand("lunmute").setTabCompleter(unmuteCmd);
getCommand("lmutelist").setExecutor(new MuteListCommand(this));
MuteHistoryCommand historyCmd = new MuteHistoryCommand(this);
getCommand("lmutehistory").setExecutor(historyCmd);
getCommand("lmutehistory").setTabCompleter(historyCmd);
MuteBlameCommand blameCmd = new MuteBlameCommand(this);
getCommand("lmuteblame").setExecutor(blameCmd);
getCommand("lmuteblame").setTabCompleter(blameCmd);
ClearChatConfigCommand clearCfgCmd = new ClearChatConfigCommand(this);
getCommand("clearchatconfig").setExecutor(clearCfgCmd);
getCommand("clearchatconfig").setTabCompleter(clearCfgCmd);
}
private void registerListeners() {
getServer().getPluginManager().registerEvents(new ChatEventListener(this, serviceRegistry), this);
getServer().getPluginManager().registerEvents(new PlayerEventListener(serviceRegistry), this);
getLogger().info("Слушатели зарегистрированы (новая архитектура)");
}
public void reload() {
configManager.reload();
messageConfig.reload();
@ -152,11 +157,23 @@ public final class LoChat extends JavaPlugin {
if (customCommandManager != null) customCommandManager.reload();
}
// Геттеры
public static LoChat getInstance() { return instance; }
public ServiceRegistry getServiceRegistry() { return serviceRegistry; }
public ConfigManager getConfigManager() { return configManager; }
public MessageConfig getMessageConfig() { return messageConfig; }
public GradientModule getGradientModule() { return gradientModule; }
public CustomCommandManager getCustomCommandManager() { return customCommandManager; }
public ServiceRegistry getServiceRegistry() {
return serviceRegistry;
}
public ConfigManager getConfigManager() {
return configManager;
}
public MessageConfig getMessageConfig() {
return messageConfig;
}
public GradientModule getGradientModule() {
return gradientModule;
}
public CustomCommandManager getCustomCommandManager() {
return customCommandManager;
}
}

View file

@ -7,7 +7,10 @@ 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

@ -7,7 +7,10 @@ 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

@ -8,6 +8,7 @@ import org.bukkit.entity.Player;
public interface MessageFilter {
/**
* Применить фильтр к сообщению
*
* @return отфильтрованное сообщение или null если заблокировано
*/
Object apply(Player player, Object message, String plainMessage);

View file

@ -7,9 +7,14 @@ 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

@ -9,6 +9,7 @@ import org.bukkit.entity.Player;
public interface MessageFilter {
/**
* Применить фильтр к сообщению
*
* @return true если сообщение прошло фильтр, false если заблокировано
*/
boolean apply(Player player, ChatMessage message);

View file

@ -9,7 +9,10 @@ import java.util.UUID;
*/
public interface ChatService {
void sendGlobalMessage(Player sender, Object message);
void sendLocalMessage(Player sender, Object message);
boolean toggleGlobalChat(UUID player);
boolean isGlobalChatDisabled(UUID player);
}

View file

@ -7,7 +7,10 @@ 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

@ -11,39 +11,41 @@ public interface IgnoreService {
* Проверяет, игнорирует ли игрок другого
*/
boolean isIgnoring(UUID player, UUID target);
/**
* Добавляет игрока в список игнорируемых
*
* @return true если успешно добавлен
*/
boolean addIgnore(UUID player, UUID target);
/**
* Удаляет игрока из списка игнорируемых
*
* @return true если успешно удалён
*/
boolean removeIgnore(UUID player, UUID target);
/**
* Получает список всех игнорируемых игроков
*/
Set<UUID> getIgnoredPlayers(UUID player);
/**
* Получает количество игнорируемых игроков
*/
int getIgnoredCount(UUID player);
/**
* Очищает список игнорируемых для игрока
*/
void clearIgnores(UUID player);
/**
* Сохраняет данные на диск
*/
void save();
/**
* Загружает данные с диска
*/

View file

@ -10,26 +10,27 @@ import java.util.Set;
public interface MentionService {
/**
* Обрабатывает упоминания в сообщении
*
* @return сообщение с подсвеченными никами
*/
String processMentions(String message, Set<Player> mentionedPlayers);
/**
* Создаёт персонализированное сообщение для игрока
* Если его ник упомянут — выделяет его особым образом
*/
String getPersonalizedMessage(String message, Player viewer);
/**
* Проверяет, упомянут ли игрок в сообщении (по нику без @)
*/
boolean isPlayerMentioned(String message, Player player);
/**
* Уведомляет упомянутых игроков звуком
*/
void notifyMentioned(Set<Player> players);
/**
* Уведомляет игрока если его ник упомянут (без @)
*/

View file

@ -8,6 +8,7 @@ import org.bukkit.entity.Player;
public interface MessageService {
/**
* Обработать входящее сообщение чата
*
* @return true если сообщение обработано успешно
*/
boolean processMessage(Player player, String message);

View file

@ -13,30 +13,38 @@ 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);
MuteData getMuteData(UUID uuid);
long getRemainingTime(UUID uuid);
// Утилиты времени
String formatTime(long millis);
long parseTime(String timeStr);
// Права доступа
long getMaxDuration(Player player);
boolean canMuteForDuration(Player player, long duration);
// История
List<MuteData.MuteHistoryEntry> getPlayerHistory(UUID uuid);
List<MuteData.MuteHistoryEntry> getMutesByOperator(String operatorName);
UUID getUUIDByName(String name);
// Список активных мутов
Map<UUID, MuteData> getActiveMutes();
// Форматирование сообщений
String formatMessage(String message, String player, String operator, String duration, String reason);
// Сохранение
void save();
}

View file

@ -9,48 +9,54 @@ import java.util.UUID;
* Сервис для управления кастомными никами
*/
public interface NickService {
/**
* Устанавливает кастомный ник игроку
* @param player UUID игрока
*
* @param player UUID игрока
* @param nickname Новый ник (с поддержкой цветов и русских символов)
* @return true если успешно, false если ник занят или невалиден
*/
boolean setNickname(UUID player, String nickname);
/**
* Сбрасывает кастомный ник игрока
*
* @param player UUID игрока
*/
void resetNickname(UUID player);
/**
* Получает кастомный ник игрока
*
* @param player UUID игрока
* @return Optional с ником или empty если нет кастомного ника
*/
Optional<String> getNickname(UUID player);
/**
* Проверяет занят ли ник
*
* @param nickname Ник для проверки
* @return true если ник уже используется
*/
boolean isNicknameTaken(String nickname);
/**
* Валидирует ник (длина, символы)
*
* @param nickname Ник для проверки
* @return true если ник валиден
*/
boolean isValidNickname(String nickname);
/**
* Обновляет displayName и tabName игрока
*
* @param player Игрок
*/
void updatePlayerDisplay(Player player);
/**
* Сохраняет данные
*/

View file

@ -11,17 +11,17 @@ public interface PMService {
* Устанавливает последнего собеседника для игрока
*/
void setLastConversation(UUID player, UUID target);
/**
* Получает последнего собеседника игрока
*/
Optional<UUID> getLastConversation(UUID player);
/**
* Удаляет информацию о последнем разговоре
*/
void removeConversation(UUID player);
/**
* Проверяет, есть ли у игрока активный разговор
*/

View file

@ -7,5 +7,6 @@ import java.util.UUID;
*/
public interface PlayerDataService {
void clearPlayerData(UUID player);
void saveAll();
}

View file

@ -9,25 +9,26 @@ import org.bukkit.entity.Player;
public interface SpyService {
/**
* Переключает режим шпиона для игрока
*
* @return true если шпион включен, false если выключен
*/
boolean toggleSpy(java.util.UUID player);
/**
* Проверяет, находится ли игрок в режиме шпиона
*/
boolean isSpying(java.util.UUID player);
/**
* Отправляет сообщение всем шпионам (для обычного чата)
*/
void sendToSpies(Player sender, Component message, boolean isGlobal);
/**
* Отправляет информацию о личном сообщении шпионам
*/
void broadcastPM(Player sender, Player receiver, String message);
/**
* Удаляет игрока из списка шпионов
*/

View file

@ -42,20 +42,20 @@ public class AnnounceCommand implements CommandExecutor {
// Отправка в чат
for (Player player : Bukkit.getOnlinePlayers()) {
player.sendMessage(formatted);
// ActionBar (если включен)
if (plugin.getConfigManager().isAnnouncementActionBarEnabled()) {
player.sendActionBar(formatted);
}
// Title (если включен)
if (plugin.getConfigManager().isAnnouncementTitleEnabled()) {
Title title = Title.title(
ChatFormatter.parse(plugin.getConfigManager().getAnnouncementTitleHeader()),
ChatFormatter.parse(message),
Title.Times.times(
Duration.ofMillis(500),
Duration.ofSeconds(plugin.getConfigManager().getAnnouncementTitleDuration()),
Duration.ofMillis(500),
Duration.ofSeconds(plugin.getConfigManager().getAnnouncementTitleDuration()),
Duration.ofMillis(500)
)
);

View file

@ -24,7 +24,7 @@ public class ClearChatCommand implements CommandExecutor {
for (int i = 0; i < com.loki.lochat.utils.Constants.CLEAR_CHAT_LINES; i++) {
player.sendMessage("");
}
// Уведомление (если включено)
if (plugin.getConfigManager().isClearChatMessageEnabled()) {
String message = plugin.getConfigManager().getClearChatMessage();

View file

@ -56,7 +56,7 @@ public class ClearChatConfigCommand implements CommandExecutor, TabCompleter {
sender.sendMessage(ChatFormatter.parse("&cУкажите текст сообщения!"));
return true;
}
String message = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
plugin.getConfigManager().setClearChatMessage(message);
sender.sendMessage(ChatFormatter.parse("&aСообщение установлено: &f" + message));
@ -64,7 +64,7 @@ public class ClearChatConfigCommand implements CommandExecutor, TabCompleter {
case "status" -> {
boolean enabled = plugin.getConfigManager().isClearChatMessageEnabled();
String message = plugin.getConfigManager().getClearChatMessage();
sender.sendMessage(ChatFormatter.parse("&6Статус сообщения очистки чата:"));
sender.sendMessage(ChatFormatter.parse("&eВключено: " + (enabled ? "&aДа" : "&cНет")));
sender.sendMessage(ChatFormatter.parse("&eСообщение: &f" + message));

View file

@ -9,17 +9,13 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class CustomCommand extends Command {
public final class CustomCommand extends Command {
private final CustomCommandManager manager;
public CustomCommand(String name, CustomCommandManager manager) {
super(name);
this.manager = manager;
init(name);
}
private void init(String name) {
this.setDescription("Кастомная команда LoChat");
this.setUsage("/" + name);
}

View file

@ -18,9 +18,9 @@ import org.jetbrains.annotations.NotNull;
*/
public class GlobalChatCommand implements CommandExecutor {
private static final MiniMessage MM = MiniMessage.miniMessage();
private final LoChat plugin;
private final ChatService chatService;
private static final MiniMessage MM = MiniMessage.miniMessage();
public GlobalChatCommand(LoChat plugin) {
this.plugin = plugin;
@ -53,10 +53,10 @@ public class GlobalChatCommand implements CommandExecutor {
}
String message = String.join(" ", args);
// Конвертируем String в Component для новой сигнатуры ChatService
Component messageComponent = MM.deserialize(message);
chatService.sendGlobalMessage(player, messageComponent);
return true;
}

View file

@ -14,8 +14,8 @@ import java.util.List;
public class LoChatCommand implements CommandExecutor, TabCompleter {
private final LoChat plugin;
private static final MiniMessage MM = MiniMessage.miniMessage();
private final LoChat plugin;
public LoChatCommand(LoChat plugin) {
this.plugin = plugin;
@ -51,14 +51,15 @@ public class LoChatCommand implements CommandExecutor, TabCompleter {
var commands = plugin.getCustomCommandManager().getCommands();
sender.sendMessage(MM.deserialize("&#FFD700Кастомные команды (" + commands.size() + "):"));
for (var cmd : commands.values()) {
sender.sendMessage(MM.deserialize("&e/" + cmd.name + " &#808080(" + cmd.type + ")"));
if (!cmd.aliases.isEmpty()) {
sender.sendMessage(MM.deserialize("&#808080 Алиасы: " + String.join(", ", cmd.aliases)));
sender.sendMessage(MM.deserialize("&e/" + cmd.name() + " &#808080(" + cmd.type() + ")"));
if (!cmd.aliases().isEmpty()) {
sender.sendMessage(MM.deserialize("&#808080 Алиасы: " + String.join(", ", cmd.aliases())));
}
}
}
}
default -> sender.sendMessage(MM.deserialize("&#FF0000Неизвестная команда. Используйте /lochat для помощи."));
default ->
sender.sendMessage(MM.deserialize("&#FF0000Неизвестная команда. Используйте /lochat для помощи."));
}
return true;
@ -66,7 +67,7 @@ public class LoChatCommand implements CommandExecutor, TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
@NotNull String alias, @NotNull String[] args) {
if (!sender.hasPermission("lochat.admin")) {
return new ArrayList<>();
}
@ -74,7 +75,7 @@ public class LoChatCommand implements CommandExecutor, TabCompleter {
if (args.length == 1) {
List<String> completions = new ArrayList<>();
String input = args[0].toLowerCase();
for (String cmd : List.of("reload", "commands")) {
if (cmd.startsWith(input)) {
completions.add(cmd);
@ -82,11 +83,11 @@ public class LoChatCommand implements CommandExecutor, TabCompleter {
}
return completions;
}
if (args.length == 2 && args[0].equalsIgnoreCase("commands")) {
List<String> completions = new ArrayList<>();
String input = args[1].toLowerCase();
if ("reload".startsWith(input)) {
completions.add("reload");
}

View file

@ -17,9 +17,9 @@ import org.jetbrains.annotations.NotNull;
*/
public class LocalChatCommand implements CommandExecutor {
private static final MiniMessage MM = MiniMessage.miniMessage();
private final LoChat plugin;
private final ChatService chatService;
private static final MiniMessage MM = MiniMessage.miniMessage();
public LocalChatCommand(LoChat plugin) {
this.plugin = plugin;
@ -39,10 +39,10 @@ public class LocalChatCommand implements CommandExecutor {
}
String message = String.join(" ", args);
// Конвертируем String в Component для новой сигнатуры ChatService
Component messageComponent = MM.deserialize(message);
chatService.sendLocalMessage(player, messageComponent);
return true;
}

View file

@ -5,6 +5,7 @@ import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.api.service.PMService;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.utils.ChatFormatter;
import com.loki.lochat.utils.PlayerUtil;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.Command;
@ -89,10 +90,10 @@ public class MsgCommand implements CommandExecutor {
// Звук для получателя
if (plugin.getConfigManager().isPmSoundEnabled()) {
try {
Sound sound = Sound.valueOf(plugin.getConfigManager().getPmSoundType());
Sound sound = PlayerUtil.parseSound(plugin.getConfigManager().getPmSoundType(), null);
if (sound != null) {
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
}
// Шпион

View file

@ -101,7 +101,7 @@ public class MuteBlameCommand implements CommandExecutor, TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
@NotNull String alias, @NotNull String[] args) {
List<String> completions = new ArrayList<>();
if (args.length == 1) {

View file

@ -73,7 +73,7 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
// Получаем настройки из конфига
String defaultReason = plugin.getConfigManager().getString("mute.default-reason", "Нету.");
String defaultDuration = plugin.getConfigManager().getString("mute.default-duration", "7d");
String reason = reasonBuilder.length() > 0 ? reasonBuilder.toString() : defaultReason;
// Определяем длительность
@ -136,7 +136,7 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
// Уведомляем замученного (если онлайн)
if (target != null) {
String msgKey = duration == 0 ? "mute.messages.you-muted-permanent" : "mute.messages.you-muted";
String msg = plugin.getConfigManager().getString(msgKey,
String msg = plugin.getConfigManager().getString(msgKey,
duration == 0 ? "§cВы замучены навсегда! Причина: %reason%" : "§cВы замучены на %duration%! Причина: %reason%");
msg = muteService.formatMessage(msg, finalTargetName, operatorName, timeDisplay, reason);
target.sendMessage(ChatFormatter.parse(msg));
@ -147,7 +147,7 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
String silentMsg = plugin.getConfigManager().getString("mute.messages.silent-muted",
"§8[Тихо] §c%player% §7замучен на §c%duration% §7(%operator%). Причина: %reason%");
silentMsg = muteService.formatMessage(silentMsg, finalTargetName, operatorName, timeDisplay, reason);
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.hasPermission("lochat.mute.see-silent")) {
p.sendMessage(ChatFormatter.parse(silentMsg));
@ -170,7 +170,7 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
@NotNull String alias, @NotNull String[] args) {
List<String> completions = new ArrayList<>();
if (args.length == 1) {

View file

@ -72,7 +72,7 @@ public class MuteHistoryCommand implements CommandExecutor, TabCompleter {
// Выводим от новых к старым
for (int i = history.size() - 1; i >= 0; i--) {
MuteData.MuteHistoryEntry entry = history.get(i);
// Статус
String status;
if (entry.unmuted) {
@ -99,13 +99,13 @@ public class MuteHistoryCommand implements CommandExecutor, TabCompleter {
sender.sendMessage(" §7Длительность: §f" + durationStr);
sender.sendMessage(" §7Причина: §f" + (entry.reason != null ? entry.reason : "Не указана"));
sender.sendMessage(" §7Выдал: §f" + entry.mutedBy);
if (entry.unmuted && entry.unmutedBy != null) {
sender.sendMessage(" §7Размутил: §f" + entry.unmutedBy + " §7(" + dateFormat.format(new Date(entry.unmutedAt)) + ")");
}
index++;
// Ограничиваем вывод
if (index > 10) {
sender.sendMessage("§7... и ещё " + (history.size() - 10) + " записей");
@ -118,7 +118,7 @@ public class MuteHistoryCommand implements CommandExecutor, TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
@NotNull String alias, @NotNull String[] args) {
List<String> completions = new ArrayList<>();
if (args.length == 1) {

View file

@ -45,19 +45,19 @@ public class MuteListCommand implements CommandExecutor {
}
sender.sendMessage("§6§l=== Активные муты (" + activeMutes.size() + ") ===");
int index = 1;
for (Map.Entry<UUID, MuteData> entry : activeMutes.entrySet()) {
UUID uuid = entry.getKey();
MuteData data = entry.getValue();
// Получаем имя игрока
String playerName = data.getPlayerName();
if (playerName == null) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
playerName = offlinePlayer.getName() != null ? offlinePlayer.getName() : uuid.toString().substring(0, 8);
}
// Форматируем время
String timeStr;
if (data.isPermanent()) {
@ -66,11 +66,11 @@ public class MuteListCommand implements CommandExecutor {
long remaining = data.getEndTime() - System.currentTimeMillis();
timeStr = "§e" + muteService.formatTime(remaining);
}
sender.sendMessage("§7" + index + ". §f" + playerName + " §7- " + timeStr);
sender.sendMessage(" §7Причина: §f" + (data.getReason() != null ? data.getReason() : "Не указана"));
sender.sendMessage(" §7Выдал: §f" + data.getMutedBy() + " §7(" + dateFormat.format(new Date(data.getMutedAt())) + ")");
index++;
}

View file

@ -17,39 +17,39 @@ import java.util.List;
public class NickCommand implements CommandExecutor, TabCompleter {
private final LoChat plugin;
private final NickService nickService;
public NickCommand(LoChat plugin) {
this.plugin = plugin;
this.nickService = plugin.getServiceRegistry().get(NickService.class);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(Component.text("Эта команда только для игроков!", NamedTextColor.RED));
return true;
}
if (!player.hasPermission("lochat.nick")) {
player.sendMessage(Component.text("У вас нет прав на использование этой команды!", NamedTextColor.RED));
return true;
}
if (args.length == 0) {
return showCurrentNick(player);
}
if (args[0].equalsIgnoreCase("reset")) {
return resetNick(player);
}
String nickname = String.join(" ", args);
return setNick(player, nickname);
}
private boolean showCurrentNick(Player player) {
var currentNick = nickService.getNickname(player.getUniqueId());
if (currentNick.isPresent()) {
Component nickComponent = ChatFormatter.parse(currentNick.get());
player.sendMessage(Component.text("Ваш текущий ник: ", NamedTextColor.GRAY)
@ -63,23 +63,23 @@ public class NickCommand implements CommandExecutor, TabCompleter {
.append(Component.text("/nick <ник>", NamedTextColor.YELLOW))
.append(Component.text(" для установки", NamedTextColor.GRAY)));
}
return true;
}
private boolean resetNick(Player player) {
var currentNick = nickService.getNickname(player.getUniqueId());
if (currentNick.isEmpty()) {
player.sendMessage(Component.text("У вас нет кастомного ника!", NamedTextColor.RED));
return true;
}
nickService.resetNickname(player.getUniqueId());
player.sendMessage(Component.text("Ваш ник сброшен!", NamedTextColor.GREEN));
return true;
}
private boolean setNick(Player player, String nickname) {
if (!nickService.isValidNickname(nickname)) {
player.sendMessage(Component.text("Невалидный ник!", NamedTextColor.RED));
@ -89,7 +89,7 @@ public class NickCommand implements CommandExecutor, TabCompleter {
player.sendMessage(Component.text("• Поддержка цветов: &#FF0000текст, <gradient:&#FF0000:&#00FF00>текст</gradient>", NamedTextColor.GRAY));
return true;
}
if (nickService.isNicknameTaken(nickname)) {
var currentNick = nickService.getNickname(player.getUniqueId());
if (currentNick.isEmpty() || !currentNick.get().equals(nickname)) {
@ -97,9 +97,9 @@ public class NickCommand implements CommandExecutor, TabCompleter {
return true;
}
}
boolean success = nickService.setNickname(player.getUniqueId(), nickname);
if (success) {
Component nickComponent = ChatFormatter.parse(nickname);
player.sendMessage(Component.text("Ваш ник изменён на: ", NamedTextColor.GREEN)
@ -107,33 +107,33 @@ public class NickCommand implements CommandExecutor, TabCompleter {
} else {
player.sendMessage(Component.text("Не удалось установить ник!", NamedTextColor.RED));
}
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
List<String> completions = new ArrayList<>();
if (args.length == 1) {
String input = args[0].toLowerCase();
if ("reset".startsWith(input)) {
completions.add("reset");
}
if (sender instanceof Player player) {
var currentNick = nickService.getNickname(player.getUniqueId());
if (currentNick.isPresent()) {
completions.add(currentNick.get());
}
}
completions.add("&#FF0000Красный");
completions.add("&#00FF00Зелёный");
completions.add("Русский_Ник");
}
return completions;
}
}

View file

@ -41,7 +41,7 @@ public class PlayerInfoCommand implements CommandExecutor {
sender.sendMessage(ChatFormatter.parse("§eУровень: §f" + target.getLevel()));
sender.sendMessage(ChatFormatter.parse("§eЗдоровье: §f" + Math.round(target.getHealth()) + "/20"));
sender.sendMessage(ChatFormatter.parse("§eМир: §f" + target.getWorld().getName()));
// Если есть градиентный модуль, показываем градиентное имя
if (plugin.getGradientModule() != null) {
String gradientName = plugin.getGradientModule().getFormattedName(target);

View file

@ -5,6 +5,7 @@ import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.api.service.PMService;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.utils.ChatFormatter;
import com.loki.lochat.utils.PlayerUtil;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.Command;
@ -88,10 +89,10 @@ public class ReplyCommand implements CommandExecutor {
// Звук для получателя
if (plugin.getConfigManager().isPmSoundEnabled()) {
try {
Sound sound = Sound.valueOf(plugin.getConfigManager().getPmSoundType());
Sound sound = PlayerUtil.parseSound(plugin.getConfigManager().getPmSoundType(), null);
if (sound != null) {
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
}
// Шпион

View file

@ -107,7 +107,7 @@ public class UnmuteCommand implements CommandExecutor, TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
@NotNull String alias, @NotNull String[] args) {
List<String> completions = new ArrayList<>();
if (args.length == 1) {

View file

@ -15,7 +15,7 @@ public class ConfigManager {
this.plugin = plugin;
plugin.saveDefaultConfig();
this.config = plugin.getConfig();
// Проверяем версию конфига и обновляем при необходимости
updateConfig();
}
@ -23,17 +23,17 @@ public class ConfigManager {
private void updateConfig() {
int currentVersion = config.getInt("config-version", 1);
int requiredVersion = 2; // Текущая версия конфига
if (currentVersion < requiredVersion) {
plugin.getLogger().info("Обновление конфига с версии " + currentVersion + " до " + requiredVersion);
// Добавляем новые настройки если их нет
addMissingConfigOptions();
// Обновляем версию
config.set("config-version", requiredVersion);
plugin.saveConfig();
plugin.getLogger().info("Конфиг успешно обновлен!");
}
}
@ -46,12 +46,12 @@ public class ConfigManager {
config.set("announcements.show-actionbar", true);
config.set("announcements.title-duration", 3);
}
// Можно добавить другие недостающие настройки
if (!config.contains("chat.pm.enabled")) {
config.set("chat.pm.enabled", true);
}
if (!config.contains("mentions.enabled")) {
config.set("mentions.enabled", true);
config.set("mentions.sound", true);
@ -59,18 +59,18 @@ public class ConfigManager {
config.set("mentions.highlight", "&#FFFF00@{player}");
config.set("mentions.self-highlight", "&#FFD700{player}");
}
if (!config.contains("automessages.enabled")) {
config.set("automessages.enabled", true);
config.set("automessages.interval", 300);
config.set("automessages.random", false);
}
if (!config.contains("clearchat.message.enabled")) {
config.set("clearchat.message.enabled", true);
config.set("clearchat.message.text", "&#04CADFЧат очищен администратором {player}");
}
if (!config.contains("lopreff.enabled")) {
config.set("lopreff.enabled", true);
config.set("lopreff.use-gradient-name", true);
@ -197,21 +197,21 @@ public class ConfigManager {
public String getSelfMentionHighlight() {
return ChatFormatter.convertAllColors(config.getString("mentions.self-highlight", "&#FFD700{player}"));
}
// Clear chat
public boolean isClearChatMessageEnabled() {
return config.getBoolean("clearchat.message.enabled", true);
}
public void setClearChatMessageEnabled(boolean enabled) {
config.set("clearchat.message.enabled", enabled);
plugin.saveConfig();
}
public String getClearChatMessage() {
return config.getString("clearchat.message.text", "&#04CADFЧат очищен администратором {player}");
}
public void setClearChatMessage(String message) {
config.set("clearchat.message.text", message);
plugin.saveConfig();

View file

@ -8,127 +8,127 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AdvancedMessageFilter {
private static final Pattern URL_PATTERN = Pattern.compile(
"(https?://)?([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?",
Pattern.CASE_INSENSITIVE
"(https?://)?([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?",
Pattern.CASE_INSENSITIVE
);
private static final Pattern IP_PATTERN = Pattern.compile(
"\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"
"\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"
);
private static final Pattern REPEAT_PATTERN = Pattern.compile(
"(.)\\1{3,}"
"(.)\\1{3,}"
);
private final FileConfiguration config;
private final List<String> whitelistedDomains;
private final List<String> blacklistedDomains;
public AdvancedMessageFilter(FileConfiguration config) {
this.config = config;
this.whitelistedDomains = config.getStringList("filters.url.whitelist");
this.blacklistedDomains = config.getStringList("filters.url.blacklist");
}
public FilterResult filterMessage(Player player, String message) {
if (config.getBoolean("filters.url.enabled", true)) {
FilterResult urlResult = filterUrls(player, message);
if (!urlResult.isAllowed()) {
if (!urlResult.allowed()) {
return urlResult;
}
message = urlResult.getFilteredMessage();
message = urlResult.filteredMessage();
}
if (config.getBoolean("filters.ip.enabled", true)) {
FilterResult ipResult = filterIPs(player, message);
if (!ipResult.isAllowed()) {
if (!ipResult.allowed()) {
return ipResult;
}
message = ipResult.getFilteredMessage();
message = ipResult.filteredMessage();
}
if (config.getBoolean("filters.repeat.enabled", true)) {
message = filterRepeatingChars(message);
}
return new FilterResult(true, message, null);
}
private FilterResult filterUrls(Player player, String message) {
if (player.hasPermission("lochat.bypass.urlfilter")) {
return new FilterResult(true, message, null);
}
Matcher matcher = URL_PATTERN.matcher(message);
while (matcher.find()) {
String url = matcher.group();
String domain = extractDomain(url);
if (!whitelistedDomains.isEmpty()) {
boolean isWhitelisted = whitelistedDomains.stream()
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
if (!isWhitelisted) {
return new FilterResult(false, message,
config.getString("filters.url.blocked-message", "&#FF6B6BСсылки запрещены!"));
return new FilterResult(false, message,
config.getString("filters.url.blocked-message", "&#FF6B6BСсылки запрещены!"));
}
}
if (!blacklistedDomains.isEmpty()) {
boolean isBlacklisted = blacklistedDomains.stream()
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
if (isBlacklisted) {
return new FilterResult(false, message,
config.getString("filters.url.blocked-message", "&#FF6B6BЭта ссылка запрещена!"));
config.getString("filters.url.blocked-message", "&#FF6B6BЭта ссылка запрещена!"));
}
}
}
return new FilterResult(true, message, null);
}
private FilterResult filterIPs(Player player, String message) {
if (player.hasPermission("lochat.bypass.ipfilter")) {
return new FilterResult(true, message, null);
}
Matcher matcher = IP_PATTERN.matcher(message);
if (matcher.find()) {
String replacement = config.getString("filters.ip.replacement", "[IP скрыт]");
String filtered = matcher.replaceAll(replacement);
if (config.getBoolean("filters.ip.block", false)) {
return new FilterResult(false, message,
config.getString("filters.ip.blocked-message", "&#FF6B6BIP адреса запрещены!"));
config.getString("filters.ip.blocked-message", "&#FF6B6BIP адреса запрещены!"));
}
return new FilterResult(true, filtered, null);
}
return new FilterResult(true, message, null);
}
private String filterRepeatingChars(String message) {
int maxRepeats = config.getInt("filters.repeat.max-repeats", 3);
Matcher matcher = REPEAT_PATTERN.matcher(message);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String repeated = matcher.group(1);
String replacement = repeated.repeat(Math.min(maxRepeats, matcher.group().length()));
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(result);
return result.toString();
}
private String extractDomain(String url) {
url = url.replaceFirst("^https?://", "");
int slashIndex = url.indexOf('/');
@ -137,28 +137,7 @@ public class AdvancedMessageFilter {
}
return url;
}
public static class FilterResult {
private final boolean allowed;
private final String filteredMessage;
private final String blockReason;
public FilterResult(boolean allowed, String filteredMessage, String blockReason) {
this.allowed = allowed;
this.filteredMessage = filteredMessage;
this.blockReason = blockReason;
}
public boolean isAllowed() {
return allowed;
}
public String getFilteredMessage() {
return filteredMessage;
}
public String getBlockReason() {
return blockReason;
}
public record FilterResult(boolean allowed, String filteredMessage, String blockReason) {
}
}

View file

@ -13,30 +13,30 @@ import org.bukkit.plugin.java.JavaPlugin;
public class CooldownFilter implements MessageFilter {
private final CooldownService cooldownService;
private final FileConfiguration config;
public CooldownFilter(CooldownService cooldownService, JavaPlugin plugin) {
this.cooldownService = cooldownService;
this.config = plugin.getConfig();
}
@Override
public boolean apply(Player player, ChatMessage message) {
if (player.hasPermission("chat.bypass.cooldown")) {
return true;
}
String chatType = message.getChatType();
int cooldown = config.getInt("chat." + chatType + ".cooldown", 0);
if (cooldown > 0) {
if (cooldownService.isOnCooldown(player.getUniqueId(), chatType, cooldown)) {
int remaining = cooldownService.getRemainingCooldown(
player.getUniqueId(), chatType, cooldown);
player.getUniqueId(), chatType, cooldown);
player.sendMessage("§cПодождите " + remaining + " сек.");
return false;
}
}
cooldownService.setCooldown(player.getUniqueId(), chatType);
return true;
}

View file

@ -10,11 +10,11 @@ import org.bukkit.entity.Player;
*/
public class MuteFilter implements MessageFilter {
private final MuteService muteService;
public MuteFilter(MuteService muteService) {
this.muteService = muteService;
}
@Override
public boolean apply(Player player, ChatMessage message) {
if (muteService.isMuted(player.getUniqueId())) {
@ -23,7 +23,7 @@ public class MuteFilter implements MessageFilter {
}
return true;
}
private void sendMuteNotification(Player player) {
long remaining = muteService.getRemainingTime(player.getUniqueId());
String timeStr = muteService.formatTime(remaining);

View file

@ -12,25 +12,23 @@ import java.util.Map;
public class ServiceRegistry {
private final Map<Class<?>, Object> services = new HashMap<>();
private final JavaPlugin plugin;
public ServiceRegistry(JavaPlugin plugin) {
private ServiceRegistry(JavaPlugin plugin) {
this.plugin = plugin;
init();
}
public ServiceRegistry(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) {
this.plugin = plugin;
initWithDeps(configManager, messageConfig);
public static ServiceRegistry create(JavaPlugin plugin) {
ServiceRegistry registry = new ServiceRegistry(plugin);
registry.registerServices();
return registry;
}
private void init() {
registerServices();
public static ServiceRegistry createWithDeps(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) {
ServiceRegistry registry = new ServiceRegistry(plugin);
registry.registerServicesWithDeps(configManager, messageConfig);
return registry;
}
private void initWithDeps(ConfigManager configManager, MessageConfig messageConfig) {
registerServicesWithDeps(configManager, messageConfig);
}
private void registerServices() {
register(ChatService.class, new ChatServiceImpl(plugin, this));
register(MuteService.class, new MuteServiceImpl(plugin));
@ -38,25 +36,25 @@ public class ServiceRegistry {
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));
register(CooldownService.class, new CooldownServiceImpl());
register(MessageService.class, new MessageServiceImpl(plugin, this));
register(PlayerDataService.class, new PlayerDataServiceImpl(plugin));
register(PMService.class, new PMServiceImpl());
register(IgnoreService.class, new IgnoreServiceImpl(plugin));
register(SpyService.class, new SpyServiceImpl(plugin, messageConfig));
register(MentionService.class, new MentionServiceImpl(configManager));
register(NickService.class, new NickServiceImpl(plugin));
}
public <T> void register(Class<T> serviceClass, T implementation) {
services.put(serviceClass, implementation);
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> serviceClass) {
Object service = services.get(serviceClass);
@ -65,7 +63,7 @@ public class ServiceRegistry {
}
return (T) service;
}
public void shutdown() {
services.clear();
}

View file

@ -19,61 +19,61 @@ import java.util.concurrent.ConcurrentHashMap;
public class ChatServiceImpl implements ChatService {
private final JavaPlugin plugin;
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
public ChatServiceImpl(JavaPlugin plugin, ServiceRegistry registry) {
this.plugin = plugin;
}
@Override
public void sendGlobalMessage(Player sender, Object message) {
String format = plugin.getConfig().getString("chat.global.format",
"{prefix} {player}: {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());
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));
.replaceText(builder -> builder.matchLiteral("{player}").replacement(playerName))
.replaceText(builder -> builder.matchLiteral("{message}").replacement(messageComponent));
for (Player player : Bukkit.getOnlinePlayers()) {
if (!isGlobalChatDisabled(player.getUniqueId())) {
player.sendMessage(finalComponent);
}
}
}
@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());
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));
.replaceText(builder -> builder.matchLiteral("{player}").replacement(playerName))
.replaceText(builder -> builder.matchLiteral("{message}").replacement(messageComponent));
for (Player player : Bukkit.getOnlinePlayers()) {
if (DistanceUtil.isInRange(sender, player, radius)) {
player.sendMessage(finalComponent);
}
}
}
@Override
public boolean toggleGlobalChat(UUID player) {
if (globalChatDisabled.contains(player)) {
@ -84,7 +84,7 @@ public class ChatServiceImpl implements ChatService {
return false;
}
}
@Override
public boolean isGlobalChatDisabled(UUID player) {
return globalChatDisabled.contains(player);

View file

@ -12,40 +12,40 @@ import java.util.concurrent.ConcurrentHashMap;
public class CooldownServiceImpl implements CooldownService {
private final Map<UUID, Long> globalCooldowns = new ConcurrentHashMap<>();
private final Map<UUID, Long> localCooldowns = new ConcurrentHashMap<>();
@Override
public boolean isOnCooldown(UUID player, String type, int cooldownSeconds) {
Map<UUID, Long> cooldowns = getCooldownMap(type);
Long lastTime = cooldowns.get(player);
if (lastTime == null) return false;
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
return elapsed < cooldownSeconds;
}
@Override
public int getRemainingCooldown(UUID player, String type, int cooldownSeconds) {
Map<UUID, Long> cooldowns = getCooldownMap(type);
Long lastTime = cooldowns.get(player);
if (lastTime == null) return 0;
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
return Math.max(0, cooldownSeconds - (int) elapsed);
}
@Override
public void setCooldown(UUID player, String type) {
getCooldownMap(type).put(player, System.currentTimeMillis());
}
@Override
public void removeCooldown(UUID player) {
globalCooldowns.remove(player);
localCooldowns.remove(player);
}
private Map<UUID, Long> getCooldownMap(String type) {
return type.equals("global") ? globalCooldowns : localCooldowns;
}

View file

@ -2,6 +2,7 @@ package com.loki.lochat.core.service;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.gradient.util.FoliaUtil;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
@ -9,8 +10,6 @@ import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.configuration.file.YamlConfiguration;
/**
* Реализация сервиса игнорирования
*/
@ -39,13 +38,15 @@ public class IgnoreServiceImpl implements IgnoreService {
for (String ignoredStr : ignoredList) {
try {
ignored.add(UUID.fromString(ignoredStr));
} catch (Exception ignored2) {}
} catch (Exception ignored2) {
}
}
if (!ignored.isEmpty()) {
ignoreMap.put(uuid, ignored);
}
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
}
}

View file

@ -2,6 +2,7 @@ package com.loki.lochat.core.service;
import com.loki.lochat.api.service.MentionService;
import com.loki.lochat.config.ConfigManager;
import com.loki.lochat.utils.PlayerUtil;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
@ -17,9 +18,8 @@ import java.util.regex.Pattern;
* Реализация сервиса упоминаний
*/
public class MentionServiceImpl implements MentionService {
private final ConfigManager configManager;
private static final Pattern MENTION_PATTERN = Pattern.compile("@(\\w+)");
private final ConfigManager configManager;
// Хранит для какого игрока какие части сообщения выделить
private final Map<Player, Set<String>> highlightedNicks = new HashMap<>();
@ -46,7 +46,7 @@ public class MentionServiceImpl implements MentionService {
mentionedPlayers.add(mentioned);
String replacement = highlight.replace("{player}", mentioned.getName());
matcher.appendReplacement(result, replacement);
// Запоминаем что этот игрок упомянут
highlightedNicks.computeIfAbsent(mentioned, k -> new HashSet<>()).add(mentioned.getName());
}
@ -64,23 +64,23 @@ public class MentionServiceImpl implements MentionService {
String viewerName = viewer.getName();
String result = message;
// Проверяем упоминание через @ник
String highlight = configManager.getMentionHighlight();
String highlightedName = highlight.replace("{player}", viewerName);
// Если в сообщении есть выделенный ник этого игрока — добавляем дополнительное выделение
if (result.contains(highlightedName)) {
// Уже выделено через processMentions, добавляем эффект
String extraHighlight = "<bold>" + highlightedName + "</bold>";
result = result.replace(highlightedName, extraHighlight);
}
// Также проверяем просто ник в сообщении (без @)
// Регистронезависимый поиск
Pattern nickPattern = Pattern.compile("(?i)\\b" + Pattern.quote(viewerName) + "\\b");
Matcher nickMatcher = nickPattern.matcher(result);
if (nickMatcher.find()) {
// Ник найден в сообщении — выделяем для этого игрока
String selfHighlight = configManager.getSelfMentionHighlight();
@ -104,12 +104,7 @@ public class MentionServiceImpl implements MentionService {
}
String soundName = configManager.getMentionSoundType();
Sound sound;
try {
sound = Sound.valueOf(soundName);
} catch (IllegalArgumentException e) {
sound = Sound.BLOCK_NOTE_BLOCK_PLING;
}
Sound sound = PlayerUtil.parseSound(soundName, Sound.BLOCK_NOTE_BLOCK_PLING);
for (Player player : players) {
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
@ -120,13 +115,13 @@ public class MentionServiceImpl implements MentionService {
public void notifyIfMentioned(String message, Player viewer, Player sender) {
if (viewer.equals(sender)) return;
if (!configManager.isMentionSoundEnabled()) return;
if (isPlayerMentioned(message, viewer)) {
String soundName = configManager.getMentionSoundType();
try {
Sound sound = Sound.valueOf(soundName);
Sound sound = PlayerUtil.parseSound(soundName, null);
if (sound != null) {
viewer.playSound(viewer.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
}
}
}

View file

@ -1,13 +1,14 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.filter.MessageFilter;
import com.loki.lochat.api.service.*;
import com.loki.lochat.api.service.ChatService;
import com.loki.lochat.api.service.CooldownService;
import com.loki.lochat.api.service.MessageService;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.core.filter.CooldownFilter;
import com.loki.lochat.core.filter.MuteFilter;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.data.model.ChatMessage;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
@ -21,29 +22,29 @@ public class MessageServiceImpl implements MessageService {
private final JavaPlugin plugin;
private final List<MessageFilter> filters;
private final ChatService chatService;
public MessageServiceImpl(JavaPlugin plugin, ServiceRegistry registry) {
this.plugin = plugin;
this.chatService = registry.get(ChatService.class);
this.filters = new ArrayList<>();
// Регистрируем фильтры в порядке применения
filters.add(new MuteFilter(registry.get(MuteService.class)));
filters.add(new CooldownFilter(registry.get(CooldownService.class), plugin));
}
@Override
public boolean processMessage(Player player, String rawMessage) {
// Создаем объект сообщения
ChatMessage message = ChatMessage.create(player, rawMessage);
// Применяем фильтры (мут, кулдаун)
for (MessageFilter filter : filters) {
if (!filter.apply(player, message)) {
return false; // Сообщение заблокировано
}
}
// Возвращаем true - сообщение разрешено
// Рендерер сам отправит его
return true;

View file

@ -24,7 +24,7 @@ public class MuteServiceImpl implements MuteService {
private final File dataFile;
private final File historyFile;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public MuteServiceImpl(JavaPlugin plugin) {
this.plugin = plugin;
this.dataFile = new File(plugin.getDataFolder(), "mutes.json");
@ -32,19 +32,19 @@ public class MuteServiceImpl implements MuteService {
load();
loadHistory();
}
@Override
public void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy) {
long endTime = duration > 0 ? System.currentTimeMillis() + duration : 0;
MuteData data = new MuteData(uuid, playerName, endTime, reason, mutedBy);
mutes.put(uuid, data);
// Добавляем в историю
addToHistory(uuid, playerName, duration, reason, mutedBy);
saveAsync();
}
@Override
public boolean unmute(UUID uuid, String unmutedBy) {
MuteData removed = mutes.remove(uuid);
@ -55,12 +55,12 @@ public class MuteServiceImpl implements MuteService {
}
return false;
}
@Override
public boolean isMuted(UUID uuid) {
MuteData data = mutes.get(uuid);
if (data == null) return false;
if (data.getEndTime() > 0 && System.currentTimeMillis() > data.getEndTime()) {
mutes.remove(uuid);
saveAsync();
@ -68,13 +68,13 @@ public class MuteServiceImpl implements MuteService {
}
return true;
}
@Override
public MuteData getMuteData(UUID uuid) {
if (!isMuted(uuid)) return null;
return mutes.get(uuid);
}
@Override
public long getRemainingTime(UUID uuid) {
MuteData data = mutes.get(uuid);
@ -82,16 +82,16 @@ public class MuteServiceImpl implements MuteService {
if (data.getEndTime() == 0) return -1; // Перманентный
return Math.max(0, data.getEndTime() - System.currentTimeMillis());
}
@Override
public String formatTime(long millis) {
if (millis <= 0) return "0с";
long seconds = millis / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if (days > 0) {
return days + "д " + (hours % 24) + "ч";
} else if (hours > 0) {
@ -102,14 +102,14 @@ public class MuteServiceImpl implements MuteService {
return seconds + "с";
}
}
@Override
public long parseTime(String timeStr) {
if (timeStr == null || timeStr.isEmpty()) return 0;
timeStr = timeStr.toLowerCase();
long multiplier = 1000; // базово в миллисекундах
try {
if (timeStr.endsWith("d")) {
return Long.parseLong(timeStr.replace("d", "")) * 24 * 60 * 60 * multiplier;
@ -126,45 +126,45 @@ public class MuteServiceImpl implements MuteService {
return -1;
}
}
@Override
public long getMaxDuration(Player player) {
if (player.hasPermission("lochat.mute.dur.perm")) {
return 0; // Перманентный
}
String[] durations = {"30d", "14d", "7d", "3d", "1d", "12h", "6h", "1h", "30m", "10m"};
for (String dur : durations) {
if (player.hasPermission("lochat.mute.dur." + dur)) {
return parseTime(dur);
}
}
return -1; // Нет прав
}
@Override
public boolean canMuteForDuration(Player player, long duration) {
if (player.hasPermission("lochat.mute.dur.perm")) {
return true;
}
if (duration == 0) {
return false;
}
long maxDuration = getMaxDuration(player);
if (maxDuration == -1) return false;
if (maxDuration == 0) return true;
return duration <= maxDuration;
}
@Override
public List<MuteData.MuteHistoryEntry> getPlayerHistory(UUID uuid) {
return history.getOrDefault(uuid, new ArrayList<>());
}
@Override
public List<MuteData.MuteHistoryEntry> getMutesByOperator(String operatorName) {
List<MuteData.MuteHistoryEntry> result = new ArrayList<>();
@ -178,7 +178,7 @@ public class MuteServiceImpl implements MuteService {
result.sort((a, b) -> Long.compare(b.mutedAt, a.mutedAt));
return result;
}
@Override
public UUID getUUIDByName(String name) {
for (Map.Entry<UUID, List<MuteData.MuteHistoryEntry>> entry : history.entrySet()) {
@ -190,7 +190,7 @@ public class MuteServiceImpl implements MuteService {
}
return null;
}
@Override
public Map<UUID, MuteData> getActiveMutes() {
mutes.entrySet().removeIf(entry -> {
@ -199,7 +199,7 @@ public class MuteServiceImpl implements MuteService {
});
return new HashMap<>(mutes);
}
@Override
public String formatMessage(String message, String player, String operator, String duration, String reason) {
return message
@ -208,17 +208,17 @@ public class MuteServiceImpl implements MuteService {
.replace("%duration%", duration != null ? duration : "")
.replace("%reason%", reason != null ? reason : "");
}
@Override
public void save() {
try {
if (!dataFile.getParentFile().exists()) {
dataFile.getParentFile().mkdirs();
}
Map<String, MuteData> toSave = new ConcurrentHashMap<>();
mutes.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
try (Writer writer = new FileWriter(dataFile)) {
gson.toJson(toSave, writer);
}
@ -226,52 +226,56 @@ public class MuteServiceImpl implements MuteService {
plugin.getLogger().warning("Ошибка сохранения мутов: " + e.getMessage());
}
}
private void load() {
if (!dataFile.exists()) return;
try (Reader reader = new FileReader(dataFile)) {
Type type = new TypeToken<Map<String, MuteData>>(){}.getType();
Type type = new TypeToken<Map<String, MuteData>>() {
}.getType();
Map<String, MuteData> loaded = gson.fromJson(reader, type);
if (loaded != null) {
loaded.forEach((key, value) -> {
try {
mutes.put(UUID.fromString(key), value);
} catch (IllegalArgumentException ignored) {}
} catch (IllegalArgumentException ignored) {
}
});
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка загрузки мутов: " + e.getMessage());
}
}
private void loadHistory() {
if (!historyFile.exists()) return;
try (Reader reader = new FileReader(historyFile)) {
Type type = new TypeToken<Map<String, List<MuteData.MuteHistoryEntry>>>(){}.getType();
Type type = new TypeToken<Map<String, List<MuteData.MuteHistoryEntry>>>() {
}.getType();
Map<String, List<MuteData.MuteHistoryEntry>> loaded = gson.fromJson(reader, type);
if (loaded != null) {
loaded.forEach((key, value) -> {
try {
history.put(UUID.fromString(key), new ArrayList<>(value));
} catch (IllegalArgumentException ignored) {}
} catch (IllegalArgumentException ignored) {
}
});
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка загрузки истории мутов: " + e.getMessage());
}
}
private void saveHistory() {
try {
if (!historyFile.getParentFile().exists()) {
historyFile.getParentFile().mkdirs();
}
Map<String, List<MuteData.MuteHistoryEntry>> toSave = new ConcurrentHashMap<>();
history.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
try (Writer writer = new FileWriter(historyFile)) {
gson.toJson(toSave, writer);
}
@ -279,7 +283,7 @@ public class MuteServiceImpl implements MuteService {
plugin.getLogger().warning("Ошибка сохранения истории мутов: " + e.getMessage());
}
}
private void addToHistory(UUID uuid, String playerName, long duration, String reason, String mutedBy) {
List<MuteData.MuteHistoryEntry> playerHistory = history.computeIfAbsent(uuid, k -> new ArrayList<>());
playerHistory.add(new MuteData.MuteHistoryEntry(
@ -294,7 +298,7 @@ public class MuteServiceImpl implements MuteService {
));
saveHistoryAsync();
}
private void updateHistoryUnmute(UUID uuid, String unmutedBy) {
List<MuteData.MuteHistoryEntry> playerHistory = history.get(uuid);
if (playerHistory != null && !playerHistory.isEmpty()) {
@ -310,11 +314,11 @@ public class MuteServiceImpl implements MuteService {
}
saveHistoryAsync();
}
private void saveHistoryAsync() {
FoliaUtil.runAsync(plugin, this::saveHistory);
}
private void saveAsync() {
FoliaUtil.runAsync(plugin, () -> {
save();

View file

@ -12,28 +12,29 @@ import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Реализация сервиса кастомных ников
*/
public class NickServiceImpl implements NickService {
// Настройки
private static final int MIN_LENGTH = 3;
private static final int MAX_LENGTH = 16;
private final JavaPlugin plugin;
private final Map<UUID, String> nicknames = new ConcurrentHashMap<>();
private final File dataFile;
private FileConfiguration data;
// Настройки
private static final int MIN_LENGTH = 3;
private static final int MAX_LENGTH = 16;
public NickServiceImpl(JavaPlugin plugin) {
this.plugin = plugin;
this.dataFile = new File(plugin.getDataFolder(), "nicknames.yml");
load();
}
private void load() {
if (!dataFile.exists()) {
try {
@ -43,9 +44,9 @@ public class NickServiceImpl implements NickService {
plugin.getLogger().warning("Не удалось создать nicknames.yml: " + e.getMessage());
}
}
data = YamlConfiguration.loadConfiguration(dataFile);
// Загружаем ники
if (data.contains("nicknames")) {
for (String key : data.getConfigurationSection("nicknames").getKeys(false)) {
@ -54,10 +55,10 @@ public class NickServiceImpl implements NickService {
nicknames.put(uuid, nickname);
}
}
plugin.getLogger().info("Загружено " + nicknames.size() + " кастомных ников");
}
@Override
public void save() {
// Сохраняем ники
@ -65,21 +66,21 @@ public class NickServiceImpl implements NickService {
for (Map.Entry<UUID, String> entry : nicknames.entrySet()) {
data.set("nicknames." + entry.getKey().toString(), entry.getValue());
}
try {
data.save(dataFile);
} catch (IOException e) {
plugin.getLogger().warning("Не удалось сохранить nicknames.yml: " + e.getMessage());
}
}
@Override
public boolean setNickname(UUID player, String nickname) {
// Валидация
if (!isValidNickname(nickname)) {
return false;
}
// Проверка уникальности
if (isNicknameTaken(nickname)) {
// Проверяем не свой ли это ник
@ -88,23 +89,23 @@ public class NickServiceImpl implements NickService {
return false;
}
}
// Устанавливаем ник
nicknames.put(player, nickname);
// Обновляем display
Player onlinePlayer = Bukkit.getPlayer(player);
if (onlinePlayer != null) {
updatePlayerDisplay(onlinePlayer);
}
return true;
}
@Override
public void resetNickname(UUID player) {
nicknames.remove(player);
// Сбрасываем display
Player onlinePlayer = Bukkit.getPlayer(player);
if (onlinePlayer != null) {
@ -112,59 +113,55 @@ public class NickServiceImpl implements NickService {
onlinePlayer.playerListName(Component.text(onlinePlayer.getName()));
}
}
@Override
public Optional<String> getNickname(UUID player) {
return Optional.ofNullable(nicknames.get(player));
}
@Override
public boolean isNicknameTaken(String nickname) {
String cleanNick = stripColors(nickname).toLowerCase();
// Проверяем существующие ники
for (String existingNick : nicknames.values()) {
if (stripColors(existingNick).equalsIgnoreCase(cleanNick)) {
return true;
}
}
// Проверяем реальные ники онлайн игроков
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.getName().equalsIgnoreCase(cleanNick)) {
return true;
}
}
return false;
}
@Override
public boolean isValidNickname(String nickname) {
if (nickname == null || nickname.isEmpty()) {
return false;
}
// Убираем цвета для проверки длины
String cleanNick = stripColors(nickname);
// Проверка длины
if (cleanNick.length() < MIN_LENGTH || cleanNick.length() > MAX_LENGTH) {
return false;
}
// Проверка символов (разрешены буквы, цифры, _, русские буквы)
if (!cleanNick.matches("[a-zA-Zа-яА-Я0-9_]+")) {
return false;
}
return true;
return cleanNick.matches("[a-zA-Zа-яА-Я0-9_]+");
}
@Override
public void updatePlayerDisplay(Player player) {
Optional<String> nickname = getNickname(player.getUniqueId());
if (nickname.isPresent()) {
// Парсим ник с цветами
Component nickComponent = ChatFormatter.parse(nickname.get());
@ -176,7 +173,7 @@ public class NickServiceImpl implements NickService {
player.playerListName(Component.text(player.getName()));
}
}
/**
* Убирает цвета из строки
*/

View file

@ -11,16 +11,16 @@ import java.util.UUID;
*/
public class PlayerDataServiceImpl implements PlayerDataService {
private final CooldownService cooldownService;
public PlayerDataServiceImpl(JavaPlugin plugin) {
this.cooldownService = new CooldownServiceImpl();
}
@Override
public void clearPlayerData(UUID player) {
cooldownService.removeCooldown(player);
}
@Override
public void saveAll() {
// Сохранение всех данных при выключении

View file

@ -60,10 +60,10 @@ public class SpyServiceImpl implements SpyService {
@Override
public void sendToSpies(Player sender, Component message, boolean isGlobal) {
if (spyEnabled.isEmpty()) return;
String chatType = isGlobal ? "Global" : "Local";
String plainMessage = PlainTextComponentSerializer.plainText().serialize(message);
String format = messageConfig.get("spy.chat-format",
String format = messageConfig.get("spy.chat-format",
"§7[SPY] §e{type} §7{sender}: §f{message}");
for (UUID spyUuid : spyEnabled) {

View file

@ -7,33 +7,50 @@ import org.bukkit.entity.Player;
*/
public class ChatMessage {
private final Player sender;
private String content;
private final boolean isGlobal;
private final long timestamp;
private String content;
private ChatMessage(Player sender, String content, boolean isGlobal) {
this.sender = sender;
this.content = content;
this.isGlobal = isGlobal;
this.timestamp = System.currentTimeMillis();
}
public static ChatMessage create(Player sender, String rawMessage) {
// Определяем тип чата
boolean isGlobal = rawMessage.startsWith("!") &&
sender.hasPermission("chat.global.use");
String content = isGlobal ?
rawMessage.substring(1).trim() :
rawMessage.trim();
boolean isGlobal = rawMessage.startsWith("!") &&
sender.hasPermission("chat.global.use");
String content = isGlobal ?
rawMessage.substring(1).trim() :
rawMessage.trim();
return new ChatMessage(sender, content, isGlobal);
}
public Player getSender() { return sender; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public boolean isGlobal() { return isGlobal; }
public long getTimestamp() { return timestamp; }
public String getChatType() { return isGlobal ? "global" : "local"; }
public Player getSender() {
return sender;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isGlobal() {
return isGlobal;
}
public long getTimestamp() {
return timestamp;
}
public String getChatType() {
return isGlobal ? "global" : "local";
}
}

View file

@ -6,15 +6,16 @@ import java.util.UUID;
* Модель данных мута (Value Object)
*/
public class MuteData {
private UUID uuid;
private String playerName;
public long endTime; // 0 = перманентный (public for JSON serialization)
public String reason;
public String mutedBy;
public long mutedAt;
public MuteData() {} // For JSON deserialization
private UUID uuid;
private String playerName;
public MuteData() {
} // For JSON deserialization
public MuteData(UUID uuid, String playerName, long endTime, String reason, String mutedBy) {
this.uuid = uuid;
this.playerName = playerName;
@ -23,7 +24,7 @@ public class MuteData {
this.mutedBy = mutedBy;
this.mutedAt = System.currentTimeMillis();
}
public MuteData(long endTime, String reason, String mutedBy, long mutedAt, String playerName) {
this.endTime = endTime;
this.reason = reason;
@ -31,15 +32,35 @@ public class MuteData {
this.mutedAt = mutedAt;
this.playerName = playerName;
}
public UUID getUuid() { return uuid; }
public String getPlayerName() { return playerName; }
public long getEndTime() { return endTime; }
public String getReason() { return reason; }
public String getMutedBy() { return mutedBy; }
public long getMutedAt() { return mutedAt; }
public boolean isPermanent() { return endTime == 0; }
public UUID getUuid() {
return uuid;
}
public String getPlayerName() {
return playerName;
}
public long getEndTime() {
return endTime;
}
public String getReason() {
return reason;
}
public String getMutedBy() {
return mutedBy;
}
public long getMutedAt() {
return mutedAt;
}
public boolean isPermanent() {
return endTime == 0;
}
/**
* Запись истории мута
*/
@ -52,12 +73,13 @@ public class MuteData {
public boolean unmuted;
public String unmutedBy;
public long unmutedAt;
public MuteHistoryEntry() {} // For JSON deserialization
public MuteHistoryEntry(String playerName, long duration, String reason,
String mutedBy, long mutedAt, boolean unmuted,
String unmutedBy, long unmutedAt) {
public MuteHistoryEntry() {
} // For JSON deserialization
public MuteHistoryEntry(String playerName, long duration, String reason,
String mutedBy, long mutedAt, boolean unmuted,
String unmutedBy, long unmutedAt) {
this.playerName = playerName;
this.duration = duration;
this.reason = reason;

View file

@ -37,18 +37,19 @@ public class GradientModule {
/**
* Инициализация модуля
*
* @return true если модуль успешно инициализирован
*/
public boolean init() {
// Загружаем конфиги
config = new GradientConfig(plugin);
messages = new GradientMessages(plugin);
if (!config.isEnabled()) {
plugin.getLogger().info("Gradient модуль отключен в конфиге.");
return false;
}
// Проверяем PlayerPoints (опционально теперь)
if (Bukkit.getPluginManager().isPluginEnabled("PlayerPoints")) {
playerPointsAPI = PlayerPoints.getInstance().getAPI();
@ -56,10 +57,10 @@ public class GradientModule {
} else {
plugin.getLogger().warning("Gradient: PlayerPoints не найден. Покупки будут бесплатными.");
}
// Инициализация хранилища
dataManager = new GradientDataManager(plugin, config);
// Подключение LuckPerms (опционально)
try {
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
@ -71,15 +72,15 @@ public class GradientModule {
} catch (NoClassDefFoundError e) {
plugin.getLogger().info("Gradient: LuckPerms не найден. Префиксы LuckPerms будут недоступны.");
}
// Регистрация команд
registerCommands();
// Регистрация слушателей
Bukkit.getPluginManager().registerEvents(new GradientPlayerListener(this), plugin);
Bukkit.getPluginManager().registerEvents(new GradientGUIListener(this), plugin);
enabled = true;
plugin.getLogger().info("Gradient модуль успешно загружен!");
return true;
@ -91,13 +92,13 @@ public class GradientModule {
plugin.getCommand("color").setExecutor(colorCommand);
plugin.getCommand("color").setTabCompleter(colorCommand);
}
GradientPrefixCommand prefixCommand = new GradientPrefixCommand(this);
if (plugin.getCommand("prefix") != null) {
plugin.getCommand("prefix").setExecutor(prefixCommand);
plugin.getCommand("prefix").setTabCompleter(prefixCommand);
}
GradientAdminCommand adminCommand = new GradientAdminCommand(this);
if (plugin.getCommand("aprefix") != null) {
plugin.getCommand("aprefix").setExecutor(adminCommand);
@ -141,22 +142,23 @@ public class GradientModule {
/**
* Получает отформатированное имя игрока с градиентом и LuckPerms префиксом
*
* @param useTabFormat true для §x§... формата (TAB), false для &#RRGGBB
*/
private String getFormattedName(Player player, boolean useTabFormat) {
if (!enabled) return player.getName();
GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId());
// Собираем полную строку: префикс + ник
StringBuilder fullText = new StringBuilder();
// LuckPerms префикс
String lpPrefix = null;
if (luckPermsHook.isEnabled()) {
lpPrefix = luckPermsHook.getActivePrefix(player);
}
// Определяем какой префикс использовать
if (data.hasPrefix() && data.isPrefixEnabled()) {
// Кастомный префикс
@ -167,21 +169,21 @@ public class GradientModule {
fullText.append(stripColors(lpPrefix));
} else {
// LP префикс со своими цветами, ник отдельно
String nick = data.hasColors() && data.isColorEnabled()
? applyGradientWithFormat(player.getName(), data.getColors(), useTabFormat)
: player.getName();
String nick = data.hasColors() && data.isColorEnabled()
? applyGradientWithFormat(player.getName(), data.getColors(), useTabFormat)
: player.getName();
return lpPrefix + nick;
}
}
// Добавляем ник
fullText.append(player.getName());
// Применяем единый градиент на всю строку
if (data.hasColors() && data.isColorEnabled()) {
return applyGradientWithFormat(fullText.toString(), data.getColors(), useTabFormat);
}
return fullText.toString();
}
@ -211,12 +213,12 @@ public class GradientModule {
*/
public String getGradientNick(Player player) {
if (!enabled) return player.getName();
GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId());
if (!data.hasColors() || !data.isColorEnabled()) {
return player.getName();
}
return GradientUtil.applyGradient(player.getName(), data.getColors(), config.isUseLegacyRgbFormat());
}
@ -226,9 +228,9 @@ public class GradientModule {
*/
public String getPrefix(Player player) {
if (!enabled) return "";
GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId());
// Кастомный префикс
if (data.hasPrefix() && data.isPrefixEnabled()) {
String prefix = config.getPrefixFormat().replace("{prefix}", data.getPrefix());
@ -237,7 +239,7 @@ public class GradientModule {
}
return prefix;
}
// LuckPerms префикс
if (luckPermsHook.isEnabled()) {
String lpPrefix = luckPermsHook.getActivePrefix(player);
@ -249,7 +251,7 @@ public class GradientModule {
return lpPrefix;
}
}
return "";
}
@ -263,12 +265,35 @@ public class GradientModule {
}
// Геттеры
public JavaPlugin getPlugin() { return plugin; }
public GradientConfig getConfig() { return config; }
public GradientMessages getMessages() { return messages; }
public GradientDataManager getDataManager() { return dataManager; }
public GradientLuckPermsHook getLuckPermsHook() { return luckPermsHook; }
public PlayerPointsAPI getPlayerPointsAPI() { return playerPointsAPI; }
public boolean hasPlayerPoints() { return playerPointsAPI != null; }
public boolean isEnabled() { return enabled; }
public JavaPlugin getPlugin() {
return plugin;
}
public GradientConfig getConfig() {
return config;
}
public GradientMessages getMessages() {
return messages;
}
public GradientDataManager getDataManager() {
return dataManager;
}
public GradientLuckPermsHook getLuckPermsHook() {
return luckPermsHook;
}
public PlayerPointsAPI getPlayerPointsAPI() {
return playerPointsAPI;
}
public boolean hasPlayerPoints() {
return playerPointsAPI != null;
}
public boolean isEnabled() {
return enabled;
}
}

View file

@ -26,10 +26,10 @@ import java.util.List;
*/
public class GradientAdminCommand implements CommandExecutor, TabCompleter {
private final GradientModule module;
private static final List<String> PRESET_COLORS = Arrays.asList(
"#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3"
);
private final GradientModule module;
public GradientAdminCommand(GradientModule module) {
this.module = module;
@ -153,11 +153,11 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aустановлен: §f" + String.join(" ", colors));
@ -194,11 +194,11 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aустановлен: §f" + prefix);
@ -209,7 +209,7 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§cИспользование: /aprefix " + (enable ? "coloron" : "coloroff") + " <игрок>");
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
msg.send(sender, "player-not-found");
@ -221,11 +221,11 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aЦвет для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен"));
@ -236,7 +236,7 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§cИспользование: /aprefix " + (enable ? "prefixon" : "prefixoff") + " <игрок>");
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
msg.send(sender, "player-not-found");
@ -256,11 +256,11 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен"));
@ -271,7 +271,7 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§cИспользование: /aprefix resetcolor <игрок>");
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
msg.send(sender, "player-not-found");
@ -284,11 +284,11 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aсброшен");
@ -299,7 +299,7 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§cИспользование: /aprefix resetprefix <игрок>");
return;
}
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
if (!target.hasPlayedBefore() && !target.isOnline()) {
msg.send(sender, "player-not-found");
@ -316,11 +316,11 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
Player onlineTarget = target.getPlayer();
if (onlineTarget != null) {
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
}
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aсброшен");
@ -330,7 +330,7 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
int count = 0;
for (Player player : Bukkit.getOnlinePlayers()) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
FoliaUtil.runEntityTask(module.getPlugin(), player,
FoliaUtil.runEntityTask(module.getPlugin(), player,
() -> DisplayNameUtil.updateDisplayName(module, player, data));
count++;
}
@ -340,19 +340,19 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
@NotNull String alias, @NotNull String[] args) {
if (!sender.hasPermission("gradient.admin")) return new ArrayList<>();
if (args.length == 1) {
return Arrays.asList("reload", "info", "setcolor", "setprefix", "coloron", "coloroff",
"prefixon", "prefixoff", "resetcolor", "resetprefix", "updateall").stream()
return Arrays.asList("reload", "info", "setcolor", "setprefix", "coloron", "coloroff",
"prefixon", "prefixoff", "resetcolor", "resetprefix", "updateall").stream()
.filter(s -> s.startsWith(args[0].toLowerCase()))
.toList();
}
if (args.length == 2) {
String sub = args[0].toLowerCase();
if (Arrays.asList("info", "setcolor", "setprefix", "coloron", "coloroff",
if (Arrays.asList("info", "setcolor", "setprefix", "coloron", "coloroff",
"prefixon", "prefixoff", "resetcolor", "resetprefix").contains(sub)) {
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)

View file

@ -26,14 +26,13 @@ import java.util.function.BiFunction;
*/
public class GradientColorCommand implements CommandExecutor, TabCompleter {
private final GradientModule module;
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
private static final List<String> PRESET_COLORS = List.of(
"#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3",
"#FF69B4", "#00FFFF", "#FFD700", "#FFFFFF", "#000000"
);
private static final Set<String> SUB_COMMANDS = Set.of("on", "off", "reset", "copy");
private final GradientModule module;
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
public GradientColorCommand(GradientModule module) {
this.module = module;
@ -77,12 +76,12 @@ public class GradientColorCommand implements CommandExecutor, TabCompleter {
private boolean handleOn(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientMessages msg = module.getMessages();
if (!data.hasColors()) {
msg.send(player, "color-no-colors");
return true;
}
data.setColorEnabled(true);
saveAndUpdate(player, data);
msg.send(player, "color-enabled");
@ -100,12 +99,12 @@ public class GradientColorCommand implements CommandExecutor, TabCompleter {
private boolean handleReset(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientMessages msg = module.getMessages();
if (!data.hasColors()) {
msg.send(player, "color-no-colors");
return true;
}
data.setColors(new ArrayList<>());
data.setColorEnabled(false);
saveAndUpdate(player, data);
@ -116,18 +115,18 @@ public class GradientColorCommand implements CommandExecutor, TabCompleter {
private boolean handleCopy(Player player, String[] args) {
GradientMessages msg = module.getMessages();
GradientConfig cfg = module.getConfig();
if (args.length < 2) {
msg.send(player, "color-copy-usage");
return true;
}
Player target = Bukkit.getPlayer(args[1]);
if (target == null) {
msg.send(player, "player-not-found");
return true;
}
GradientPlayerData targetData = module.getDataManager().getPlayerData(target.getUniqueId());
if (!targetData.hasColors()) {
msg.send(player, "color-copy-no-colors", "player", target.getName());
@ -135,8 +134,8 @@ public class GradientColorCommand implements CommandExecutor, TabCompleter {
}
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
if (!player.hasPermission("gradient.bypass.cooldown") &&
if (!player.hasPermission("gradient.bypass.cooldown") &&
!checkCooldown(player, cfg.getColorCooldown(), data.getLastColorChange())) {
return true;
}
@ -148,7 +147,7 @@ public class GradientColorCommand implements CommandExecutor, TabCompleter {
return true;
}
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
GradientConfirmGUI.ConfirmType.COLOR, colors, data.getPrefix(), price);
FoliaUtil.runEntityTask(module.getPlugin(), player, gui::open);
return true;
@ -170,7 +169,7 @@ public class GradientColorCommand implements CommandExecutor, TabCompleter {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
if (!player.hasPermission("gradient.bypass.cooldown") &&
if (!player.hasPermission("gradient.bypass.cooldown") &&
!checkCooldown(player, cfg.getColorCooldown(), data.getLastColorChange())) {
return true;
}
@ -191,7 +190,7 @@ public class GradientColorCommand implements CommandExecutor, TabCompleter {
return true;
}
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
GradientConfirmGUI.ConfirmType.COLOR, colors, data.getPrefix(), price);
FoliaUtil.runEntityTask(module.getPlugin(), player, gui::open);
return true;
@ -222,35 +221,35 @@ public class GradientColorCommand implements CommandExecutor, TabCompleter {
}
private void saveAndUpdate(Player player, GradientPlayerData data) {
FoliaUtil.runEntityTask(module.getPlugin(), player,
FoliaUtil.runEntityTask(module.getPlugin(), player,
() -> DisplayNameUtil.updateDisplayName(module, player, data));
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
@NotNull String alias, @NotNull String[] args) {
if (args.length == 1) {
String input = args[0].toLowerCase();
List<String> completions = new ArrayList<>(SUB_COMMANDS);
completions.addAll(PRESET_COLORS);
return completions.stream().filter(s -> s.toLowerCase().startsWith(input)).toList();
}
String firstArg = args[0].toLowerCase();
if (args.length == 2 && firstArg.equals("copy")) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName)
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase())).toList();
}
if (!SUB_COMMANDS.contains(firstArg) && args.length <= module.getConfig().getMaxColors()) {
return PRESET_COLORS.stream()
.filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
.toList();
}
return List.of();
}
}

View file

@ -28,13 +28,12 @@ import java.util.regex.Pattern;
*/
public class GradientPrefixCommand implements CommandExecutor, TabCompleter {
private final GradientModule module;
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
private static final Pattern COLOR_CODE_PATTERN = Pattern.compile(
"(?i)(&[0-9a-fk-or]|§[0-9a-fk-or]|#[0-9a-f]{6}|&#[0-9a-f]{6}|<#[0-9a-f]{6}>)");
private static final Pattern ALLOWED_PREFIX_PATTERN = Pattern.compile("^[a-zA-Zа-яА-ЯёЁ0-9 _\\-]+$");
private static final Set<String> SUB_COMMANDS = Set.of("on", "off", "reset");
private final GradientModule module;
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
public GradientPrefixCommand(GradientModule module) {
this.module = module;
@ -77,12 +76,12 @@ public class GradientPrefixCommand implements CommandExecutor, TabCompleter {
private boolean handleOn(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientMessages msg = module.getMessages();
if (!data.hasPrefix()) {
msg.send(player, "prefix-no-prefix");
return true;
}
data.setPrefixEnabled(true);
if (module.getLuckPermsHook() != null) {
module.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(module, data));
@ -106,12 +105,12 @@ public class GradientPrefixCommand implements CommandExecutor, TabCompleter {
private boolean handleReset(Player player, String[] args) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientMessages msg = module.getMessages();
if (!data.hasPrefix()) {
msg.send(player, "prefix-no-prefix");
return true;
}
data.setPrefix(null);
data.setPrefixEnabled(false);
if (module.getLuckPermsHook() != null) {
@ -154,7 +153,7 @@ public class GradientPrefixCommand implements CommandExecutor, TabCompleter {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
if (!player.hasPermission("gradient.bypass.cooldown") &&
if (!player.hasPermission("gradient.bypass.cooldown") &&
!checkCooldown(player, cfg.getPrefixCooldown(), data.getLastPrefixChange())) {
return true;
}
@ -165,9 +164,9 @@ public class GradientPrefixCommand implements CommandExecutor, TabCompleter {
return true;
}
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
GradientConfirmGUI.ConfirmType.PREFIX,
data.hasColors() && data.isColorEnabled() ? data.getColors() : null,
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
GradientConfirmGUI.ConfirmType.PREFIX,
data.hasColors() && data.isColorEnabled() ? data.getColors() : null,
prefix, price);
FoliaUtil.runEntityTask(module.getPlugin(), player, gui::open);
return true;
@ -203,15 +202,15 @@ public class GradientPrefixCommand implements CommandExecutor, TabCompleter {
}
private void saveAndUpdate(Player player, GradientPlayerData data) {
FoliaUtil.runEntityTask(module.getPlugin(), player,
FoliaUtil.runEntityTask(module.getPlugin(), player,
() -> DisplayNameUtil.updateDisplayName(module, player, data));
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
@NotNull String alias, @NotNull String[] args) {
if (args.length == 1) {
return SUB_COMMANDS.stream()
.filter(s -> s.startsWith(args[0].toLowerCase()))

View file

@ -33,7 +33,7 @@ public class GradientConfig {
plugin.saveResource("gradient-config.yml", false);
}
config = YamlConfiguration.loadConfiguration(configFile);
// Загружаем дефолты
InputStream defStream = plugin.getResource("gradient-config.yml");
if (defStream != null) {
@ -56,23 +56,70 @@ public class GradientConfig {
}
}
public boolean isEnabled() { return config.getBoolean("enabled", true); }
public int getMaxColors() { return config.getInt("max-colors", 7); }
public int getMinColors() { return config.getInt("min-colors", 1); }
public int getPricePerColor() { return config.getInt("price-per-color", 50); }
public int getPrefixPrice() { return config.getInt("prefix-price", 500); }
public boolean isPrefixOneTimePurchase() { return config.getBoolean("prefix-one-time-purchase", true); }
public int getColorCooldown() { return config.getInt("color-cooldown", 60); }
public int getPrefixCooldown() { return config.getInt("prefix-cooldown", 300); }
public int getMaxPrefixLength() { return config.getInt("max-prefix-length", 7); }
public String getPrefixFormat() { return config.getString("prefix-format", "[{prefix}] "); }
public boolean isGradientOnPrefix() { return config.getBoolean("gradient-on-prefix", true); }
public boolean isGradientOnLuckPermsPrefix() { return config.getBoolean("gradient-on-luckperms-prefix", true); }
public boolean isContinuousGradient() { return config.getBoolean("continuous-gradient", true); }
public boolean isUseLegacyRgbFormat() { return config.getBoolean("use-legacy-rgb-format", true); }
public String getStorageType() { return config.getString("storage-type", "YAML"); }
public String getSqliteFile() { return config.getString("sqlite-file", "gradient-data.db"); }
public boolean isEnabled() {
return config.getBoolean("enabled", true);
}
public int getMaxColors() {
return config.getInt("max-colors", 7);
}
public int getMinColors() {
return config.getInt("min-colors", 1);
}
public int getPricePerColor() {
return config.getInt("price-per-color", 50);
}
public int getPrefixPrice() {
return config.getInt("prefix-price", 500);
}
public boolean isPrefixOneTimePurchase() {
return config.getBoolean("prefix-one-time-purchase", true);
}
public int getColorCooldown() {
return config.getInt("color-cooldown", 60);
}
public int getPrefixCooldown() {
return config.getInt("prefix-cooldown", 300);
}
public int getMaxPrefixLength() {
return config.getInt("max-prefix-length", 7);
}
public String getPrefixFormat() {
return config.getString("prefix-format", "[{prefix}] ");
}
public boolean isGradientOnPrefix() {
return config.getBoolean("gradient-on-prefix", true);
}
public boolean isGradientOnLuckPermsPrefix() {
return config.getBoolean("gradient-on-luckperms-prefix", true);
}
public boolean isContinuousGradient() {
return config.getBoolean("continuous-gradient", true);
}
public boolean isUseLegacyRgbFormat() {
return config.getBoolean("use-legacy-rgb-format", true);
}
public String getStorageType() {
return config.getString("storage-type", "YAML");
}
public String getSqliteFile() {
return config.getString("sqlite-file", "gradient-data.db");
}
public List<String> getPrefixBlacklist() {
return config.getStringList("prefix-blacklist");
}
@ -83,17 +130,17 @@ public class GradientConfig {
.anyMatch(blacklisted -> lowerPrefix.contains(blacklisted.toLowerCase()));
}
public boolean isUpdateDisplayName() {
public boolean isUpdateDisplayName() {
if (mainConfig.contains("display.update-display-name")) {
return mainConfig.getBoolean("display.update-display-name", true);
}
return config.getBoolean("display.update-display-name", true);
return config.getBoolean("display.update-display-name", true);
}
public boolean isUpdateTabList() {
public boolean isUpdateTabList() {
if (mainConfig.contains("display.update-tab-list")) {
return mainConfig.getBoolean("display.update-tab-list", true);
}
return config.getBoolean("display.update-tab-list", true);
return config.getBoolean("display.update-tab-list", true);
}
}

View file

@ -32,7 +32,7 @@ public class GradientMessages {
plugin.saveResource("gradient-messages.yml", false);
}
messages = YamlConfiguration.loadConfiguration(messagesFile);
InputStream defStream = plugin.getResource("gradient-messages.yml");
if (defStream != null) {
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(

View file

@ -28,7 +28,7 @@ public class GradientDataManager {
this.plugin = plugin;
this.config = config;
this.useSqlite = config.getStorageType().equalsIgnoreCase("SQLITE");
if (useSqlite) {
initSqlite();
} else {
@ -40,20 +40,20 @@ public class GradientDataManager {
try {
File dbFile = new File(plugin.getDataFolder(), config.getSqliteFile());
sqliteConnection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
try (Statement stmt = sqliteConnection.createStatement()) {
stmt.executeUpdate("""
CREATE TABLE IF NOT EXISTS gradient_data (
uuid TEXT PRIMARY KEY,
prefix TEXT,
colors TEXT,
color_enabled INTEGER DEFAULT 1,
prefix_enabled INTEGER DEFAULT 1,
prefix_purchased INTEGER DEFAULT 0,
last_color_change INTEGER DEFAULT 0,
last_prefix_change INTEGER DEFAULT 0
)
""");
CREATE TABLE IF NOT EXISTS gradient_data (
uuid TEXT PRIMARY KEY,
prefix TEXT,
colors TEXT,
color_enabled INTEGER DEFAULT 1,
prefix_enabled INTEGER DEFAULT 1,
prefix_purchased INTEGER DEFAULT 0,
last_color_change INTEGER DEFAULT 0,
last_prefix_change INTEGER DEFAULT 0
)
""");
}
plugin.getLogger().info("Gradient SQLite база данных инициализирована.");
} catch (SQLException e) {
@ -65,7 +65,7 @@ public class GradientDataManager {
private void loadYamlData() {
File dataFile = new File(plugin.getDataFolder(), "gradient-data.yml");
if (!dataFile.exists()) return;
FileConfiguration data = YamlConfiguration.loadConfiguration(dataFile);
for (String uuidStr : data.getKeys(false)) {
try {
@ -79,7 +79,8 @@ public class GradientDataManager {
playerData.setLastColorChange(data.getLong(uuidStr + ".last-color-change", 0));
playerData.setLastPrefixChange(data.getLong(uuidStr + ".last-prefix-change", 0));
cache.put(uuid, playerData);
} catch (IllegalArgumentException ignored) {}
} catch (IllegalArgumentException ignored) {
}
}
}
@ -89,7 +90,7 @@ public class GradientDataManager {
private GradientPlayerData loadPlayerData(UUID uuid) {
GradientPlayerData data = new GradientPlayerData(uuid);
if (useSqlite && sqliteConnection != null) {
try (PreparedStatement stmt = sqliteConnection.prepareStatement(
"SELECT prefix, colors, color_enabled, prefix_enabled, prefix_purchased, last_color_change, last_prefix_change FROM gradient_data WHERE uuid = ?")) {
@ -111,14 +112,14 @@ public class GradientDataManager {
plugin.getLogger().warning("Ошибка загрузки gradient данных: " + e.getMessage());
}
}
return data;
}
public void savePlayerData(UUID uuid) {
GradientPlayerData data = cache.get(uuid);
if (data == null) return;
if (useSqlite && sqliteConnection != null) {
try (PreparedStatement stmt = sqliteConnection.prepareStatement(
"INSERT OR REPLACE INTO gradient_data (uuid, prefix, colors, color_enabled, prefix_enabled, prefix_purchased, last_color_change, last_prefix_change) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) {
@ -150,7 +151,7 @@ public class GradientDataManager {
private void saveYamlData() {
File dataFile = new File(plugin.getDataFolder(), "gradient-data.yml");
FileConfiguration data = new YamlConfiguration();
for (Map.Entry<UUID, GradientPlayerData> entry : cache.entrySet()) {
String path = entry.getKey().toString();
GradientPlayerData pd = entry.getValue();
@ -162,7 +163,7 @@ public class GradientDataManager {
data.set(path + ".last-color-change", pd.getLastColorChange());
data.set(path + ".last-prefix-change", pd.getLastPrefixChange());
}
try {
data.save(dataFile);
} catch (IOException e) {

View file

@ -29,25 +29,71 @@ public class GradientPlayerData {
this.lastPrefixChange = 0;
}
public UUID getUuid() { return uuid; }
public String getPrefix() { return prefix; }
public void setPrefix(String prefix) { this.prefix = prefix; }
public List<String> getColors() { return colors; }
public UUID getUuid() {
return uuid;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public List<String> getColors() {
return colors;
}
public void setColors(List<String> colors) {
this.colors = colors != null ? new ArrayList<>(colors) : new ArrayList<>();
}
public boolean hasPrefix() { return prefix != null && !prefix.isEmpty(); }
public boolean hasColors() { return colors != null && !colors.isEmpty(); }
public boolean isColorEnabled() { return colorEnabled; }
public void setColorEnabled(boolean colorEnabled) { this.colorEnabled = colorEnabled; }
public boolean isPrefixEnabled() { return prefixEnabled; }
public void setPrefixEnabled(boolean prefixEnabled) { this.prefixEnabled = prefixEnabled; }
public boolean isPrefixPurchased() { return prefixPurchased; }
public void setPrefixPurchased(boolean prefixPurchased) { this.prefixPurchased = prefixPurchased; }
public long getLastColorChange() { return lastColorChange; }
public void setLastColorChange(long lastColorChange) { this.lastColorChange = lastColorChange; }
public long getLastPrefixChange() { return lastPrefixChange; }
public void setLastPrefixChange(long lastPrefixChange) { this.lastPrefixChange = lastPrefixChange; }
public boolean hasPrefix() {
return prefix != null && !prefix.isEmpty();
}
public boolean hasColors() {
return colors != null && !colors.isEmpty();
}
public boolean isColorEnabled() {
return colorEnabled;
}
public void setColorEnabled(boolean colorEnabled) {
this.colorEnabled = colorEnabled;
}
public boolean isPrefixEnabled() {
return prefixEnabled;
}
public void setPrefixEnabled(boolean prefixEnabled) {
this.prefixEnabled = prefixEnabled;
}
public boolean isPrefixPurchased() {
return prefixPurchased;
}
public void setPrefixPurchased(boolean prefixPurchased) {
this.prefixPurchased = prefixPurchased;
}
public long getLastColorChange() {
return lastColorChange;
}
public void setLastColorChange(long lastColorChange) {
this.lastColorChange = lastColorChange;
}
public long getLastPrefixChange() {
return lastPrefixChange;
}
public void setLastPrefixChange(long lastPrefixChange) {
this.lastPrefixChange = lastPrefixChange;
}
}

View file

@ -23,27 +23,22 @@ import java.util.List;
/**
* GUI подтверждения для градиентного модуля
*/
public class GradientConfirmGUI implements InventoryHolder {
public enum ConfirmType {
COLOR, PREFIX
}
public final class GradientConfirmGUI implements InventoryHolder {
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
.character('&')
.hexCharacter('#')
.hexColors()
.build();
private final GradientModule module;
private final Player player;
private final Inventory inventory;
private Inventory inventory;
private final ConfirmType type;
private final List<String> colors;
private final String prefix;
private final int price;
public GradientConfirmGUI(GradientModule module, Player player, ConfirmType type,
public GradientConfirmGUI(GradientModule module, Player player, ConfirmType type,
List<String> colors, String prefix, int price) {
this.module = module;
this.player = player;
@ -51,7 +46,11 @@ public class GradientConfirmGUI implements InventoryHolder {
this.colors = colors;
this.prefix = prefix;
this.price = price;
this.inventory = Bukkit.createInventory(this, 27,
}
private void initInventory() {
if (inventory != null) return;
this.inventory = Bukkit.createInventory(this, 27,
Component.text("Подтверждение", NamedTextColor.DARK_GRAY));
setupItems();
}
@ -65,12 +64,12 @@ public class GradientConfirmGUI implements InventoryHolder {
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
SkullMeta skullMeta = (SkullMeta) head.getItemMeta();
skullMeta.setOwningPlayer(player);
String preview = buildPreview();
// Для GUI используем LegacyComponentSerializer с поддержкой HEX
Component previewComponent = SERIALIZER.deserialize(preview);
skullMeta.displayName(previewComponent.decoration(TextDecoration.ITALIC, false));
List<Component> lore = new ArrayList<>();
lore.add(Component.empty());
lore.add(Component.text("Так будет выглядеть ваш ник", NamedTextColor.GRAY)
@ -134,7 +133,7 @@ public class GradientConfirmGUI implements InventoryHolder {
if (lpPrefix != null && !lpPrefix.isEmpty()) {
// Конвертируем LP префикс в legacy формат
lpPrefix = convertToLegacy(lpPrefix);
// Если градиент на LP префикс включен - применяем градиент на всю строку
if (colorsToUse != null && !colorsToUse.isEmpty() && module.getConfig().isGradientOnLuckPermsPrefix()) {
String cleanLpPrefix = stripColors(lpPrefix);
@ -187,14 +186,37 @@ public class GradientConfirmGUI implements InventoryHolder {
}
public void open() {
initInventory();
player.openInventory(inventory);
}
@Override
public @NotNull Inventory getInventory() { return inventory; }
public Player getPlayer() { return player; }
public ConfirmType getType() { return type; }
public List<String> getColors() { return colors; }
public String getPrefix() { return prefix; }
public int getPrice() { return price; }
public @NotNull Inventory getInventory() {
initInventory();
return inventory;
}
public Player getPlayer() {
return player;
}
public ConfirmType getType() {
return type;
}
public List<String> getColors() {
return colors;
}
public String getPrefix() {
return prefix;
}
public int getPrice() {
return price;
}
public enum ConfirmType {
COLOR, PREFIX
}
}

View file

@ -22,7 +22,7 @@ public class GradientLuckPermsHook {
public GradientLuckPermsHook(JavaPlugin plugin) {
this.enabled = false;
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
try {
this.luckPerms = LuckPermsProvider.get();
@ -40,12 +40,12 @@ public class GradientLuckPermsHook {
public void setPrefix(Player player, String prefix) {
if (!isEnabled()) return;
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
if (user == null) return;
user.data().clear(node ->
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY
user.data().clear(node ->
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY
);
if (prefix != null && !prefix.isEmpty()) {
@ -58,12 +58,12 @@ public class GradientLuckPermsHook {
public void setPrefix(UUID uuid, String prefix) {
if (!isEnabled()) return;
luckPerms.getUserManager().loadUser(uuid).thenAccept(user -> {
if (user == null) return;
user.data().clear(node ->
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY
user.data().clear(node ->
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY
);
if (prefix != null && !prefix.isEmpty()) {
@ -85,10 +85,10 @@ public class GradientLuckPermsHook {
public String getActivePrefix(Player player) {
if (!isEnabled()) return null;
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
if (user == null) return null;
CachedMetaData metaData = user.getCachedData().getMetaData();
return metaData.getPrefix();
}

View file

@ -19,12 +19,11 @@ import java.util.Map;
*/
public class GradientGUIListener implements Listener {
private final GradientModule module;
private static final Map<GradientConfirmGUI.ConfirmType, String> PRICE_MSG_KEYS = Map.of(
GradientConfirmGUI.ConfirmType.COLOR, "not-enough-points-color",
GradientConfirmGUI.ConfirmType.PREFIX, "not-enough-points-prefix"
);
private final GradientModule module;
public GradientGUIListener(GradientModule module) {
this.module = module;
@ -33,9 +32,9 @@ public class GradientGUIListener implements Listener {
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getInventory().getHolder() instanceof GradientConfirmGUI gui)) return;
event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player player) || !player.equals(gui.getPlayer())) return;
switch (event.getRawSlot()) {
@ -46,10 +45,10 @@ public class GradientGUIListener implements Listener {
private void handleConfirm(Player player, GradientConfirmGUI gui) {
player.closeInventory();
GradientMessages msg = module.getMessages();
int price = gui.getPrice();
if (price > 0 && !player.hasPermission("gradient.bypass.cost")) {
if (!module.hasPlayerPoints()) {
player.sendMessage("§cPlayerPoints не установлен!");
@ -85,14 +84,14 @@ public class GradientGUIListener implements Listener {
if (module.getLuckPermsHook() != null) {
module.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(module, data));
}
msg.send(player, price > 0 ? "prefix-success" : "prefix-success-free",
msg.send(player, price > 0 ? "prefix-success" : "prefix-success-free",
"price", String.valueOf(price));
}
}
FoliaUtil.runEntityTask(module.getPlugin(), player,
FoliaUtil.runEntityTask(module.getPlugin(), player,
() -> DisplayNameUtil.updateDisplayName(module, player, data));
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
}

View file

@ -31,7 +31,7 @@ public class GradientPlayerListener implements Listener {
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
FoliaUtil.runAsync(module.getPlugin(),
FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
}
}

View file

@ -21,13 +21,14 @@ public final class DisplayNameUtil {
.hexColors()
.build();
private DisplayNameUtil() {}
private DisplayNameUtil() {
}
public static void updateDisplayName(GradientModule module, Player player, GradientPlayerData data) {
GradientConfig cfg = module.getConfig();
String prefix = null;
String prefixFormat = cfg.getPrefixFormat();
// Определяем префикс: сначала кастомный, потом LuckPerms
if (data.isPrefixEnabled() && data.hasPrefix()) {
// Есть кастомный префикс
@ -37,9 +38,9 @@ public final class DisplayNameUtil {
String lpPrefix = module.getLuckPermsHook().getActivePrefix(player);
if (lpPrefix != null && !lpPrefix.isEmpty()) {
String displayName = buildWithLuckPermsPrefix(
lpPrefix,
player.getName(),
data.isColorEnabled() ? data.getColors() : null,
lpPrefix,
player.getName(),
data.isColorEnabled() ? data.getColors() : null,
cfg.isUseLegacyRgbFormat(),
cfg.isContinuousGradient()
);
@ -51,7 +52,7 @@ public final class DisplayNameUtil {
return;
}
}
// Строим display name с кастомным префиксом или без префикса
String displayName = GradientUtil.buildDisplayName(
prefix,
@ -62,7 +63,7 @@ public final class DisplayNameUtil {
prefixFormat,
cfg.isUseLegacyRgbFormat()
);
// Для display name нужен §x формат, конвертируем MiniMessage в legacy
displayName = convertMiniMessageToLegacy(displayName);
var component = SERIALIZER.deserialize(displayName);
@ -75,12 +76,12 @@ public final class DisplayNameUtil {
*/
private static String convertMiniMessageToLegacy(String text) {
if (text == null) return "";
// Конвертируем <#RRGGBB> в §x§R§R§G§G§B§B
Pattern pattern = Pattern.compile("<#([0-9a-fA-F]{6})>");
Matcher matcher = pattern.matcher(text);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String hex = matcher.group(1).toLowerCase();
StringBuilder replacement = new StringBuilder("§x");
@ -90,18 +91,18 @@ public final class DisplayNameUtil {
matcher.appendReplacement(sb, replacement.toString());
}
matcher.appendTail(sb);
return sb.toString();
}
private static String buildWithLuckPermsPrefix(String lpPrefix, String nick,
List<String> colors,
boolean useLegacyFormat,
boolean continuousGradient) {
private static String buildWithLuckPermsPrefix(String lpPrefix, String nick,
List<String> colors,
boolean useLegacyFormat,
boolean continuousGradient) {
if (colors == null || colors.isEmpty()) {
return lpPrefix + nick;
}
if (continuousGradient) {
String cleanPrefix = stripColors(lpPrefix);
String fullText = cleanPrefix + nick;
@ -118,15 +119,15 @@ public final class DisplayNameUtil {
public static String buildColoredPrefix(GradientModule module, GradientPlayerData data) {
if (!data.hasPrefix() || !data.isPrefixEnabled()) return null;
GradientConfig cfg = module.getConfig();
String prefix = cfg.getPrefixFormat().replace("{prefix}", data.getPrefix()).stripTrailing();
// Применяем градиент только если цвета включены И есть цвета
if (data.hasColors() && data.isColorEnabled() && cfg.isGradientOnPrefix()) {
return GradientUtil.applyGradient(prefix, data.getColors(), cfg.isUseLegacyRgbFormat());
}
// Возвращаем префикс без цветов
return prefix;
}
@ -137,12 +138,12 @@ public final class DisplayNameUtil {
public static String getFullDisplayName(GradientModule module, Player player) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
GradientConfig cfg = module.getConfig();
String prefix = null;
if (data.isPrefixEnabled() && data.hasPrefix()) {
prefix = data.getPrefix();
}
return GradientUtil.buildDisplayName(
prefix,
player.getName(),

View file

@ -17,7 +17,8 @@ public final class FoliaUtil {
IS_FOLIA = checkFolia();
}
private FoliaUtil() {}
private FoliaUtil() {
}
private static boolean checkFolia() {
try {

View file

@ -5,15 +5,14 @@ package com.loki.lochat.gradient.util;
*/
public final class GradientConstants {
private GradientConstants() {}
public static final long MILLIS_PER_SECOND = 1000L;
// GUI слоты
public static final int GUI_CONFIRM_SLOT = 11;
public static final int GUI_CANCEL_SLOT = 15;
public static final int GUI_PREVIEW_SLOT = 13;
// LuckPerms
public static final int LUCKPERMS_PREFIX_PRIORITY = 100;
private GradientConstants() {
}
}

View file

@ -11,7 +11,8 @@ public final class GradientUtil {
private static final Pattern HEX_PATTERN = Pattern.compile("^#[0-9A-Fa-f]{6}$");
private GradientUtil() {}
private GradientUtil() {
}
public static boolean isValidHex(String color) {
return HEX_PATTERN.matcher(color).matches();
@ -50,7 +51,7 @@ public final class GradientUtil {
// Вычисляем позицию символа от 0.0 до 1.0
double ratio = length > 1 ? (double) i / (length - 1) : 0.0;
Color interpolated = interpolateMultiColor(colors, ratio);
String hex = String.format("#%02x%02x%02x",
String hex = String.format("#%02x%02x%02x",
interpolated.getRed(), interpolated.getGreen(), interpolated.getBlue());
result.append(formatHex(hex, useLegacyFormat)).append(chars[i]);
}
@ -142,7 +143,7 @@ public final class GradientUtil {
for (int i = 0; i < length; i++) {
double ratio = length > 1 ? (double) i / (length - 1) : 0.0;
Color interpolated = interpolateMultiColor(colors, ratio);
String hex = String.format("#%02x%02x%02x",
String hex = String.format("#%02x%02x%02x",
interpolated.getRed(), interpolated.getGreen(), interpolated.getBlue());
result.append(formatHexForTab(hex)).append(chars[i]);
}
@ -151,8 +152,8 @@ public final class GradientUtil {
return result.toString();
}
public static String buildDisplayName(String prefix, String nick, List<String> colors,
boolean gradientOnPrefix, boolean continuousGradient,
public static String buildDisplayName(String prefix, String nick, List<String> colors,
boolean gradientOnPrefix, boolean continuousGradient,
String prefixFormat, boolean useLegacyFormat) {
if (colors == null || colors.isEmpty()) {
if (prefix != null && !prefix.isEmpty()) {

View file

@ -16,7 +16,7 @@ public class LibertyBansHook {
public LibertyBansHook(LoChat plugin) {
this.plugin = plugin;
this.muteService = plugin.getServiceRegistry().get(MuteService.class);
// Проверяем наличие LibertyBans
if (plugin.getServer().getPluginManager().getPlugin("LibertyBans") != null) {
plugin.getLogger().info("LibertyBans обнаружен. Используйте встроенную систему мутов LoChat (/lmute, /lunmute)");

View file

@ -1,7 +1,10 @@
package com.loki.lochat.integrations;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.*;
import com.loki.lochat.api.service.ChatService;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.api.service.PMService;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.gradient.GradientModule;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.Bukkit;
@ -66,25 +69,25 @@ public class PlaceholderAPIHook extends PlaceholderExpansion {
yield lastPlayer.getName() != null ? lastPlayer.getName() : "";
}
case "spy_enabled" -> String.valueOf(spyService.isSpying(uuid));
// Gradient placeholders (совместимость с LoPreff)
// Используем TAB формат §x§R§R§G§G§B§B для совместимости с TAB плагином
case "gradient_full", "full" -> {
Player onlinePlayer = player.getPlayer();
if (onlinePlayer == null) yield player.getName() != null ? player.getName() : "";
// Если gradient модуль выключен — берём только LuckPerms префикс + ник
if (gradient == null || !gradient.isEnabled()) {
yield player.getName() != null ? player.getName() : "";
}
// Используем TAB формат для плейсхолдеров
yield gradient.getFormattedNameForTab(onlinePlayer);
}
case "gradient_name", "name" -> {
Player onlinePlayer = player.getPlayer();
if (onlinePlayer == null) yield player.getName() != null ? player.getName() : "";
if (gradient == null || !gradient.isEnabled()) {
yield player.getName();
}
@ -93,14 +96,14 @@ public class PlaceholderAPIHook extends PlaceholderExpansion {
case "gradient_prefix", "prefix" -> {
Player onlinePlayer = player.getPlayer();
if (onlinePlayer == null) yield "";
if (gradient == null || !gradient.isEnabled()) yield "";
yield gradient.getPrefix(onlinePlayer);
}
case "lp_prefix" -> {
Player onlinePlayer = player.getPlayer();
if (onlinePlayer == null) yield "";
if (gradient == null || !gradient.isEnabled()) yield "";
yield gradient.getLuckPermsPrefix(onlinePlayer);
}

View file

@ -35,9 +35,9 @@ public class SkinsRestorerHook {
Class<?> apiClass = Class.forName("net.skinsrestorer.api.SkinsRestorerAPI");
Method getApiMethod = apiClass.getMethod("getApi");
skinsAPI = getApiMethod.invoke(null);
getSkinDataMethod = skinsAPI.getClass().getMethod("getSkinData", String.class);
enabled = true;
plugin.getLogger().info("SkinsRestorer подключен!");
} catch (Exception e) {
@ -52,6 +52,7 @@ public class SkinsRestorerHook {
/**
* Получает текстуру скина игрока
*
* @param player игрок
* @return URL текстуры или null
*/
@ -72,6 +73,7 @@ public class SkinsRestorerHook {
/**
* Получает URL головы игрока для отображения
*
* @param playerName имя игрока
* @return URL головы
*/

View file

@ -5,8 +5,8 @@ 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.event.player.AsyncChatEvent;
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.event.ClickEvent;
@ -25,129 +25,129 @@ public class ChatEventListener implements Listener {
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());
boolean allowed = messageService.processMessage(sender, message);
if (!allowed) {
event.setCancelled(true);
return;
}
AdvancedMessageFilter.FilterResult filterResult = advancedFilter.filterMessage(sender, message);
if (!filterResult.isAllowed()) {
if (!filterResult.allowed()) {
event.setCancelled(true);
sender.sendMessage(com.loki.lochat.utils.ChatFormatter.parse(filterResult.getBlockReason()));
sender.sendMessage(com.loki.lochat.utils.ChatFormatter.parse(filterResult.blockReason()));
return;
}
String filteredMessage = filterResult.getFilteredMessage();
String filteredMessage = filterResult.filteredMessage();
event.message(Component.text(filteredMessage));
event.renderer(new EnhancedChatRenderer(sender, event.message()));
}
private class EnhancedChatRenderer implements ChatRenderer {
private final Player sender;
private final Component originalMessage;
public EnhancedChatRenderer(Player sender, Component originalMessage) {
this.sender = sender;
this.originalMessage = originalMessage;
}
@Override
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
String prefix = plugin.getConfig().getString("chat.global.prefix", "[G]");
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 Component.text(prefix + " ")
.append(playerComponent)
.append(Component.text(": "))
.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>▶ Клик: написать ЛС"
"<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()));
.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);
String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!35538117-c5a8-4bfe-b9f2-b0dcf9374222'()*+,;=%]+)";
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)
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("❤"));

View file

@ -15,12 +15,12 @@ import org.bukkit.event.player.PlayerQuitEvent;
public class PlayerEventListener implements Listener {
private final PlayerDataService playerDataService;
private final NickService nickService;
public PlayerEventListener(ServiceRegistry registry) {
this.playerDataService = registry.get(PlayerDataService.class);
this.nickService = registry.get(NickService.class);
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
@ -29,7 +29,7 @@ public class PlayerEventListener implements Listener {
nickService.updatePlayerDisplay(player);
}
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();

View file

@ -19,7 +19,7 @@ public class AutoMessageManager {
private final Random random = new Random();
private final AtomicInteger currentIndex = new AtomicInteger(0);
private boolean running = false;
private Map<String, List<String>> messages;
private List<String> messageOrder;
private String prefix;
@ -35,21 +35,21 @@ public class AutoMessageManager {
if (!file.exists()) {
plugin.saveResource("automessages.yml", false);
}
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
// Настройки
prefix = config.getString("settings.prefix", "&6[&eINFO&6]&r");
mode = config.getString("settings.mode", "sequential").toLowerCase();
// Загружаем сообщения
messages = new LinkedHashMap<>();
ConfigurationSection msgSection = config.getConfigurationSection("messages");
if (msgSection != null) {
for (String key : msgSection.getKeys(false)) {
List<String> lines = new ArrayList<>();
if (msgSection.isList(key)) {
lines.addAll(msgSection.getStringList(key));
} else {
@ -58,16 +58,16 @@ public class AutoMessageManager {
lines.add(single);
}
}
if (!lines.isEmpty()) {
messages.put(key, lines);
}
}
}
// Определяем порядок
messageOrder = new ArrayList<>();
if (mode.equals("custom")) {
String orderStr = config.getString("settings.order", "");
for (String item : orderStr.split(",")) {
@ -87,11 +87,11 @@ public class AutoMessageManager {
}
}
}
if (messageOrder.isEmpty()) {
messageOrder.addAll(messages.keySet());
}
plugin.getLogger().info("Загружено " + messages.size() + " автосообщений");
}
@ -125,14 +125,14 @@ public class AutoMessageManager {
}
String messageKey;
if (mode.equals("random")) {
messageKey = messageOrder.get(random.nextInt(messageOrder.size()));
} else {
int index = currentIndex.getAndUpdate(i -> (i + 1) % messageOrder.size());
messageKey = messageOrder.get(index);
}
List<String> lines = messages.get(messageKey);
if (lines == null || lines.isEmpty()) return;
@ -140,7 +140,7 @@ public class AutoMessageManager {
for (String line : lines) {
// Заменяем плейсхолдеры
String processed = line.replace("%online%", String.valueOf(Bukkit.getOnlinePlayers().size()));
// Добавляем префикс к первой непустой строке
Component formatted;
if (isFirstLine && !line.trim().isEmpty()) {
@ -149,7 +149,7 @@ public class AutoMessageManager {
} else {
formatted = ChatFormatter.parse(processed);
}
for (var player : Bukkit.getOnlinePlayers()) {
player.sendMessage(formatted);
}

View file

@ -24,14 +24,15 @@ public class CustomCommandManager {
private final LoChat plugin;
private final Map<String, CustomCommandData> commands;
public CustomCommandManager(LoChat plugin) {
private CustomCommandManager(LoChat plugin) {
this.plugin = plugin;
this.commands = new HashMap<>();
init();
}
private void init() {
loadCommands();
public static CustomCommandManager create(LoChat plugin) {
CustomCommandManager manager = new CustomCommandManager(plugin);
manager.loadCommands();
return manager;
}
private void loadCommands() {
@ -41,15 +42,15 @@ public class CustomCommandManager {
}
FileConfiguration commandsConfig = YamlConfiguration.loadConfiguration(commandsFile);
// Очищаем старые команды
commands.clear();
// Загружаем команды из конфига
for (String commandName : commandsConfig.getKeys(false)) {
ConfigurationSection section = commandsConfig.getConfigurationSection(commandName);
if (section == null) continue;
CustomCommandData data = new CustomCommandData(
commandName,
section.getString("permission"),
@ -58,13 +59,13 @@ public class CustomCommandManager {
section.getString("type", "chat"),
section.getString("target", "sender")
);
commands.put(commandName, data);
// Регистрируем команду
registerCommand(data);
}
plugin.getLogger().info("Загружено " + commands.size() + " кастомных команд");
}
@ -74,17 +75,17 @@ public class CustomCommandManager {
Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
commandMapField.setAccessible(true);
CommandMap commandMap = (CommandMap) commandMapField.get(Bukkit.getServer());
// Создаем и регистрируем команду
CustomCommand command = new CustomCommand(data.name, this);
commandMap.register("lochat", command);
// Регистрируем алиасы
for (String alias : data.aliases) {
CustomCommand aliasCommand = new CustomCommand(alias, this);
commandMap.register("lochat", aliasCommand);
}
} catch (Exception e) {
plugin.getLogger().severe("Ошибка регистрации команды " + data.name + ": " + e.getMessage());
}
@ -105,18 +106,18 @@ public class CustomCommandManager {
}
}
}
if (data == null) return false;
// Проверяем права
if (data.permission != null && !player.hasPermission(data.permission)) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getNoPermission()));
return true;
}
// Обрабатываем плейсхолдеры
String message = processPlaceholders(data.message, player, args);
// Выполняем команду в зависимости от типа
switch (data.type.toLowerCase()) {
case "broadcast" -> sendBroadcast(data.target, message);
@ -124,26 +125,26 @@ public class CustomCommandManager {
case "actionbar" -> sendActionBar(data.target, player, message);
default -> sendChatMessage(data.target, player, message);
}
return true;
}
private String processPlaceholders(String message, Player player, String[] args) {
String result = message;
// Базовые плейсхолдеры
result = result.replace("{player}", player.getName());
result = result.replace("{x}", String.valueOf(player.getLocation().getBlockX()));
result = result.replace("{y}", String.valueOf(player.getLocation().getBlockY()));
result = result.replace("{z}", String.valueOf(player.getLocation().getBlockZ()));
result = result.replace("{world}", player.getWorld().getName());
// Аргументы команды
for (int i = 0; i < args.length; i++) {
result = result.replace("{arg" + i + "}", args[i]);
}
result = result.replace("{args}", String.join(" ", args));
// PlaceholderAPI если доступен
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
try {
@ -156,13 +157,13 @@ public class CustomCommandManager {
plugin.getLogger().warning("Ошибка при обработке PlaceholderAPI плейсхолдеров: " + e.getMessage());
}
}
return result;
}
private void sendChatMessage(String target, Player sender, String message) {
Component component = ChatFormatter.parse(message);
switch (target.toLowerCase()) {
case "sender" -> sender.sendMessage(component);
case "all" -> {
@ -208,10 +209,10 @@ public class CustomCommandManager {
String[] parts = message.split("\\n", 2);
Component title = ChatFormatter.parse(parts[0]);
Component subtitle = parts.length > 1 ? ChatFormatter.parse(parts[1]) : Component.empty();
Title titleObj = Title.title(title, subtitle,
Title titleObj = Title.title(title, subtitle,
Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500)));
switch (target.toLowerCase()) {
case "sender" -> sender.showTitle(titleObj);
case "all" -> {
@ -236,7 +237,7 @@ public class CustomCommandManager {
private void sendActionBar(String target, Player sender, String message) {
Component component = ChatFormatter.parse(message);
switch (target.toLowerCase()) {
case "sender" -> sender.sendActionBar(component);
case "all" -> {
@ -263,22 +264,7 @@ public class CustomCommandManager {
return commands;
}
public static class CustomCommandData {
public final String name;
public final String permission;
public final String message;
public final List<String> aliases;
public final String type;
public final String target;
public CustomCommandData(String name, String permission, String message,
List<String> aliases, String type, String target) {
this.name = name;
this.permission = permission;
this.message = message;
this.aliases = aliases;
this.type = type;
this.target = target;
}
public record CustomCommandData(String name, String permission, String message, List<String> aliases, String type,
String target) {
}
}

View file

@ -17,9 +17,14 @@ public class EmojiManager {
private boolean enabled;
private boolean requirePermission;
public EmojiManager(LoChat plugin) {
private EmojiManager(LoChat plugin) {
this.plugin = plugin;
load();
}
public static EmojiManager create(LoChat plugin) {
EmojiManager manager = new EmojiManager(plugin);
manager.load();
return manager;
}
public void load() {

View file

@ -11,7 +11,7 @@ import org.bukkit.command.CommandSender;
public class ChatFormatter {
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.legacyAmpersand();
public static Object parse(String message) {
try {
return MINI_MESSAGE.deserialize(message);
@ -19,14 +19,14 @@ public class ChatFormatter {
return LEGACY.deserialize(message);
}
}
public static String toPlain(Object message) {
if (message instanceof Component component) {
return LegacyComponentSerializer.legacySection().serialize(component);
}
return message.toString();
}
public static void sendMessage(CommandSender sender, Object message) {
if (message instanceof Component component) {
sender.sendMessage(component);
@ -34,17 +34,17 @@ public class ChatFormatter {
sender.sendMessage(message.toString());
}
}
public static void broadcast(Object message) {
if (message instanceof Component component) {
org.bukkit.Bukkit.broadcast(component);
}
}
public static String stripTags(String message) {
return message.replaceAll("<[^>]+>", "").replaceAll("§[0-9a-fk-or]", "");
}
public static String convertAllColors(String message) {
return message.replace("&", "§");
}

View file

@ -6,12 +6,12 @@ import org.bukkit.entity.Player;
* Утилита для проверки расстояния между игроками
*/
public class DistanceUtil {
public static boolean isInRange(Player player1, Player player2, int radius) {
if (!player1.getWorld().equals(player2.getWorld())) {
return false;
}
return player1.getLocation().distance(player2.getLocation()) <= radius;
}
}

View file

@ -9,7 +9,7 @@ import org.bukkit.plugin.java.JavaPlugin;
*/
public class FoliaUtil {
private static boolean isFolia;
static {
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
@ -18,11 +18,11 @@ public class FoliaUtil {
isFolia = false;
}
}
public static boolean isFolia() {
return isFolia;
}
public static void runAsync(JavaPlugin plugin, Runnable task) {
if (isFolia) {
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> task.run());
@ -30,7 +30,7 @@ public class FoliaUtil {
Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
}
}
public static void runEntityTask(JavaPlugin plugin, Entity entity, Runnable task) {
if (isFolia) {
entity.getScheduler().run(plugin, scheduledTask -> task.run(), null);
@ -38,12 +38,13 @@ public class FoliaUtil {
Bukkit.getScheduler().runTask(plugin, task);
}
}
/**
* Запускает глобальную повторяющуюся задачу
* @param plugin плагин
* @param task задача
* @param delayTicks задержка перед первым запуском (в тиках)
*
* @param plugin плагин
* @param task задача
* @param delayTicks задержка перед первым запуском (в тиках)
* @param periodTicks период повторения (в тиках)
*/
public static void runTimerAsync(JavaPlugin plugin, Runnable task, long delayTicks, long periodTicks) {

View file

@ -4,17 +4,18 @@ package com.loki.lochat.util;
* Утилита для парсинга времени (DRY - вынесена общая логика)
*/
public class TimeParser {
/**
* Парсит строку времени в миллисекунды
*
* @param timeStr строка вида "1d", "2h", "30m", "60s"
* @return миллисекунды или -1 при ошибке
*/
public static long parse(String timeStr) {
if (timeStr == null || timeStr.isEmpty()) return 0;
timeStr = timeStr.toLowerCase();
try {
if (timeStr.endsWith("d")) {
return Long.parseLong(timeStr.replace("d", "")) * 24 * 60 * 60 * 1000L;
@ -31,18 +32,18 @@ public class TimeParser {
return -1;
}
}
/**
* Форматирует миллисекунды в читаемую строку
*/
public static String format(long millis) {
if (millis <= 0) return "0с";
long seconds = millis / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if (days > 0) {
return days + "д " + (hours % 24) + "ч";
} else if (hours > 0) {

View file

@ -8,28 +8,28 @@ import org.bukkit.plugin.Plugin;
* Менеджер для работы с Adventure API на Arclight/Forge серверах
*/
public class AdventureManager {
private static boolean adventureAvailable = false;
private static Object audiences;
public static void init(Plugin plugin) {
try {
// Проверяем наличие Adventure API
Class.forName("net.kyori.adventure.text.Component");
Class<?> audiencesClass = Class.forName("net.kyori.adventure.platform.bukkit.BukkitAudiences");
// Создаем BukkitAudiences через рефлексию
Object audiencesInstance = audiencesClass.getMethod("create", Plugin.class).invoke(null, plugin);
audiences = audiencesInstance;
adventureAvailable = true;
plugin.getLogger().info("Adventure API обнаружен - используем нативную поддержку");
} catch (Exception e) {
adventureAvailable = false;
plugin.getLogger().info("Adventure API не найден - используем legacy режим");
}
}
public static void shutdown() {
if (audiences != null && adventureAvailable) {
try {
@ -41,7 +41,7 @@ public class AdventureManager {
}
adventureAvailable = false;
}
public static void sendMessage(CommandSender sender, Object message) {
if (adventureAvailable && audiences != null) {
try {
@ -53,7 +53,7 @@ public class AdventureManager {
// Fallback на legacy
}
}
// Legacy fallback
if (message instanceof String) {
sender.sendMessage((String) message);
@ -61,7 +61,7 @@ public class AdventureManager {
sender.sendMessage(message.toString());
}
}
public static void sendMessage(Player player, Object message) {
if (adventureAvailable && audiences != null) {
try {
@ -73,7 +73,7 @@ public class AdventureManager {
// Fallback на legacy
}
}
// Legacy fallback
if (message instanceof String) {
player.sendMessage((String) message);
@ -81,7 +81,7 @@ public class AdventureManager {
player.sendMessage(message.toString());
}
}
public static boolean isAdventureAvailable() {
return adventureAvailable;
}

View file

@ -11,7 +11,8 @@ public final class ChatFormatter {
private static final PlainTextComponentSerializer PLAIN =
PlainTextComponentSerializer.plainText();
private ChatFormatter() {}
private ChatFormatter() {
}
/* ===================== BASIC ===================== */
@ -38,7 +39,7 @@ public final class ChatFormatter {
*/
private static String convertLegacyFormats(String message) {
if (message == null) return "";
// Конвертируем &#RRGGBB в <#RRGGBB>
java.util.regex.Pattern pattern1 = java.util.regex.Pattern.compile("&#([0-9a-fA-F]{6})");
java.util.regex.Matcher matcher1 = pattern1.matcher(message);
@ -49,7 +50,7 @@ public final class ChatFormatter {
}
matcher1.appendTail(sb1);
message = sb1.toString();
// Конвертируем #RRGGBB в <#RRGGBB> (только если не внутри тегов)
java.util.regex.Pattern pattern2 = java.util.regex.Pattern.compile("(?<!<)#([0-9a-fA-F]{6})(?![^<]*>)");
java.util.regex.Matcher matcher2 = pattern2.matcher(message);
@ -60,7 +61,7 @@ public final class ChatFormatter {
}
matcher2.appendTail(sb2);
message = sb2.toString();
// Конвертируем §[0-9a-fk-or] в соответствующие MiniMessage теги
message = message.replaceAll("§0", "<black>");
message = message.replaceAll("§1", "<dark_blue>");
@ -84,7 +85,7 @@ public final class ChatFormatter {
message = message.replaceAll("§n", "<underlined>");
message = message.replaceAll("§o", "<italic>");
message = message.replaceAll("§r", "<reset>");
// Конвертируем &[0-9a-fk-or] в соответствующие MiniMessage теги
message = message.replaceAll("&0", "<black>");
message = message.replaceAll("&1", "<dark_blue>");
@ -102,7 +103,7 @@ public final class ChatFormatter {
message = message.replaceAll("&d", "<light_purple>");
message = message.replaceAll("&e", "<yellow>");
message = message.replaceAll("&f", "<white>");
// Конвертируем форматирование
message = message.replaceAll("&k", "<obfuscated>");
message = message.replaceAll("&l", "<bold>");
@ -110,7 +111,7 @@ public final class ChatFormatter {
message = message.replaceAll("&n", "<underlined>");
message = message.replaceAll("&o", "<italic>");
message = message.replaceAll("&r", "<reset>");
return message;
}
@ -122,7 +123,7 @@ public final class ChatFormatter {
public static String toPlain(Component component) {
return component == null ? "" : PLAIN.serialize(component);
}
public static String toPlain(Object obj) {
if (obj == null) return "";
if (obj instanceof Component component) {
@ -130,7 +131,7 @@ public final class ChatFormatter {
}
return obj.toString();
}
/**
* Отправляет сообщение игроку
*/
@ -190,7 +191,7 @@ public final class ChatFormatter {
// Получаем отображаемые имена игроков
String senderDisplay = getPlayerDisplayName(sender);
String receiverDisplay = getPlayerDisplayName(receiver);
return parse(format
.replace("{sender}", sender.getName())
.replace("{receiver}", receiver.getName())
@ -208,7 +209,7 @@ public final class ChatFormatter {
// Получаем отображаемые имена игроков
String senderDisplay = getPlayerDisplayName(sender);
String receiverDisplay = getPlayerDisplayName(receiver);
return parse(format
.replace("{sender}", sender.getName())
.replace("{receiver}", receiver.getName())
@ -223,12 +224,12 @@ public final class ChatFormatter {
private static String getPlayerDisplayName(Player player) {
// Получаем экземпляр плагина
com.loki.lochat.LoChat plugin = com.loki.lochat.LoChat.getInstance();
// Если градиентный модуль включен, используем его
if (plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) {
return plugin.getGradientModule().getFormattedName(player);
}
// Иначе просто имя игрока
return player.getName();
}

View file

@ -5,27 +5,24 @@ package com.loki.lochat.utils;
*/
public final class Constants {
private Constants() {}
// Чат
public static final int DEFAULT_LOCAL_RADIUS = 100;
public static final int CLEAR_CHAT_LINES = 100;
// Время
public static final long MILLIS_IN_SECOND = 1000L;
public static final int SECONDS_IN_MINUTE = 60;
public static final int MINUTES_IN_HOUR = 60;
public static final int HOURS_IN_DAY = 24;
// Градиенты
public static final int DEFAULT_PRICE_PER_COLOR = 50;
public static final int DEFAULT_PREFIX_PRICE = 500;
public static final int LUCKPERMS_PREFIX_PRIORITY = 100;
// Folia/Paper совместимость
public static final int TICKS_PER_SECOND = 20;
public static final int MILLIS_PER_TICK = 50;
// Анти-спам
public static final int PERCENTAGE_MULTIPLIER = 100;
private Constants() {
}
}

View file

@ -5,22 +5,23 @@ import org.bukkit.entity.Player;
public class DistanceUtil {
private 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;
}

View file

@ -16,22 +16,22 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MentionHandler {
private static final Pattern EVERYONE_PATTERN = Pattern.compile("@everyone\\b");
private static final Pattern HERE_PATTERN = Pattern.compile("@here\\b");
private static final Pattern ROLE_PATTERN = Pattern.compile("@role:([\\w.]+)");
private static final Pattern PLAYER_PATTERN = Pattern.compile("@(\\w+)");
private final JavaPlugin plugin;
public MentionHandler(JavaPlugin plugin) {
this.plugin = plugin;
}
public Component processMentions(Component message, Player sender, Player viewer) {
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
List<Player> mentionedPlayers = new ArrayList<>();
if (sender.hasPermission("lochat.mention.everyone")) {
Matcher everyoneMatcher = EVERYONE_PATTERN.matcher(plainText);
if (everyoneMatcher.find()) {
@ -39,7 +39,7 @@ public class MentionHandler {
message = highlightMention(message, "@everyone");
}
}
if (sender.hasPermission("lochat.mention.here")) {
Matcher hereMatcher = HERE_PATTERN.matcher(plainText);
if (hereMatcher.find()) {
@ -47,57 +47,57 @@ public class MentionHandler {
message = highlightMention(message, "@here");
}
}
if (sender.hasPermission("lochat.mention.role")) {
Matcher roleMatcher = ROLE_PATTERN.matcher(plainText);
while (roleMatcher.find()) {
String permission = roleMatcher.group(1);
String fullMention = "@role:" + permission;
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.hasPermission(permission)) {
mentionedPlayers.add(player);
}
}
message = highlightMention(message, fullMention);
}
}
Matcher playerMatcher = PLAYER_PATTERN.matcher(plainText);
while (playerMatcher.find()) {
String playerName = playerMatcher.group(1);
Player mentioned = Bukkit.getPlayerExact(playerName);
if (mentioned != null && mentioned.isOnline()) {
mentionedPlayers.add(mentioned);
message = highlightMention(message, "@" + playerName);
}
}
if (mentionedPlayers.contains(viewer)) {
playMentionSound(viewer);
message = highlightForViewer(message, viewer);
}
return message;
}
private Component highlightMention(Component message, String mention) {
return message.replaceText(builder ->
builder.matchLiteral(mention)
.replacement(Component.text(mention, NamedTextColor.YELLOW, TextDecoration.BOLD))
return message.replaceText(builder ->
builder.matchLiteral(mention)
.replacement(Component.text(mention, NamedTextColor.YELLOW, TextDecoration.BOLD))
);
}
private Component highlightForViewer(Component message, Player viewer) {
String viewerMention = "@" + viewer.getName();
return message.replaceText(builder ->
builder.matchLiteral(viewerMention)
.replacement(Component.text(viewerMention, NamedTextColor.GOLD, TextDecoration.BOLD))
return message.replaceText(builder ->
builder.matchLiteral(viewerMention)
.replacement(Component.text(viewerMention, NamedTextColor.GOLD, TextDecoration.BOLD))
);
}
private void playMentionSound(Player player) {
FoliaUtil.runEntityTask(plugin, player, () -> {
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.5f);

View file

@ -2,6 +2,7 @@ package com.loki.lochat.utils;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import java.util.UUID;
@ -11,10 +12,32 @@ import java.util.UUID;
*/
public final class PlayerUtil {
private PlayerUtil() {}
private PlayerUtil() {
}
/**
* Safely parses a sound name from config.
* Uses deprecated Sound.valueOf() for backward compatibility with string-based config values.
*
* @param soundName the sound name to parse
* @param defaultSound the default sound to return if parsing fails
* @return the parsed Sound or the default if invalid
*/
@SuppressWarnings("deprecation")
public static Sound parseSound(String soundName, Sound defaultSound) {
if (soundName == null || soundName.isEmpty()) {
return defaultSound;
}
try {
return Sound.valueOf(soundName.toUpperCase());
} catch (IllegalArgumentException e) {
return defaultSound;
}
}
/**
* Находит игрока по имени (онлайн или оффлайн)
*
* @param name имя игрока
* @return UUID игрока или null если не найден
*/
@ -36,6 +59,7 @@ public final class PlayerUtil {
/**
* Проверяет, существует ли игрок (играл на сервере)
*
* @param name имя игрока
* @return true если игрок существует
*/
@ -45,6 +69,7 @@ public final class PlayerUtil {
/**
* Получает имя игрока по UUID
*
* @param uuid UUID игрока
* @return имя игрока или null
*/
@ -60,6 +85,7 @@ public final class PlayerUtil {
/**
* Проверяет, онлайн ли игрок
*
* @param uuid UUID игрока
* @return true если игрок онлайн
*/

View file

@ -7,42 +7,42 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TextFormatter {
private static final Pattern BOLD_PATTERN = Pattern.compile("\\*\\*(.+?)\\*\\*");
private static final Pattern ITALIC_PATTERN = Pattern.compile("\\*(.+?)\\*");
private static final Pattern UNDERLINE_PATTERN = Pattern.compile("__(.+?)__");
private static final Pattern STRIKETHROUGH_PATTERN = Pattern.compile("~~(.+?)~~");
private static final Pattern CODE_PATTERN = Pattern.compile("`(.+?)`");
public static Component formatMarkdown(Component message) {
String text = PlainTextComponentSerializer.plainText().serialize(message);
text = formatBold(text);
text = formatItalic(text);
text = formatUnderline(text);
text = formatStrikethrough(text);
text = formatCode(text);
return ChatFormatter.parse(text);
}
private static String formatBold(String text) {
Matcher matcher = BOLD_PATTERN.matcher(text);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String content = matcher.group(1);
matcher.appendReplacement(result, "<bold>" + content + "</bold>");
}
matcher.appendTail(result);
return result.toString();
}
private static String formatItalic(String text) {
Matcher matcher = ITALIC_PATTERN.matcher(text);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String content = matcher.group(1);
if (!content.startsWith("*") && !content.endsWith("*")) {
@ -50,46 +50,46 @@ public class TextFormatter {
}
}
matcher.appendTail(result);
return result.toString();
}
private static String formatUnderline(String text) {
Matcher matcher = UNDERLINE_PATTERN.matcher(text);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String content = matcher.group(1);
matcher.appendReplacement(result, "<underlined>" + content + "</underlined>");
}
matcher.appendTail(result);
return result.toString();
}
private static String formatStrikethrough(String text) {
Matcher matcher = STRIKETHROUGH_PATTERN.matcher(text);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String content = matcher.group(1);
matcher.appendReplacement(result, "<strikethrough>" + content + "</strikethrough>");
}
matcher.appendTail(result);
return result.toString();
}
private static String formatCode(String text) {
Matcher matcher = CODE_PATTERN.matcher(text);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String content = matcher.group(1);
matcher.appendReplacement(result, "&#888888`" + content + "`");
}
matcher.appendTail(result);
return result.toString();
}
}