From 4f6eaf35e9cb91ec5addf0c6781224469b47e78a Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Mon, 6 Apr 2026 00:38:34 +0200 Subject: [PATCH] chore: clean up documentation and rebuild with badwords filter - Remove outdated CODE_REVIEW_RU.md and REFACTORING_SERVICES.md documentation - Add badwords.txt resource file for swear filter functionality - Rebuild compiled classes with updated filter and configuration logic - Update all configuration files (appearance, discord, filters, messages) - Rebuild core components: ConfigManager, PluginInitializer, SwearFilter, GradientModule, PrefixService, PlayerEventListener, EnhancedChatRenderer - Consolidate build artifacts with source changes for consistency --- CODE_REVIEW_RU.md | 356 ------------------ REFACTORING_SERVICES.md | 287 -------------- bin/main/badwords.txt | 58 +++ .../loki/lochat/config/ConfigManager.class | Bin 9024 -> 10387 bytes .../loki/lochat/core/PluginInitializer.class | Bin 5640 -> 5668 bytes .../core/filter/filters/SwearFilter.class | Bin 5656 -> 7660 bytes .../loki/lochat/gradient/GradientModule.class | Bin 6635 -> 6805 bytes .../gradient/service/PrefixService.class | Bin 4334 -> 4696 bytes .../lochat/listener/PlayerEventListener.class | Bin 2323 -> 8240 bytes .../renderer/EnhancedChatRenderer.class | Bin 10576 -> 10970 bytes bin/main/config.yml | 255 +++++++++++-- bin/main/config/appearance.yml | 7 +- bin/main/config/discord.yml | 26 +- bin/main/config/filters.yml | 329 +++------------- bin/main/messages.yml | 108 +++--- .../com/loki/lochat/config/ConfigManager.java | 34 ++ .../loki/lochat/core/PluginInitializer.java | 2 +- .../core/filter/filters/SwearFilter.java | 59 ++- .../loki/lochat/gradient/GradientModule.java | 10 + .../gradient/service/PrefixService.java | 54 ++- .../lochat/listener/PlayerEventListener.java | 128 ++++++- .../lochat/renderer/EnhancedChatRenderer.java | 20 +- src/main/resources/badwords.txt | 58 +++ src/main/resources/config.yml | 255 +++++++++++-- src/main/resources/config/appearance.yml | 7 +- src/main/resources/config/discord.yml | 26 +- src/main/resources/config/filters.yml | 329 +++------------- src/main/resources/messages.yml | 108 +++--- 28 files changed, 1152 insertions(+), 1364 deletions(-) delete mode 100644 CODE_REVIEW_RU.md delete mode 100644 REFACTORING_SERVICES.md create mode 100644 bin/main/badwords.txt create mode 100644 src/main/resources/badwords.txt diff --git a/CODE_REVIEW_RU.md b/CODE_REVIEW_RU.md deleted file mode 100644 index 326900b..0000000 --- a/CODE_REVIEW_RU.md +++ /dev/null @@ -1,356 +0,0 @@ -# 🔍 Code Review: Проблемы архитектуры (SOLID, DRY, KISS) - -## 🔴 Критические проблемы - -### 1. DRY: Дублирование кода в конфигах -**Файлы:** `AppearanceConfig.java`, `MessagesConfig.java`, `FiltersConfig.java` - -**Проблема:** -- Каждый конфиг содержит 30-50 геттеров с одинаковой структурой -- ~300 строк повторяющегося кода -- При изменении логики нужно править в 3 местах - -**Решение:** -```java -// Создать базовый класс с generic методами -public abstract class BaseConfig { - protected String getString(String path, String def) { ... } - protected int getInt(String path, int def) { ... } - protected boolean getBoolean(String path, boolean def) { ... } -} - -// Использовать в конфигах -public class MessagesConfig extends BaseConfig { - public String getNoPermission() { - return getString("general.no_permission", "&#CF6679Нет прав!"); - } -} -``` - ---- - -### 2. SRP: ConfigManager делает слишком много -**Файл:** `ConfigManager.java` - -**Проблема:** -- Управляет 5 конфигами -- Содержит логику обновления версий -- Имеет 30+ геттеров для настроек -- Нарушает Single Responsibility Principle - -**Решение:** -```java -// Разделить на отдельные классы -class ConfigManager { - private final AppearanceConfig appearance; - private final MessagesConfig messages; - // Только управление конфигами -} - -class ConfigUpdater { - // Логика обновления версий - void updateConfig(FileConfiguration config) { ... } -} -``` - ---- - -### 3. KISS: ColorConverter неэффективен -**Файл:** `ColorConverter.java` - -**Проблема:** -```java -private static String convertAmpersandColors(String message) { - return message - .replaceAll("&0", "") - .replaceAll("&1", "") - // ... 16 вызовов replaceAll -} -``` -- Каждый `replaceAll` компилирует regex -- O(n * 16) сложность -- Дублирование для `§` и `&` - -**Решение:** -```java -private static final Map COLOR_MAP = Map.ofEntries( - Map.entry("&0", ""), - Map.entry("§0", ""), - Map.entry("&1", ""), - Map.entry("§1", "") - // ... -); - -private static String convertColors(String message) { - for (Map.Entry entry : COLOR_MAP.entrySet()) { - message = message.replace(entry.getKey(), entry.getValue()); - } - return message; -} -``` - ---- - -### 4. DRY: Дублирование проверок permissions -**Файл:** `AdvancedMessageFilter.java` - -**Проблема:** -```java -if (!player.hasPermission("lochat.bypass.swear") && !player.hasPermission("lochat.bypass.filter")) -if (!player.hasPermission("lochat.bypass.flood") && !player.hasPermission("lochat.bypass.filter")) -if (!player.hasPermission("lochat.bypass.spam") && !player.hasPermission("lochat.bypass.filter")) -``` - -**Решение:** -```java -private boolean canBypassFilter(Player player, String filterType) { - return player.hasPermission("lochat.bypass." + filterType) - || player.hasPermission("lochat.bypass.filter"); -} - -// Использование -if (!canBypassFilter(player, "swear")) { - FilterResult swear = swearFilter.filter(player, message); - // ... -} -``` - ---- - -### 5. OCP: EnhancedChatRenderer не расширяем -**Файл:** `EnhancedChatRenderer.java` - -**Проблема:** -```java -switch (placeholder) { - case "emoji" -> result = result.append(emoji); - case "prefix" -> result = result.append(prefix); - // Добавление нового плейсхолдера требует изменения кода -} -``` - -**Решение:** -```java -// Strategy pattern для плейсхолдеров -interface PlaceholderResolver { - Component resolve(Player player, boolean isGlobal); -} - -Map resolvers = Map.of( - "emoji", (p, g) -> buildEmojiComponent(g), - "prefix", (p, g) -> buildPrefix(g), - "player", (p, g) -> buildPlayerComponent(p) -); - -// Использование -Component resolved = resolvers.getOrDefault(placeholder, - (p, g) -> Component.text("{" + placeholder + "}")) - .resolve(player, isGlobal); -``` - ---- - -### 6. ISP: MuteServiceImpl не реализован полностью -**Файл:** `MuteServiceImpl.java` - -**Проблема:** -```java -@Override -public void save() { - // TODO: Implement save to file -} - -private void load() { - // TODO: Implement load from file -} -``` - -**Решение:** -- Либо реализовать методы -- Либо разделить интерфейс на `MuteService` и `PersistentMuteService` - ---- - -### 7. Отсутствует зависимость VoiceChat -**Файл:** `MuteServiceImpl.java` - -**Проблема:** -```java -import de.maxhenkel.voicechat.api.BukkitVoicechatService; // ❌ Не найден -``` - -**Решение:** -```gradle -// build.gradle.kts -dependencies { - compileOnly("de.maxhenkel.voicechat:voicechat-api:2.5.0") -} -``` - -Или сделать опциональной зависимостью: -```java -private void initVoiceChat() { - try { - Class.forName("de.maxhenkel.voicechat.api.BukkitVoicechatService"); - // Инициализация - } catch (ClassNotFoundException e) { - plugin.getLogger().info("VoiceChat not found, voice mute disabled"); - } -} -``` - ---- - -### 8. SRP: ChatEventListener слишком сложный -**Файл:** `ChatEventListener.java` - -**Проблема:** -- Метод `onChat` содержит 80+ строк -- Обрабатывает: режим чата, фильтрацию, радиус, Discord, статистику -- Нарушает Single Responsibility - -**Решение:** -```java -class ChatEventListener { - private final ChatModeHandler modeHandler; - private final MessageFilterHandler filterHandler; - private final LocalChatHandler localHandler; - private final DiscordHandler discordHandler; - private final StatsHandler statsHandler; - - @EventHandler - public void onChat(AsyncChatEvent event) { - Player sender = event.getPlayer(); - String message = extractMessage(event); - - // Делегируем обработку - ChatMode mode = modeHandler.determineMode(message); - if (!filterHandler.filter(sender, message)) { - event.setCancelled(true); - return; - } - - if (mode == ChatMode.LOCAL) { - localHandler.handleLocal(event, sender); - } - - discordHandler.sendToDiscord(sender, message, mode); - statsHandler.recordMessage(sender, mode); - } -} -``` - ---- - -### 9. DRY: Дублирование конвертации цветов -**Файл:** `ColorConverter.java` - -**Проблема:** -```java -private static String convertSectionColors(String message) { ... } -private static String convertAmpersandColors(String message) { ... } -``` -Идентичная логика, только разные символы. - -**Решение:** -```java -private static String convertColors(String message, char symbol) { - return message - .replaceAll(symbol + "0", "") - .replaceAll(symbol + "1", "") - // ... -} - -public static String convertLegacyFormats(String message) { - message = convertColors(message, '§'); - message = convertColors(message, '&'); - return message; -} -``` - ---- - -### 10. DIP: Прямая зависимость от LoChat -**Файлы:** `EnhancedChatRenderer.java`, `ChatEventListener.java`, и др. - -**Проблема:** -```java -com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; -``` -Нарушает Dependency Inversion Principle. - -**Решение:** -```java -// Создать интерфейс -interface ChatPlugin { - ConfigManager getConfigManager(); - GradientModule getGradientModule(); - DiscordIntegration getDiscordIntegration(); -} - -// LoChat реализует интерфейс -class LoChat extends JavaPlugin implements ChatPlugin { ... } - -// Использование -class EnhancedChatRenderer { - private final ChatPlugin plugin; - - public EnhancedChatRenderer(ChatPlugin plugin, boolean isGlobal) { - this.plugin = plugin; - // Нет каста - } -} -``` - ---- - -## 📊 Статистика проблем - -| Принцип | Нарушений | Критичность | -|---------|-----------|-------------| -| DRY | 4 | 🔴 Высокая | -| SRP | 3 | 🔴 Высокая | -| KISS | 2 | 🟡 Средняя | -| OCP | 1 | 🟡 Средняя | -| DIP | 1 | 🟡 Средняя | -| ISP | 1 | 🟢 Низкая | - ---- - -## ✅ Что сделано хорошо - -1. **ServiceRegistry** - правильная реализация DI -2. **Разделение на модули** - gradient, integrations, core -3. **Использование интерфейсов** - api.service.* -4. **PluginInitializer/PluginShutdown** - разделение логики инициализации -5. **BaseConfig** - базовый класс для конфигов (уже есть!) - ---- - -## 🎯 Приоритеты исправления - -### Высокий приоритет -1. Исправить отсутствующую зависимость VoiceChat -2. Упростить ColorConverter (KISS) -3. Вынести проверки permissions в отдельный метод (DRY) - -### Средний приоритет -4. Разделить ChatEventListener на handler'ы (SRP) -5. Убрать касты к LoChat через интерфейс (DIP) -6. Сделать EnhancedChatRenderer расширяемым (OCP) - -### Низкий приоритет -7. Реализовать save/load в MuteServiceImpl (ISP) -8. Упростить ConfigManager (SRP) -9. Объединить методы конвертации цветов (DRY) - ---- - -## 📝 Итого - -Проект имеет хорошую базовую архитектуру (ServiceRegistry, модули, интерфейсы), но есть проблемы с: -- **Дублированием кода** (DRY) - особенно в конфигах и фильтрах -- **Слишком большими классами** (SRP) - ConfigManager, ChatEventListener -- **Неэффективными алгоритмами** (KISS) - ColorConverter - -Рекомендую начать с исправления критических проблем (VoiceChat, ColorConverter, permissions), затем постепенно рефакторить остальное. diff --git a/REFACTORING_SERVICES.md b/REFACTORING_SERVICES.md deleted file mode 100644 index f620025..0000000 --- a/REFACTORING_SERVICES.md +++ /dev/null @@ -1,287 +0,0 @@ -# 🔧 Рефакторинг сервисов - уменьшение количества файлов - -## Текущая ситуация -- **11 интерфейсов** в `api/service/` -- **12 реализаций** в `core/service/` -- **23 файла** только для сервисов -- Некоторые сервисы по 30 строк кода - -## Проблема -Слишком много мелких файлов, которые можно объединить по функциональности. - ---- - -## 🎯 План рефакторинга - -### 1. Объединить простые сервисы в один - -**Было:** -``` -PMService.java (28 строк) -SpyService.java (69 строк) -IgnoreService.java (97 строк) -``` - -**Станет:** -```java -// MessagingService.java - всё что связано с общением -public interface MessagingService { - // PM - void setLastConversation(UUID sender, UUID receiver); - UUID getLastConversation(UUID player); - - // Spy - boolean toggleSpy(UUID player); - boolean isSpying(UUID player); - void broadcastPM(Player sender, Player receiver, String message); - - // Ignore - void addIgnore(UUID player, UUID target); - void removeIgnore(UUID player, UUID target); - boolean isIgnoring(UUID player, UUID target); - Set getIgnoreList(UUID player); -} -``` - -**Экономия:** 3 интерфейса + 3 реализации = **6 файлов → 2 файла** - ---- - -### 2. Объединить Mute + Punishment - -**Было:** -``` -MuteService.java + MuteServiceImpl.java (212 строк) -PunishmentService.java + PunishmentServiceImpl.java (173 строки) -MuteHistoryManager.java (77 строк) -``` - -**Станет:** -```java -// ModerationService.java - всё что связано с модерацией -public interface ModerationService { - // Mutes - void mute(UUID uuid, String name, long duration, String reason, String by); - boolean unmute(UUID uuid, String by); - boolean isMuted(UUID uuid); - MuteData getMuteData(UUID uuid); - - // Bans - void ban(UUID uuid, String name, long duration, String reason, String by); - boolean unban(UUID uuid, String by); - boolean isBanned(UUID uuid); - BanData getBanData(UUID uuid); - - // Warns - void warn(UUID uuid, String name, String reason, String by); - List getWarns(UUID uuid); - - // History - List getHistory(UUID uuid); - List getHistoryByModerator(String name); -} -``` - -**Экономия:** 3 интерфейса + 3 реализации = **6 файлов → 2 файла** - ---- - -### 3. Объединить Cooldown + PlayerData - -**Было:** -``` -CooldownService.java + CooldownServiceImpl.java (40 строк) -PlayerDataService.java + PlayerDataServiceImpl.java (102 строки) -``` - -**Станет:** -```java -// PlayerService.java - всё что связано с данными игрока -public interface PlayerService { - // Cooldowns - boolean hasCooldown(UUID player, String type); - void setCooldown(UUID player, String type, long duration); - long getRemainingCooldown(UUID player, String type); - void clearCooldowns(UUID player); - - // Statistics - void recordMessage(UUID player, String chatType); - PlayerStats getStats(UUID player); - void saveAll(); -} -``` - -**Экономия:** 2 интерфейса + 2 реализации = **4 файла → 2 файла** - ---- - -### 4. Оставить как есть (нормальный размер) - -``` -ChatService.java + ChatServiceImpl.java (114 строк) ✅ -MessageService.java + MessageServiceImpl.java (36 строк) ✅ -MentionService.java + MentionServiceImpl.java (104 строки) ✅ -NickService.java + NickServiceImpl.java (155 строк) ✅ -``` - -Эти сервисы имеют чёткую ответственность и нормальный размер. - ---- - -## 📊 Результат рефакторинга - -| До | После | Экономия | -|----|-------|----------| -| 11 интерфейсов | 7 интерфейсов | -4 | -| 12 реализаций | 8 реализаций | -4 | -| **23 файла** | **15 файлов** | **-8 файлов** | - ---- - -## 🔨 Пошаговая реализация - -### Шаг 1: Создать MessagingService - -```java -// api/service/MessagingService.java -public interface MessagingService { - // PM - void setLastConversation(UUID sender, UUID receiver); - UUID getLastConversation(UUID player); - - // Spy - boolean toggleSpy(UUID player); - boolean isSpying(UUID player); - void broadcastPM(Player sender, Player receiver, String message); - void sendToSpies(Player sender, Component message, boolean isGlobal); - void removeSpy(UUID player); - - // Ignore - void addIgnore(UUID player, UUID target); - void removeIgnore(UUID player, UUID target); - boolean isIgnoring(UUID player, UUID target); - Set getIgnoreList(UUID player); - void save(); -} - -// core/service/MessagingServiceImpl.java -public class MessagingServiceImpl implements MessagingService { - private final JavaPlugin plugin; - private final MessageConfig messageConfig; - - // PM state - private final Map lastConversation = new ConcurrentHashMap<>(); - - // Spy state - private final Set spyEnabled = ConcurrentHashMap.newKeySet(); - - // Ignore state - private final Map> ignoreMap = new ConcurrentHashMap<>(); - - public MessagingServiceImpl(JavaPlugin plugin, MessageConfig messageConfig) { - this.plugin = plugin; - this.messageConfig = messageConfig; - loadIgnoreData(); - } - - // Реализация всех методов... -} -``` - -### Шаг 2: Обновить ServiceRegistry - -```java -// Было -register(PMService.class, new PMServiceImpl()); -register(SpyService.class, new SpyServiceImpl(messageConfig)); -register(IgnoreService.class, new IgnoreServiceImpl(plugin)); - -// Стало -MessagingService messaging = new MessagingServiceImpl(plugin, messageConfig); -register(MessagingService.class, messaging); - -// Для обратной совместимости (опционально) -register(PMService.class, messaging); -register(SpyService.class, messaging); -register(IgnoreService.class, messaging); -``` - -### Шаг 3: Удалить старые файлы - -```bash -# Удалить интерфейсы -rm src/main/java/com/loki/lochat/api/service/PMService.java -rm src/main/java/com/loki/lochat/api/service/SpyService.java -rm src/main/java/com/loki/lochat/api/service/IgnoreService.java - -# Удалить реализации -rm src/main/java/com/loki/lochat/core/service/PMServiceImpl.java -rm src/main/java/com/loki/lochat/core/service/SpyServiceImpl.java -rm src/main/java/com/loki/lochat/core/service/IgnoreServiceImpl.java -``` - ---- - -## ⚠️ Альтернативный подход (если не хочешь ломать API) - -Можно оставить старые интерфейсы, но сделать их делегатами: - -```java -// Старый интерфейс остаётся -public interface PMService { - void setLastConversation(UUID sender, UUID receiver); - UUID getLastConversation(UUID player); -} - -// Но реализация делегирует в MessagingService -public class PMServiceAdapter implements PMService { - private final MessagingService messaging; - - public PMServiceAdapter(MessagingService messaging) { - this.messaging = messaging; - } - - @Override - public void setLastConversation(UUID sender, UUID receiver) { - messaging.setLastConversation(sender, receiver); - } - - @Override - public UUID getLastConversation(UUID player) { - return messaging.getLastConversation(player); - } -} -``` - -Это позволит: -- Сохранить обратную совместимость -- Уменьшить количество реализаций (3 → 1) -- Постепенно мигрировать код на новый API - ---- - -## 🎯 Рекомендация - -**Начни с MessagingService** - это самое простое объединение: -1. PM, Spy, Ignore логически связаны (общение между игроками) -2. Все три сервиса маленькие (28-97 строк) -3. Объединение даст ~200 строк - нормальный размер - -**Потом ModerationService** - это сложнее: -1. Mute + Punishment + History = ~460 строк -2. Нужно будет рефакторить внутреннюю структуру -3. Но логически это одна область (модерация) - -**PlayerService оставь на потом** - там всё норм. - ---- - -## 📝 Итого - -Объединение сервисов: -- ✅ Уменьшит количество файлов с 23 до 15 -- ✅ Упростит навигацию по коду -- ✅ Сгруппирует связанную функциональность -- ⚠️ Потребует обновления кода, который использует старые сервисы - -Начни с MessagingService - это быстро и безопасно. diff --git a/bin/main/badwords.txt b/bin/main/badwords.txt new file mode 100644 index 0000000..734afd5 --- /dev/null +++ b/bin/main/badwords.txt @@ -0,0 +1,58 @@ +# Список запрещенных слов (по одному на строку) +# Строки начинающиеся с # игнорируются +# Фильтр автоматически ловит замены букв (х->x, о->o и т.д.) + +# Основной мат +хуй +хуя +хуе +хую +хуем +хуи +пизда +пизде +пизду +пиздой +пизды +ебать +ебал +ебет +ебут +ебала +ебали +ебаный +ебанный +ебучий +ебучая +ебучее +блять +блядь +бляди +блядей +сука +суки +сучка +сучки +пидор +пидора +пидоры +пидорас +пидорасы +гандон +гандоны +мудак +мудаки +мудила +мудилы +долбоеб +долбоебы +уебан +уебаны +уебок +уебки +говно +говна +говном +дерьмо +дерьма +дерьмом diff --git a/bin/main/com/loki/lochat/config/ConfigManager.class b/bin/main/com/loki/lochat/config/ConfigManager.class index 02d07d725f9dbec35985dedd489896f3a19d5134..7a37db7b74d59311a886189bd7b210a50542697b 100644 GIT binary patch delta 1403 zcmaKr%TE(g6voeyhfPBuFh!9?ZNM~uaa4qM>bpFuytMd2@lmH?pw-eMQ({ch4B*Cu zFUl<|3lCB*sIftd|iE6+)V#4kc8iZJ}VZQ~roZUL)a1Bv0Ir&}y0s@f0341)fru z1qTskobCNSm&@(hTYY1S*o>XlRw!NVB}=m{ZP}r)Ew)Y(@3L*Wc8Y@;UHxeEgX>5< zm&CbPG}%dSmj@k-1&x$f5e}D7&c=c{#9;*g|A>nI8SKl*b_gw97SF&kAWe%4k$66_ zvC^bN{FL5d!RrV;Ig}Io$Cb#OQ7DjmsigY5(Or8B?;OP2s?c=2W&&ZgLw(!SqAoTq zSKqYYq*}}Q@u51wbyWut?CKujh&g z1I(@?FiLmC)9{yen(0}9s%)*kYXgpP*v(zL3axs-CerwVZ(j;X{E;p_y`}vk$-Zrdu;#! delta 108 zcmbOnc)*S8)W2Q(7#J8F8SOW6eHNUoCThxN#Aw3KXgoPlRGZOsalGUS diff --git a/bin/main/com/loki/lochat/core/PluginInitializer.class b/bin/main/com/loki/lochat/core/PluginInitializer.class index 39888f28fc13ceae1fa9c9ccb4fabba44c153a77..3fd93d8ef0b70a68078fd58885291a7dc2144926 100644 GIT binary patch delta 187 zcmXAhI}1T^7{-5x<8pGz?F?sBCRq(WfRaoGOcqLtOVU}vZSs#(Xt10*?tqG3Q;kRFxEXG9|a<+>ql diff --git a/bin/main/com/loki/lochat/core/filter/filters/SwearFilter.class b/bin/main/com/loki/lochat/core/filter/filters/SwearFilter.class index a62a6d49a4c04692f481f10d46b51e8c28f18d04..cb3a032f5c9f913bcb08cd4c2a361823d5868ca4 100644 GIT binary patch literal 7660 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 U{(SpVz=|kEP2*x#!f0y!59@&%P5=M^ diff --git a/bin/main/com/loki/lochat/gradient/GradientModule.class b/bin/main/com/loki/lochat/gradient/GradientModule.class index 486569bb5a2bfebcb27a498c321cbaa560691b0c..93344ea4d0df91f980156767811ca0d9e2713282 100644 GIT binary patch delta 247 zcmaEDJk?a-)W2Q(7#J8F8Q!xqFflTyPd>;ZIe8uj-$wIFeofBw)RN%Rw6x3$E(SM- zJ3I_`8ICeC2mwWXN|Un#Qj2nnA<~oA@k_Ei;9Vy%LLuo81GH~AZ8?W ze*@5bW(HXx$p@qv7?v?`F|1&aU|2cXM%;+`A;Y7|iju;UnI!}n*H7-3(4Oo7WNikr zL?=I#5Sv^Jn4dGe0IS>u6xlubyrekeD=_~%kiQ?Kg7Niac}X?K!(jHC$uW{T WjK?Q;O1d(h28po01=`KT@D2d3NlmZ- delta 212 zcmbPg`r26F)W2Q(7#J8F8D6t9FflTyPd>;ZIe8uj-$wIFercif)DoZ4E#{zs1NXwE3c-I~(&|hI^9(C50!il@MfHI{Aa7DD!=W2Vjv` zK#`STk%tVAz#{6Bf{bfHB9pI3h)qrb@-~8bf|6oF-Wz~UVPcR4l57l&KyoWs>vM(_ iu-1D(#XCVFj4vj$N$D`|oop=S%6JeY!u}Gh^A!MzlSC!} diff --git a/bin/main/com/loki/lochat/gradient/service/PrefixService.class b/bin/main/com/loki/lochat/gradient/service/PrefixService.class index 46b5885c886574d304790eddb8522f77503b14fa..7a2872e29441a58efb82c25d12e6c5f682a3a7fa 100644 GIT binary patch delta 1077 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 delta 858 zcmZvaOHUI~6vzK}`k0yamO^DHAheWH+fF;>QN;(KAfkvCiUoWjGLl&|VoPd8*+`5V zx8^SV7(%o)xG=6<_%)32OBmys7SV;7 zUiWyhRIHw7uoB{_b5O*jN&hhpKiU}_iR6lm4)I*dsGaDR(ZyhhXHLV#aU4D3hcv=N z95NIceGF;wLh2E2*6LHi(9fa5BV$0UIWny$IK1F?KEdHb%R7&=ov9}=D&rJGT;!Ed z%Urown%TBWRgTj*BML4m0s(_-DP`SY9R zj#XiBmrILJidFH}85Jd^MZIG8pKbP!d%U=aIgV?%F6v55(J;@kfV}vo=wo5r;3y!^ z5U5y>%G=g-xwLIn9-Gx-x#UF{%N#3qS#6dJTRR@y!W|j6#jHBoe%G#fH#RM^@&_g< z7L=sWJl$en%QC0Ia3cuU5TPL2 z2%q1P>1Nu!wqD;sdI#!#ROIx#F;~4jyUdN NjgHQ6pO_@9{{|Wbh6?}y diff --git a/bin/main/com/loki/lochat/renderer/EnhancedChatRenderer.class b/bin/main/com/loki/lochat/renderer/EnhancedChatRenderer.class index 47af895030523170c04ec8e2b979aa4b693189a5..3635f6059d3b021fc128f8b57cd9118d1c08254f 100644 GIT binary patch delta 4347 zcmb7Hd3;k<7XD6?=Dj4hDKv!g($ZF1q$>qkEhs`+%2H$p0hIuut)xv&QWjZ)$fAf) zxF8kKW6ev-aF^s`|kbDx!?J2 z-nmid$J$>0?Z{C8ed(hEwoKLCa%xS5Z~6$IXRbFex@N|Via9nA0s=vqqqeow2+YE4 zIZSm^SGh$v<;5iTTI(peS*?|iYFUS7S$mRARL35dyKHIZ2MF!t7OkC0!D{)fEjMiq zp~W4ZS)M|lr>d-QbSO|!Rc6OZNP%@&FMHa1ByS+tC(p~CblZYk^KvFHu%QkQE7&MI z3#S<235C3YDuG9^N$#-sR_pMnz+>1VzqI$WY{L!(+j(GW%1+c1;)5YiAVe@_=M1)? z7P|y?V~;Es9n_WBEAS-t$qk|;WhD*>9K=loi>JEUTQ%K=Z8$9O6e98$(XLZ18Uz~k z8P#%6u)Nqm-K*jmLP9jy+p7a!BU!b0PT(kx$zchZxzAti4;sl-u|mZ%6-!mjSFt>a zGk;p(3|^MY5(f0!hF1k%)7LOv0r0xOIlMtI�)HZUiw=58^F>xAkEbpSP+kRIWF@ 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 Tc<8(IKAnd~8}J2vXtDkq(1hmA delta 4095 zcmaJ^33yXg7XD9?=DjAj3k_-eS}3(tXlYB8-Gaz&Sz7i*_CP}^O`DRYYz4z6g0ct~ zP^z+HRa6!wv?wkpxGOr&xXg@#GdhgRjI%k8g3P%uP2u~-Z}KHC=bXF!_uT(K@4ZhZ zo*(CUT)O6V~fCj*eW+Td-swA=k)rVX){;M z$jhC+!hs#QU&Bs9iaD?@Sm7%Sc+0&@3deYYL2sa1-~sHC-#h#1JMf^uLwHygi9xsS z!9ERpe_>dOCpdS$m%;DH0YXe|&=Uv}tU0;E9oUFN0*|0kHjB>sdORlZI1bB8qBwm$ zo)mZr0|<6cO^vs@%z-^PDsT+P<;b|Kn>OMZffH&*eZHr5{$PKZSI0?0T#e7O)Ek&p z6Y$QfSfW;F7C41x<->6uQ`YHNqhqy>r8?FoaeB`Pyo^`mS8+oJ?ZG*LSJi~pmepPt zcmr<|Z1XCr%S^;m)qHOYyra6=ectNw;CvN2uQqX9E{w0VeJt>aybzyq)9(a+kLe7k z+#4L_pX>1rt@1CZP`y7BxS;0MDr$#T)dZJn_?*y*&&`kn6581RDDVZol#>&N*e~HL z4S$yV67r3&)reRgQSF)I^Kv#W;ah>f;IHz_gxl=7C zMjD~})ivl($G@_LxqB zI%6)=yR4#Cv7*#d~Kogme43ZO;Gdy?HHI0v+v%oty*jJ~?tc?1I7+BOB=?+0tX`1{vWxRU^ zZ?Jx`Kj14{p*B5B&}{O^fvMT$b17dV=6a$(P+mBvuClTsSU6dA)M*|wzP^U*hdE}x zpb8}dm8dZsR4Y^p^5Hng7I}Pi-qG`PswOz<%Q=r#EKhS>ji7}TkZ+}Su?K09Ms@OX zYHHAd+XW-V{uJFu7%M&L>8j?+w{6+I2h~;h%De&dP__w@Y7$#*#WF9KSlS_IC$@4n zs=bSS71drf;RAwVX_s7)*2DfFJ*?3~OsOo{FWnI%XgBSVm(n~;tbKy^(*Zd)y^_wz z)9KSYZluG4o@glad6fr>w%TNy+ z+iS6$V=?H=<5%zqJNjcd?nDS0CZIc3Vig0LiUO?WezBO%J=d`1z&zZ*qnCu>L>1C- z7Y7A`aA6%=al9;$3+rX{)Uf_4QlM)f4I|kzzxVK!*O&qf(YTIV)a{t@x&X^TM#d?i zvL5p(=^DobBImk^hIC-F|!<|BsJ3vvL^!&+hXz!vd8sKllPIjI%otj2$VCQr=q?(Evv7Ei<&8Eyo_}O{EbS5`q4m&SK zTP7NMehb1zOf&R?R;QHD+Scj~U@xZC>&BkF)yrm2i+Uj>vnx*FCE)tqhjF%-nU>cY ze*VzyQVZ3`?PyHIDCc(`+9>R1O%4azYZBfhzH-b-hB(gCIeK8t?F$V8r zEIz|He1q`_{fG&qVB9!Dkv}C|inK%uu!z zx0s=9DQ+=C*}Q0@F6cJOkNfv=_+qZn2g*QWn_ext=AnI_At84D=21y@>jHc;`s9;l756e zi$P0E_Kl&B8liE9e_N6t#;3aw%f#hKK4UMlx$EM;u%4tzWu8SG((VDLO)^r*CtgF)16?SrxfR&hR zeA?6!iO*n&zmUs#GnbSgKj@S*s7(Y;+iM6ZB@#+2&F@kWruI#A!?g@XP_AZ_YZ&EP zUg<8`zjI1R6;E}9?OQQ9D4Ws!9{^?ntce^Z7r+u`vCcHl&1c-aM}Tg9$lHPxE# z5_Ut&RP&upwAKvW%^`l3iVP^yj2J^>E;iFf3nshmjFb;a2|*G&EyHzza=Hc)96Xf44k-A8#-m`G?(l3 zvz(f!ChSgY+)&oy6|=f&bYc+ySjErrVir10>h(PhHhP8MsLq<#SFgLiPH%F168C(I Q-Vt;j25rJcde0vFU**@I?*IS* diff --git a/bin/main/config.yml b/bin/main/config.yml index aede1c2..f6eb1e2 100644 --- a/bin/main/config.yml +++ b/bin/main/config.yml @@ -1,58 +1,143 @@ -# LoChat Configuration v1.5.5 -config-version: 4 +# ╔══════════════════════════════════════════════════════════════════════════════╗ +# ║ LoChat Configuration ║ +# ║ Единый конфиг плагина ║ +# ╚══════════════════════════════════════════════════════════════════════════════╝ + +config-version: 5 prefix: "�D4FF[LoChat]" -# Базовые настройки чата -# Детальные настройки (префиксы, цвета, форматы) в config/*.yml - +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Режимы чата │ +# └─────────────────────────────────────────────────────────────────────────────┘ chat: - # Режим чата: - # - Оба включены = локальный (по умолчанию) + глобальный (с "!") - # - Только global = единый чат без префиксов для всех + # Глобальный чат (все видят, активируется через "!") global: enabled: true cooldown: 3 + + # Локальный чат (только в радиусе) local: enabled: true cooldown: 1 + radius: 100 + + # Личные сообщения pm: enabled: true cooldown: 2 + + # Лимиты сообщений + min_message_length: 1 + max_message_length: 2000 + clear_lines: 100 -# Hub команда -hub_command: - enabled: true - server: "hub" +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Внешний вид чата (префиксы, форматы, цвета) │ +# └─────────────────────────────────────────────────────────────────────────────┘ +appearance: + # Префиксы чата (GLOBAL / LOCAL) + prefixes: + global: + emoji: "✦" + text: "GLOBAL" + colors: ["#B798A8", "#7858E9"] + separator: + text: " ▶ " + color: "#9878C9" + message-color: "#E8E0F0" + + local: + emoji: "◆" + text: "LOCAL" + colors: ["#9878C9", "#7858E9"] + separator: + text: " ▶ " + color: "#8B6BD6" + message-color: "#E8E0F0" + + # Формат сообщений + # {emoji} {prefix} {separator} {player_prefix} {player} {player_suffix} {message} + chat-format: + global: "{emoji} {prefix} {separator} {player_prefix}{player}{player_suffix}: {message}" + local: "{emoji} {prefix} {separator} {player_prefix}{player}{player_suffix}: {message}" + + # Hover при наведении на ник + hover: + enabled: true + format: + - "ẲE9▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬" + - "&#B798A8✦ &f{player}" + - "" + - "⚖C9⏱ &fПинг: ẲE9{ping}ms" + - "⚖C9❤ &fЗдоровье: ẲE9{health}/20" + - "⚖C9🍖 &fГолод: ẲE9{food}/20" + - "" + - "ẲE9▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬" + - "&#B798A8✦ &fНажми чтобы написать" + + # Эмодзи (текстовые замены) + emojis: + ":heart:": "❤" + ":star:": "★" + ":diamond:": "♦" + ":check:": "✓" + ":cross:": "✗" + ":arrow:": "→" + ":smile:": ":)" + ":sad:": ":(" + ":shrug:": "¯\\_(ツ)_/¯" + + # Цвета системных сообщений + colors: + nobody_heard: "#9878C9" + error: "#CF6679" + success: "#9878C9" + warning: "#B798A8" + info: "#A485BC" -# Градиентные ники -lopreff: +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Градиентные ники и префиксы │ +# └─────────────────────────────────────────────────────────────────────────────┘ +gradient: enabled: true use-gradient-name: true use-custom-prefix: true - -display: - update-display-name: true - update-tab-list: true - -# Градиенты (детали в config/gradient.yml) -gradient: - storage-type: "file" + + # Настройки отображения + display: + update-display-name: true + update-tab-list: true + + # Лимиты min-colors: 1 max-colors: 7 + max-prefix-length: 16 + + # Цены (PlayerPoints) price-per-color: 50 prefix-price: 500 prefix-one-time-purchase: true use-permission-instead-of-cost: false - max-prefix-length: 16 + + # Кулдауны (секунды) color-cooldown: 60 prefix-cooldown: 120 + + # Форматирование prefix-format: "[{prefix}] " gradient-on-prefix: true gradient-on-luckperms-prefix: true + gradient-on-luckperms-suffix: true continuous-gradient: true use-legacy-rgb-format: true + + # Хранилище (file / sqlite) + storage-type: "file" + sqlite-file: "gradient-data.db" -# RP команды (детали в config/rp.yml) +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ RP команды (/me, /do, /try, /roll) │ +# └─────────────────────────────────────────────────────────────────────────────┘ rp: radius: 100 cooldowns: @@ -61,6 +146,126 @@ rp: do: 3 roll: 2 -# Модерация: после /lmute выполнить команду от консоли (SimpleVoiceChat: voice mute {player}) +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Фильтры чата │ +# └─────────────────────────────────────────────────────────────────────────────┘ +filters: + # Мат-фильтр + swear: + enabled: true + replace: false + replacement: "***" + words: + - "хуй" + - "пизда" + - "ебать" + + # URL фильтр + url: + enabled: true + replacement: "[ссылка скрыта]" + + # IP фильтр + ip: + enabled: true + replacement: "[IP скрыт]" + + # Скрытые URL (discord.gg и т.д.) + hidden-urls: + enabled: true + patterns: + - "discord\\.gg" + - "dsc\\.gg" + + # Анти-спам + spam: + enabled: true + max-repeating-chars: 5 + + # Анти-флуд + flood: + enabled: true + max-same-messages: 3 + + # Капс + caps: + enabled: true + max-caps-percent: 70 + min-length: 5 + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Модерация (муты, баны, варны) │ +# └─────────────────────────────────────────────────────────────────────────────┘ moderation: + # Дефолтные значения + default-mute-duration: "7d" + default-mute-reason: "Без причины" + default-ban-reason: "Без причины" + + # Команда после мута (для голосового чата) voice-mute-console-command: "" + + # Сообщение при кике забаненного + ban-kick-message: "&#CF6679Вы забанены! &#B798A8Причина: ẲE9{reason}" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Звуки │ +# └─────────────────────────────────────────────────────────────────────────────┘ +sounds: + # Упоминание в чате + mention: + enabled: true + sound: "BLOCK_NOTE_BLOCK_PLING" + volume: 1.0 + pitch: 1.5 + + # Личное сообщение + pm: + enabled: true + sound: "ENTITY_EXPERIENCE_ORB_PICKUP" + volume: 0.8 + pitch: 1.2 + + # Команды + command: + enabled: false + sound: "UI_BUTTON_CLICK" + volume: 0.5 + pitch: 1.0 + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Сообщения входа/выхода/смерти │ +# └─────────────────────────────────────────────────────────────────────────────┘ +messages: + # Сообщение при входе + # {player} - ник игрока + join: + enabled: true + format: "⚖C9✦ &#B798A8{player} ẲE9зашел на сервер" + + # Сообщение при выходе + quit: + enabled: true + format: "⚖C9✦ &#B798A8{player} ẲE9вышел с сервера" + + # Сообщение при смерти + # {player} - ник игрока + # {killer} - ник убийцы (если есть) + # {weapon} - название оружия (если есть) + # {death_message} - стандартное сообщение смерти + death: + enabled: true + # Убийство игроком + player_kill: "&#CF6679☠ &#B798A8{player} ẲE9был убит игроком &#B798A8{killer} ⚖C9({weapon})" + # Смерть от моба + mob_kill: "&#CF6679☠ &#B798A8{player} ẲE9был убит мобом &#B798A8{killer}" + # Обычная смерть + default: "&#CF6679☠ &#B798A8{player} ẲE9{death_message}" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Прочее │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Hub команда +hub_command: + enabled: true + server: "hub" diff --git a/bin/main/config/appearance.yml b/bin/main/config/appearance.yml index 4626e44..a76805e 100644 --- a/bin/main/config/appearance.yml +++ b/bin/main/config/appearance.yml @@ -44,16 +44,17 @@ prefixes: # {separator} — separator (e.g. ▶) # {player_prefix} — LuckPerms / gradient prefix from LoPreff # {player} — display name + hover (click to /msg) +# {player_suffix} — LuckPerms / gradient suffix from LoPreff # {message} — message body # # Examples: # Default: {emoji} {prefix} {separator} {player} : {message} # Minimal: {player_prefix} {player} ▶ {message} -# Custom: ✦ {player_prefix} {player} ▶ {message} +# Custom: ✦ {player_prefix} {player} {player_suffix} ▶ {message} chat-format: - global: "{emoji} {prefix} {separator} {player} : {message}" - local: "{emoji} {prefix} {separator} {player} : {message}" + global: "{emoji} {prefix} {separator} {player_prefix}{player}{player_suffix} : {message}" + local: "{emoji} {prefix} {separator} {player_prefix}{player}{player_suffix} : {message}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Hover shown when pointing at the player name in chat │ diff --git a/bin/main/config/discord.yml b/bin/main/config/discord.yml index d0ca13b..1070b6a 100644 --- a/bin/main/config/discord.yml +++ b/bin/main/config/discord.yml @@ -40,6 +40,7 @@ events: enabled: true # Настройки входа на сервер + # {player} - ник игрока join: enabled: true title: "🟢 Игрок зашел на сервер" @@ -47,7 +48,8 @@ events: color: "00FF00" thumbnail: "https://mc-heads.net/avatar/{player}/64" - # Настройки выхода с сервера + # Настройки выхода с сервера + # {player} - ник игрока quit: enabled: true title: "🔴 Игрок покинул сервер" @@ -56,11 +58,27 @@ events: thumbnail: "https://mc-heads.net/avatar/{player}/64" # Настройки смерти игрока + # {player} - ник игрока + # {killer} - ник убийцы (если есть) + # {weapon} - название оружия (если есть) + # {death_message} - стандартное сообщение смерти death: enabled: true - title: "💀 Смерть игрока" - description: "**{player}** {death_message}" - color: "800080" + # Убийство игроком + player_kill: + title: "⚔️ PvP убийство" + description: "**{player}** был убит игроком **{killer}** с помощью **{weapon}**" + color: "FF0000" + # Смерть от моба + mob_kill: + title: "💀 Смерть от моба" + description: "**{player}** был убит мобом **{killer}**" + color: "800080" + # Обычная смерть + default: + title: "💀 Смерть игрока" + description: "**{player}** {death_message}" + color: "800080" thumbnail: "https://mc-heads.net/avatar/{player}/64" # Настройки фильтрации diff --git a/bin/main/config/filters.yml b/bin/main/config/filters.yml index 5812333..029c0f0 100644 --- a/bin/main/config/filters.yml +++ b/bin/main/config/filters.yml @@ -1,286 +1,81 @@ # ╔══════════════════════════════════════════════════════════════════════════════╗ -# ║ LoChat Filters Configuration ║ -# ║ Конфигурация фильтров ║ -# ║ Версия: 1.5.5-1.20.1 ║ +# ║ LoChat — Фильтры чата ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР ЗАГЛАВНЫХ БУКВ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -caps: - # Включить фильтр заглавных букв - enabled: true - - # Максимальный процент заглавных букв (0-100) - max-percent: 70 - - # Минимальная длина сообщения для проверки - min-length: 5 - - # Автоматически переводить в нижний регистр - auto-lowercase: true - - # Действие при нарушении: block, warn, lowercase - action: "lowercase" - - # Сообщение при блокировке - block-message: "&#FF6B6BСлишком много заглавных букв!" - - # Сообщение-предупреждение - warn-message: "&#FFA726Не используйте слишком много заглавных букв" - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР РЕКЛАМЫ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -advertising: - # Включить фильтр рекламы - enabled: true - - # Блокировать скрытые URL (bit.ly, tinyurl и т.д.) - block-hidden-urls: true - - # Блокировать домены - block-domains: true - - # Блокировать IP адреса - block-ips: true - - # Действие при обнаружении: block, warn, replace - action: "block" - - # Сообщение при блокировке - block-message: "&#FF6B6BРеклама запрещена!" - - # Замена для заблокированного контента - replacement: "[РЕКЛАМА УДАЛЕНА]" - - # Белый список доменов (разрешенные) - whitelist: - - "youtube.com" - - "youtu.be" - - "discord.gg" - - "twitch.tv" - - "imgur.com" - - "gyazo.com" - - "prntscr.com" - - # Черный список доменов (запрещенные) - blacklist: - - "scam-site.com" - - "virus-site.ru" - - "fake-minecraft.net" - - # Регулярные выражения для поиска рекламы - patterns: - - "(?i)\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}(?::[0-9]{1,5})?\\b" # IP адреса - - "(?i)\\b[a-z0-9.-]+\\.(com|net|org|ru|de|uk)\\b" # Домены - - "(?i)\\b(server|сервер)\\s*[:-]?\\s*[a-z0-9.-]+\\b" # Серверы - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР IP АДРЕСОВ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -ip: - # Включить фильтр IP адресов - enabled: true - - # Блокировать сообщение или заменять IP - block: false - - # Замена для IP адресов - replacement: "[IP СКРЫТ]" - - # Сообщение при блокировке - block-message: "&#FF6B6BIP адреса запрещены!" - - # Белый список IP (разрешенные) - whitelist: - - "127.0.0.1" - - "localhost" - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР ПОВТОРОВ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -repeat: - # Включить фильтр повторяющихся символов - enabled: true - - # Максимальное количество повторов подряд - max-repeats: 3 - - # Действие: block, warn, replace - action: "replace" - - # Сообщение при блокировке - block-message: "&#FF6B6BНе повторяйте символы!" - - # Автоматически сокращать повторы - auto-reduce: true - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР СПАМА │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -spam: - # Включить антиспам - enabled: true - - # Максимальное количество одинаковых сообщений - max-similar-messages: 3 - - # Время для сброса счетчика (секунды) - reset-time: 60 - - # Процент схожести сообщений (0-100) - similarity-threshold: 80 - - # Действие при спаме: block, warn, mute - action: "warn" - - # Сообщение при блокировке - block-message: "&#FF6B6BНе спамьте!" - - # Время мута за спам (секунды) - mute-duration: 300 - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР МАТА │ +# │ Мат-фильтр │ # └─────────────────────────────────────────────────────────────────────────────┘ +# Использует badwords.txt для списка слов +# Автоматически ловит замены букв: х->x, о->o, е->e и т.д. +# Обход: lochat.bypass.swear swear: - # Включить фильтр мата enabled: true - - # Использовать внешний файл со словами - use-external-file: true - - # Файл со словами (относительно папки плагина) - words-file: "filter-words.json" - - # Действие: block, warn, replace, mute - action: "replace" - - # Символ для замены - replacement-char: "*" - - # Сообщение при блокировке - block-message: "&#FF6B6BМат запрещен!" - - # Время мута за мат (секунды) - mute-duration: 600 - - # Встроенный список слов (если не используется внешний файл) - words: - - "плохое_слово1" - - "плохое_слово2" - - # Проверять фрагменты слов - check-fragments: true - - # Игнорировать регистр - ignore-case: true - - # Проверять транслитерацию - check-transliteration: true + replace: false # true = заменять на ***, false = блокировать сообщение + replacement: "***" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР ФЛУДА │ +# │ URL фильтр │ # └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует ссылки в чате +# Обход: lochat.bypass.urlfilter + +url: + enabled: true + replacement: "[ссылка скрыта]" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ IP фильтр │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует IP адреса в чате +# Обход: lochat.bypass.ipfilter + +ip: + enabled: true + replacement: "[IP скрыт]" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Скрытые URL (discord.gg и т.д.) │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует популярные сокращенные ссылки +# Обход: lochat.bypass.hiddenurls + +hidden-urls: + enabled: true + patterns: + - "discord\\.gg" + - "dsc\\.gg" + - "bit\\.ly" + - "tinyurl\\.com" + - "goo\\.gl" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Анти-спам │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует повторяющиеся символы (ааааааа, хххххх) +# Обход: lochat.bypass.spam + +spam: + enabled: true + max-repeating-chars: 5 # Максимум повторений одного символа + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Анти-флуд │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует одинаковые сообщения подряд +# Обход: lochat.bypass.flood flood: - # Включить антифлуд enabled: true - - # Максимальное количество сообщений в период - max-messages: 5 - - # Период времени (секунды) - time-period: 10 - - # Действие при флуде: block, warn, mute - action: "warn" - - # Сообщение при блокировке - block-message: "&#FF6B6BНе флудите!" - - # Время мута за флуд (секунды) - mute-duration: 180 + max-same-messages: 3 # Сколько раз можно отправить одно и то же # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ДОПОЛНИТЕЛЬНЫЕ ФИЛЬТРЫ │ +# │ Капс-фильтр │ # └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует сообщения с большим количеством заглавных букв +# Обход: lochat.bypass.caps -additional: - # Фильтр эмодзи - emoji: - enabled: false - max-per-message: 5 - block-message: "&#FF6B6BСлишком много эмодзи!" - - # Фильтр длинных сообщений - length: - enabled: true - max-length: 256 - action: "truncate" # truncate или block - block-message: "&#FF6B6BСообщение слишком длинное!" - - # Фильтр пустых сообщений - empty: - enabled: true - block-message: "&#FF6B6BПустые сообщения запрещены!" - - # Фильтр только пробелов - whitespace: - enabled: true - block-message: "&#FF6B6BСообщение не может состоять только из пробелов!" - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ НАСТРОЙКИ ОБХОДА │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -bypass: - # Права для обхода фильтров - permissions: - caps: "lochat.bypass.caps" - advertising: "lochat.bypass.advertising" - ip: "lochat.bypass.ip" - repeat: "lochat.bypass.repeat" - spam: "lochat.bypass.spam" - swear: "lochat.bypass.swear" - flood: "lochat.bypass.flood" - all: "lochat.bypass.filter" - - # Группы, которые обходят все фильтры - groups: - - "admin" - - "moderator" - - # Миры, где фильтры отключены - worlds: - - "creative_world" - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ЛОГИРОВАНИЕ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -logging: - # Логировать заблокированные сообщения +caps: enabled: true - - # Файл для логов - file: "plugins/LoChat/logs/filter.log" - - # Формат лога - format: "[{time}] {player} ({filter}): {message}" - - # Логировать в консоль - console: true - - # Уведомлять администраторов - notify-admins: true - - # Право для получения уведомлений - notify-permission: "lochat.filter.notify" \ No newline at end of file + max-caps-percent: 70 # Максимум % заглавных букв + min-length: 5 # Минимальная длина для проверки diff --git a/bin/main/messages.yml b/bin/main/messages.yml index 452cc4d..4cd0418 100644 --- a/bin/main/messages.yml +++ b/bin/main/messages.yml @@ -1,16 +1,16 @@ # ╔══════════════════════════════════════════════════════════════════════════════╗ # ║ LoChat — messages.yml ║ -# ║ Default palette (Spacelegacy): #B798A8 (dusty rose) → #7858E9 (violet) ║ +# ║ Default palette (Spacelegacy): #7858E9 (dusty rose) → #7858E9 (violet) ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # # Color roles (reference when editing strings below): -# &#B798A8 — primary labels / body (dusty rose) +# ẲE9 — primary labels / body (dusty rose) # ⚖C9 — accents / separators (mid violet) # ẲE9 — emphasis / values (deep violet) # &f / #E8E0F0 — message body white # &#CF6679 — errors (muted red, fits the palette) -prefix: "&#B798A8[⚖C9LB6BD6oẲE9CⅵDChB6BD6a⚖C9t&#B798A8]" +prefix: "ẲE9[⚖C9LB6BD6oẲE9CⅵDChB6BD6a⚖C9tẲE9]" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Global chat (toggle messages, server-disabled notice) │ @@ -18,7 +18,7 @@ prefix: "&#B798A8[⚖C9LB6BD6oẲE9CⅵDChB6BD6a⚖C9t&#B798A8 global: toggled-on: "⚖C9Глобальный чат ẲE9включён" - toggled-off: "⚖C9Глобальный чат &#B798A8отключён" + toggled-off: "⚖C9Глобальный чат ẲE9отключён" disabled: "&#CF6679Глобальный чат отключён на этом сервере" # ┌─────────────────────────────────────────────────────────────────────────────┐ @@ -26,19 +26,19 @@ global: # └─────────────────────────────────────────────────────────────────────────────┘ local: - no-players: "&#B798A8Рядом нет игроков" - nobody-heard: "⚖C9Вас никто не услышал &#B798A8— рядом нет игроков" + no-players: "ẲE9Рядом нет игроков" + nobody-heard: "⚖C9Вас никто не услышал ẲE9— рядом нет игроков" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Private messages (/msg, /reply) │ # └─────────────────────────────────────────────────────────────────────────────┘ pm: - format-sent: "&#B798A8[⚖C9→&#B798A8] ⚖C9Вы &#B798A8→ {receiver_display}&#B798A8: &f{message}" - format-received: "&#B798A8[⚖C9←&#B798A8] {sender_display} &#B798A8→ ⚖C9Вы&#B798A8: &f{message}" + format-sent: "ẲE9[⚖C9→ẲE9] ⚖C9Вы ẲE9→ {receiver_display}ẲE9: &f{message}" + format-received: "ẲE9[⚖C9←ẲE9] {sender_display} ẲE9→ ⚖C9ВыẲE9: &f{message}" use-gradient-names: true - sent: "&#B798A8[⚖C9→&#B798A8] ⚖C9Вы &#B798A8→ &f{player}&#B798A8: &f{message}" - received: "&#B798A8[⚖C9←&#B798A8] &f{player} &#B798A8→ ⚖C9Вы&#B798A8: &f{message}" + sent: "ẲE9[⚖C9→ẲE9] ⚖C9Вы ẲE9→ &f{player}ẲE9: &f{message}" + received: "ẲE9[⚖C9←ẲE9] &f{player} ẲE9→ ⚖C9ВыẲE9: &f{message}" no-reply: "&#CF6679Некому ответить" ignored: "&#CF6679Этот игрок вас игнорирует" player-offline: "&#CF6679Игрок не в сети" @@ -51,12 +51,12 @@ pm: ignore: added: "⚖C9Игрок ẲE9{player} ⚖C9добавлен в игнор" removed: "⚖C9Игрок ẲE9{player} ⚖C9убран из игнора" - already-ignored: "&#B798A8Игрок ẲE9{player} &#B798A8уже в игноре" - not-ignored: "&#B798A8Игрок ẲE9{player} &#B798A8не в игноре" + already-ignored: "ẲE9Игрок ẲE9{player} ẲE9уже в игноре" + not-ignored: "ẲE9Игрок ẲE9{player} ẲE9не в игноре" self: "&#CF6679Нельзя игнорировать самого себя" - list-empty: "&#B798A8Ваш список игнора пуст" + list-empty: "ẲE9Ваш список игнора пуст" list-header: "ẲE9▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬" - list-footer: "&#B798A8Всего: ẲE9{count} &#B798A8игроков" + list-footer: "ẲE9Всего: ẲE9{count} ẲE9игроков" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Announcements │ @@ -71,9 +71,9 @@ announce: spy: enabled: "⚖C9Режим шпиона ẲE9включён" - disabled: "⚖C9Режим шпиона &#B798A8отключён" - format: "&#B798A8[⚖C9SPY&#B798A8] ẲE9{sender} &#B798A8→ ⚖C9{receiver}&#B798A8: &f{message}" - chat-format: "&#B798A8[⚖C9SPY&#B798A8] &#B798A8{type} ẲE9{sender}&#B798A8: &f{message}" + disabled: "⚖C9Режим шпиона ẲE9отключён" + format: "ẲE9[⚖C9SPYẲE9] ẲE9{sender} ẲE9→ ⚖C9{receiver}ẲE9: &f{message}" + chat-format: "ẲE9[⚖C9SPYẲE9] ẲE9{type} ẲE9{sender}ẲE9: &f{message}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Message filters │ @@ -81,14 +81,14 @@ spy: filter: blocked: "&#CF6679Сообщение заблокировано" - warning: "&#B798A8Предупреждение" + warning: "ẲE9Предупреждение" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Cooldowns │ # └─────────────────────────────────────────────────────────────────────────────┘ cooldown: - wait: "&#B798A8Подождите ẲE9{time} &#B798A8сек" + wait: "ẲE9Подождите ẲE9{time} ẲE9сек" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Anti-spam (caps, repeat, similar messages) │ @@ -104,7 +104,7 @@ antispam: # └─────────────────────────────────────────────────────────────────────────────┘ clearchat: - cleared: "⚖C9Чат очищен &#B798A8администратором ẲE9{player}" + cleared: "⚖C9Чат очищен ẲE9администратором ẲE9{player}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Command / usage errors │ @@ -113,7 +113,7 @@ clearchat: errors: no-permission: "&#CF6679Нет прав" player-not-found: "&#CF6679Игрок не найден" - invalid-usage: "&#B798A8Использование: ẲE9{usage}" + invalid-usage: "ẲE9Использование: ẲE9{usage}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ General (permissions, reload, hub, etc.) │ @@ -124,7 +124,7 @@ general: players-only: "&#CF6679Только для игроков!" config-reloaded: "⚖C9Конфиг ẲE9перезагружен!" invalid-player: "&#CF6679Игрок ẲE9{player} &#CF6679не найден!" - cooldown-active: "&#B798A8Подождите ẲE9{time}с" + cooldown-active: "ẲE9Подождите ẲE9{time}с" command-disabled: "&#CF6679Команда отключена!" hub: @@ -137,9 +137,9 @@ hub: mute: muted: "⚖C9Игрок ẲE9{player} ⚖C9замучен на ẲE9{time}" unmuted: "⚖C9Игрок ẲE9{player} ⚖C9размучен" - already-muted: "&#B798A8Игрок ẲE9{player} &#B798A8уже замучен" - not-muted: "&#B798A8Игрок ẲE9{player} &#B798A8не замучен" - you-muted: "&#CF6679Вы замучены! &#B798A8Осталось: ẲE9{time}" + already-muted: "ẲE9Игрок ẲE9{player} ẲE9уже замучен" + not-muted: "ẲE9Игрок ẲE9{player} ẲE9не замучен" + you-muted: "&#CF6679Вы замучены! ẲE9Осталось: ẲE9{time}" permanent: "&#CF6679Вы замучены навсегда!" # ┌─────────────────────────────────────────────────────────────────────────────┐ @@ -149,32 +149,32 @@ mute: gradient: color-set: "⚖C9Градиент ẲE9установлен!" color-enabled: "⚖C9Градиентный ник ẲE9включён" - color-disabled: "&#B798A8Градиентный ник ⚖C9отключён" - color-reset: "&#B798A8Градиент сброшен" + color-disabled: "ẲE9Градиентный ник ⚖C9отключён" + color-reset: "ẲE9Градиент сброшен" prefix-set: "⚖C9Префикс ẲE9установлен!" - prefix-reset: "&#B798A8Префикс сброшен" + prefix-reset: "ẲE9Префикс сброшен" prefix-reset-success: "⚖C9Префикс успешно ẲE9сброшен!" prefix-enabled: "⚖C9Префикс ẲE9включён" - prefix-disabled: "&#B798A8Префикс ⚖C9отключён" + prefix-disabled: "ẲE9Префикс ⚖C9отключён" invalid-hex: "&#CF6679Неверный HEX цвет: ẲE9{color}" invalid-color: "&#CF6679Неверный цвет: ẲE9{color}" too-many-colors: "&#CF6679Слишком много цветов! Максимум: ẲE9{max}" too-few-colors: "&#CF6679Слишком мало цветов! Минимум: ẲE9{min}" not-enough-colors: "&#CF6679Слишком мало цветов! Минимум: ẲE9{min}" - cooldown: "&#B798A8Подождите ẲE9{time} &#B798A8сек" + cooldown: "ẲE9Подождите ẲE9{time} ẲE9сек" not-enough-points: "&#CF6679Недостаточно поинтов! Нужно: ẲE9{price}" not-enough-points-color: "&#CF6679Недостаточно поинтов! Нужно: ẲE9{price}" not-enough-points-prefix: "&#CF6679Недостаточно поинтов! Нужно: ẲE9{price}" player-not-found: "&#CF6679Игрок не найден" - color-help: "&#B798A8Использование: ẲE9/color " + color-help: "ẲE9Использование: ẲE9/color " color-success: "⚖C9Градиент успешно ẲE9установлен!" - color-no-colors: "&#B798A8У вас нет установленных цветов" - color-copy-usage: "&#B798A8Использование: ẲE9/color copy <игрок>" - color-copy-no-colors: "&#B798A8У игрока ẲE9{player} &#B798A8нет цветов" - prefix-help: "&#B798A8Использование: ẲE9/prefix <текст> &#B798A8или ẲE9/prefix reset" - prefix-success: "⚖C9Префикс установлен! &#B798A8Стоимость: ẲE9{price} &#B798A8поинтов" + color-no-colors: "ẲE9У вас нет установленных цветов" + color-copy-usage: "ẲE9Использование: ẲE9/color copy <игрок>" + color-copy-no-colors: "ẲE9У игрока ẲE9{player} ẲE9нет цветов" + prefix-help: "ẲE9Использование: ẲE9/prefix <текст> ẲE9или ẲE9/prefix reset" + prefix-success: "⚖C9Префикс установлен! ẲE9Стоимость: ẲE9{price} ẲE9поинтов" prefix-success-free: "⚖C9Префикс ẲE9установлен!" - prefix-no-prefix: "&#B798A8У вас нет префикса" + prefix-no-prefix: "ẲE9У вас нет префикса" prefix-too-long: "&#CF6679Префикс слишком длинный! Максимум: ẲE9{max}" prefix-no-colors: "&#CF6679Префикс не может содержать цветовые коды!" prefix-invalid-chars: "&#CF6679Недопустимые символы в префиксе!" @@ -182,30 +182,30 @@ gradient: only-player: "&#CF6679Только для игроков!" no-permission: "&#CF6679Нет прав!" reload-success: "⚖C9Конфигурация ẲE9перезагружена!" - info-header: "ẲE9▬▬▬▬▬▬▬▬ &#B798A8{player} ẲE9▬▬▬▬▬▬▬▬" - info-prefix: "&#B798A8Префикс: &f{prefix}" - info-prefix-enabled: "&#B798A8Префикс активен: &f{enabled}" - info-colors: "&#B798A8Цвета: &f{colors}" - info-color-enabled: "&#B798A8Цвет активен: &f{enabled}" - info-prefix-purchased: "&#B798A8Куплен: &f{purchased}" + info-header: "ẲE9▬▬▬▬▬▬▬▬ ẲE9{player} ẲE9▬▬▬▬▬▬▬▬" + info-prefix: "ẲE9Префикс: &f{prefix}" + info-prefix-enabled: "ẲE9Префикс активен: &f{enabled}" + info-colors: "ẲE9Цвета: &f{colors}" + info-color-enabled: "ẲE9Цвет активен: &f{enabled}" + info-prefix-purchased: "ẲE9Куплен: &f{purchased}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Custom nicknames (/nick) │ # └─────────────────────────────────────────────────────────────────────────────┘ nick: - current: "&#B798A8Ваш ник: ẲE9{nick}" - usage: "&#B798A8Использование: ẲE9/nick <ник> &#B798A8или ẲE9/nick reset" - reset-usage: "&#B798A8Используйте ẲE9/nick reset &#B798A8для сброса" - set-usage: "&#B798A8Используйте ẲE9/nick <ник> &#B798A8для установки" - no-nick: "&#B798A8У вас нет кастомного ника" + current: "ẲE9Ваш ник: ẲE9{nick}" + usage: "ẲE9Использование: ẲE9/nick <ник> ẲE9или ẲE9/nick reset" + reset-usage: "ẲE9Используйте ẲE9/nick reset ẲE9для сброса" + set-usage: "ẲE9Используйте ẲE9/nick <ник> ẲE9для установки" + no-nick: "ẲE9У вас нет кастомного ника" reset-success: "⚖C9Ник ẲE9сброшен!" set-success: "⚖C9Ник изменён на: &f{nick}" invalid: "&#CF6679Невалидный ник!" - requirements-header: "&#B798A8Требования:" + requirements-header: "ẲE9Требования:" requirements-length: "⚖C9◆ &fДлина: 3-16 символов" requirements-chars: "⚖C9◆ &fБуквы, цифры, _, русские буквы" - requirements-colors: "⚖C9◆ &fЦвета: ẲE9&#RRGGBB&#B798A8текст" + requirements-colors: "⚖C9◆ &fЦвета: ẲE9&#RRGGBBẲE9текст" taken: "&#CF6679Этот ник уже занят!" error: "&#CF6679Не удалось установить ник!" @@ -214,12 +214,12 @@ nick: # └─────────────────────────────────────────────────────────────────────────────┘ rp: - me: "* {player} {action}" - try-format: "* {player} пытается {action}... {result}" + me: "* {player} {action}" + try-format: "* {player} пытается {action}... {result}" try-success: "[Успех!]" try-fail: "[Провал!]" - do-format: "[Сцена] {description} ({player})" - roll-format: "* {player} бросает кубик и получает {result} из {max}" + do-format: "[Сцена] {description} ({player})" + roll-format: "* {player} бросает кубик и получает {result} из {max}" roll-min-error: "Минимальное значение кубика: 2" roll-max-error: "Максимальное значение: 1000000" roll-invalid-number: "Укажите число: /roll [максимум]" diff --git a/src/main/java/com/loki/lochat/config/ConfigManager.java b/src/main/java/com/loki/lochat/config/ConfigManager.java index 775ad98..7408ca9 100644 --- a/src/main/java/com/loki/lochat/config/ConfigManager.java +++ b/src/main/java/com/loki/lochat/config/ConfigManager.java @@ -275,4 +275,38 @@ public class ConfigManager { public boolean useCustomPrefix() { return config.getBoolean("lopreff.use-custom-prefix", true); } + + // ========== Кастомные сообщения ========== + + public boolean isJoinMessageEnabled() { + return config.getBoolean("messages.join.enabled", true); + } + + public String getJoinMessageFormat() { + return config.getString("messages.join.format", "⚖C9✦ &#B798A8{player} ẲE9зашел на сервер"); + } + + public boolean isQuitMessageEnabled() { + return config.getBoolean("messages.quit.enabled", true); + } + + public String getQuitMessageFormat() { + return config.getString("messages.quit.format", "⚖C9✦ &#B798A8{player} ẲE9вышел с сервера"); + } + + public boolean isDeathMessageEnabled() { + return config.getBoolean("messages.death.enabled", true); + } + + public String getDeathPlayerKillFormat() { + return config.getString("messages.death.player_kill", "&#CF6679☠ &#B798A8{player} ẲE9был убит игроком &#B798A8{killer} ⚖C9({weapon})"); + } + + public String getDeathMobKillFormat() { + return config.getString("messages.death.mob_kill", "&#CF6679☠ &#B798A8{player} ẲE9был убит мобом &#B798A8{killer}"); + } + + public String getDeathDefaultFormat() { + return config.getString("messages.death.default", "&#CF6679☠ &#B798A8{player} ẲE9{death_message}"); + } } diff --git a/src/main/java/com/loki/lochat/core/PluginInitializer.java b/src/main/java/com/loki/lochat/core/PluginInitializer.java index 24657ed..aa2bee2 100644 --- a/src/main/java/com/loki/lochat/core/PluginInitializer.java +++ b/src/main/java/com/loki/lochat/core/PluginInitializer.java @@ -103,7 +103,7 @@ public class PluginInitializer { new ChatEventListener(plugin, serviceRegistry, filter), plugin ); plugin.getServer().getPluginManager().registerEvents( - new PlayerEventListener(serviceRegistry, filter), plugin + new PlayerEventListener(plugin, serviceRegistry, filter), plugin ); plugin.getServer().getPluginManager().registerEvents( new ModerationListener(serviceRegistry), plugin diff --git a/src/main/java/com/loki/lochat/core/filter/filters/SwearFilter.java b/src/main/java/com/loki/lochat/core/filter/filters/SwearFilter.java index af3b5fb..b6c0d06 100644 --- a/src/main/java/com/loki/lochat/core/filter/filters/SwearFilter.java +++ b/src/main/java/com/loki/lochat/core/filter/filters/SwearFilter.java @@ -9,9 +9,7 @@ import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.regex.Pattern; public class SwearFilter { @@ -19,6 +17,22 @@ public class SwearFilter { private final boolean replaceMode; private final String replacement; private final Set swearPatterns = new HashSet<>(); + + // Карта замены латиницы на кириллицу для обхода фильтра + private static final Map LATIN_TO_CYRILLIC = Map.ofEntries( + Map.entry('a', 'а'), Map.entry('A', 'А'), + Map.entry('e', 'е'), Map.entry('E', 'Е'), + Map.entry('o', 'о'), Map.entry('O', 'О'), + Map.entry('p', 'р'), Map.entry('P', 'Р'), + Map.entry('c', 'с'), Map.entry('C', 'С'), + Map.entry('y', 'у'), Map.entry('Y', 'У'), + Map.entry('x', 'х'), Map.entry('X', 'Х'), + Map.entry('k', 'к'), Map.entry('K', 'К'), + Map.entry('m', 'м'), Map.entry('M', 'М'), + Map.entry('h', 'н'), Map.entry('H', 'Н'), + Map.entry('t', 'т'), Map.entry('T', 'Т'), + Map.entry('b', 'в'), Map.entry('B', 'В') + ); public SwearFilter(FileConfiguration config, JavaPlugin plugin) { this.enabled = config.getBoolean("filters.swear.enabled", true); @@ -37,13 +51,46 @@ public class SwearFilter { reader.lines() .map(String::trim) .filter(line -> !line.isEmpty() && !line.startsWith("#")) - .forEach(word -> swearPatterns.add( - Pattern.compile("\\b" + Pattern.quote(word) + "\\b", Pattern.CASE_INSENSITIVE) - )); + .forEach(word -> { + // Создаем паттерн с учетом возможной замены букв + String regex = buildFlexiblePattern(word); + swearPatterns.add(Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE)); + }); } catch (Exception e) { plugin.getLogger().warning("Failed to load badwords.txt: " + e.getMessage()); } } + + /** + * Создает гибкий паттерн, который ловит замены букв (х->x, о->o и т.д.) + */ + private String buildFlexiblePattern(String word) { + StringBuilder pattern = new StringBuilder(); + + for (char c : word.toLowerCase().toCharArray()) { + // Для каждой буквы создаем группу с возможными заменами + switch (c) { + case 'а' -> pattern.append("[аaА@]"); + case 'е' -> pattern.append("[еeЕ3]"); + case 'о' -> pattern.append("[оoО0]"); + case 'р' -> pattern.append("[рpР]"); + case 'с' -> pattern.append("[сcС]"); + case 'у' -> pattern.append("[уyУ]"); + case 'х' -> pattern.append("[хxХ]"); + case 'к' -> pattern.append("[кkК]"); + case 'м' -> pattern.append("[мmМ]"); + case 'н' -> pattern.append("[нhНN]"); + case 'т' -> pattern.append("[тtТ]"); + case 'в' -> pattern.append("[вbВ]"); + case 'и' -> pattern.append("[иiИ1]"); + case 'з' -> pattern.append("[з3З]"); + case 'б' -> pattern.append("[б6Б]"); + default -> pattern.append(Pattern.quote(String.valueOf(c))); + } + } + + return pattern.toString(); + } public FilterResult filter(Player player, String message) { if (!enabled || player.hasPermission("lochat.bypass.swear")) { diff --git a/src/main/java/com/loki/lochat/gradient/GradientModule.java b/src/main/java/com/loki/lochat/gradient/GradientModule.java index 4e89c86..5af1f70 100644 --- a/src/main/java/com/loki/lochat/gradient/GradientModule.java +++ b/src/main/java/com/loki/lochat/gradient/GradientModule.java @@ -149,6 +149,16 @@ public class GradientModule { return prefixService.getPrefix(player, data); } + /** + * Получает суффикс игрока из LuckPerms с градиентом + */ + public String getSuffix(Player player) { + if (!enabled) return ""; + + GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId()); + return prefixService.getLuckPermsSuffix(player, data); + } + /** * Получает только LuckPerms префикс */ diff --git a/src/main/java/com/loki/lochat/gradient/service/PrefixService.java b/src/main/java/com/loki/lochat/gradient/service/PrefixService.java index dbd278e..80425b8 100644 --- a/src/main/java/com/loki/lochat/gradient/service/PrefixService.java +++ b/src/main/java/com/loki/lochat/gradient/service/PrefixService.java @@ -79,10 +79,47 @@ public class PrefixService { } /** - * Получает полное имя игрока: префикс + ник с градиентом + * Получает суффикс из LuckPerms + */ + public String getLuckPermsSuffix(Player player, GradientPlayerData data) { + if (!luckPermsAvailable) { + return ""; + } + + try { + LuckPerms luckPerms = LuckPermsProvider.get(); + User user = luckPerms.getUserManager().getUser(player.getUniqueId()); + + if (user == null) { + return ""; + } + + CachedMetaData metaData = user.getCachedData().getMetaData(); + String suffix = metaData.getSuffix(); + + if (suffix == null || suffix.isEmpty()) { + return ""; + } + + // Применяем градиент если нужно + if (data != null && data.hasColors() && data.isColorEnabled() && config.isGradientOnLuckPermsPrefix()) { + String cleanSuffix = GradientService.stripColors(suffix); + return GradientService.applyGradient(cleanSuffix, data.getColors()); + } + + return suffix; + + } catch (Exception e) { + return ""; + } + } + + /** + * Получает полное имя игрока: префикс + ник с градиентом + суффикс */ public String getFullName(Player player, GradientPlayerData data) { String prefix = getPrefix(player, data); + String suffix = getLuckPermsSuffix(player, data); String nick = player.getName(); // Применяем градиент к нику если есть цвета @@ -90,7 +127,12 @@ public class PrefixService { nick = GradientService.applyGradient(nick, data.getColors()); } - return prefix.isEmpty() ? nick : prefix + " " + nick; + StringBuilder result = new StringBuilder(); + if (!prefix.isEmpty()) result.append(prefix).append(" "); + result.append(nick); + if (!suffix.isEmpty()) result.append(" ").append(suffix); + + return result.toString(); } /** @@ -98,6 +140,7 @@ public class PrefixService { */ public String getFullNameForTab(Player player, GradientPlayerData data) { String prefix = getPrefix(player, data); + String suffix = getLuckPermsSuffix(player, data); String nick = player.getName(); // Применяем градиент в формате TAB @@ -105,7 +148,12 @@ public class PrefixService { nick = GradientService.applyGradientForTab(nick, data.getColors()); } - return prefix.isEmpty() ? nick : prefix + " " + nick; + StringBuilder result = new StringBuilder(); + if (!prefix.isEmpty()) result.append(prefix).append(" "); + result.append(nick); + if (!suffix.isEmpty()) result.append(" ").append(suffix); + + return result.toString(); } public boolean isLuckPermsAvailable() { diff --git a/src/main/java/com/loki/lochat/listener/PlayerEventListener.java b/src/main/java/com/loki/lochat/listener/PlayerEventListener.java index 3c4ba6a..2b52675 100644 --- a/src/main/java/com/loki/lochat/listener/PlayerEventListener.java +++ b/src/main/java/com/loki/lochat/listener/PlayerEventListener.java @@ -1,30 +1,42 @@ package com.loki.lochat.listener; +import com.loki.lochat.LoChat; import com.loki.lochat.api.service.MessagingService; import com.loki.lochat.api.service.NickService; import com.loki.lochat.api.service.PlayerService; +import com.loki.lochat.config.ConfigManager; import com.loki.lochat.core.filter.AdvancedMessageFilter; import com.loki.lochat.core.registry.ServiceRegistry; +import com.loki.lochat.utils.ChatFormatter; +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; /** * Слушатель событий игрока */ public class PlayerEventListener implements Listener { + private final LoChat plugin; private final PlayerService playerService; private final MessagingService messagingService; private final NickService nickService; private final AdvancedMessageFilter advancedFilter; + private final ConfigManager configManager; - public PlayerEventListener(ServiceRegistry registry, AdvancedMessageFilter advancedFilter) { + public PlayerEventListener(LoChat plugin, ServiceRegistry registry, AdvancedMessageFilter advancedFilter) { + this.plugin = plugin; this.playerService = registry.get(PlayerService.class); this.messagingService = registry.get(MessagingService.class); this.nickService = registry.get(NickService.class); - this.advancedFilter = advancedFilter; + this.advancedFilter = advancedFilter; + this.configManager = plugin.getConfigManager(); } @EventHandler @@ -33,6 +45,13 @@ public class PlayerEventListener implements Listener { if (nickService != null) { nickService.updatePlayerDisplay(player); } + + // Кастомное сообщение входа + if (configManager.isJoinMessageEnabled()) { + String format = configManager.getJoinMessageFormat(); + String message = format.replace("{player}", player.getName()); + event.joinMessage(ChatFormatter.parse(message)); + } } @EventHandler @@ -44,7 +63,110 @@ public class PlayerEventListener implements Listener { messagingService.removeConversation(uuid); messagingService.removeSpy(uuid); - // Очищаем flood / spam трекеры — иначе данные висят в памяти вечно + // Очищаем flood / spam трекеры advancedFilter.clearPlayer(uuid); + + // Кастомное сообщение выхода + if (configManager.isQuitMessageEnabled()) { + String format = configManager.getQuitMessageFormat(); + String message = format.replace("{player}", player.getName()); + event.quitMessage(ChatFormatter.parse(message)); + } + } + + @EventHandler + public void onDeath(PlayerDeathEvent event) { + if (!configManager.isDeathMessageEnabled()) { + return; + } + + Player player = event.getPlayer(); + Player killer = player.getKiller(); + + String message; + + if (killer != null) { + // Убийство игроком + ItemStack weapon = killer.getInventory().getItemInMainHand(); + String weaponName = getWeaponName(weapon); + + message = configManager.getDeathPlayerKillFormat() + .replace("{player}", player.getName()) + .replace("{killer}", killer.getName()) + .replace("{weapon}", weaponName); + } else { + // Проверяем убийцу-моба + Entity lastDamager = player.getLastDamageCause() != null ? + player.getLastDamageCause().getEntity() : null; + + if (lastDamager instanceof LivingEntity mob && !(lastDamager instanceof Player)) { + String mobName = getMobName(mob); + message = configManager.getDeathMobKillFormat() + .replace("{player}", player.getName()) + .replace("{killer}", mobName); + } else { + // Обычная смерть + Component deathMsg = event.deathMessage(); + String deathText = deathMsg != null ? ChatFormatter.toPlain(deathMsg) : "умер"; + message = configManager.getDeathDefaultFormat() + .replace("{player}", player.getName()) + .replace("{death_message}", deathText); + } + } + + event.deathMessage(ChatFormatter.parse(message)); + } + + private String getWeaponName(ItemStack item) { + if (item == null || item.getType().isAir()) { + return "Руки"; + } + + if (item.hasItemMeta() && item.getItemMeta().hasDisplayName()) { + return ChatFormatter.toPlain(item.getItemMeta().displayName()); + } + + return translateMaterial(item.getType().name()); + } + + private String getMobName(LivingEntity mob) { + if (mob.customName() != null) { + return ChatFormatter.toPlain(mob.customName()); + } + return translateEntityType(mob.getType().name()); + } + + private String translateMaterial(String material) { + return switch (material) { + case "DIAMOND_SWORD" -> "Алмазный меч"; + case "IRON_SWORD" -> "Железный меч"; + case "GOLDEN_SWORD" -> "Золотой меч"; + case "STONE_SWORD" -> "Каменный меч"; + case "WOODEN_SWORD" -> "Деревянный меч"; + case "NETHERITE_SWORD" -> "Незеритовый меч"; + case "BOW" -> "Лук"; + case "CROSSBOW" -> "Арбалет"; + case "TRIDENT" -> "Трезубец"; + case "DIAMOND_AXE" -> "Алмазный топор"; + case "IRON_AXE" -> "Железный топор"; + case "NETHERITE_AXE" -> "Незеритовый топор"; + default -> material.replace("_", " ").toLowerCase(); + }; + } + + private String translateEntityType(String entityType) { + return switch (entityType) { + case "ZOMBIE" -> "Зомби"; + case "SKELETON" -> "Скелет"; + case "CREEPER" -> "Крипер"; + case "SPIDER" -> "Паук"; + case "ENDERMAN" -> "Эндермен"; + case "BLAZE" -> "Ифрит"; + case "WITCH" -> "Ведьма"; + case "PIGLIN" -> "Пиглин"; + case "HOGLIN" -> "Хоглин"; + case "WITHER_SKELETON" -> "Скелет-иссушитель"; + default -> entityType.replace("_", " ").toLowerCase(); + }; } } diff --git a/src/main/java/com/loki/lochat/renderer/EnhancedChatRenderer.java b/src/main/java/com/loki/lochat/renderer/EnhancedChatRenderer.java index 2b4798d..c93eb58 100644 --- a/src/main/java/com/loki/lochat/renderer/EnhancedChatRenderer.java +++ b/src/main/java/com/loki/lochat/renderer/EnhancedChatRenderer.java @@ -57,13 +57,14 @@ public class EnhancedChatRenderer implements ChatRenderer { Component prefix = buildPrefix(); Component separator = buildSeparator(); Component playerPrefix = buildPlayerPrefix(source); + Component playerSuffix = buildPlayerSuffix(source); Component player = buildPlayerComponent(source); - return buildFormattedMessage(format, emoji, prefix, separator, playerPrefix, player, processed); + return buildFormattedMessage(format, emoji, prefix, separator, playerPrefix, playerSuffix, player, processed); } private Component buildFormattedMessage(String format, Component emoji, Component prefix, - Component separator, Component playerPrefix, + Component separator, Component playerPrefix, Component playerSuffix, Component player, Component message) { Component result = Component.empty(); java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\{([^}]+)\\}"); @@ -83,6 +84,7 @@ public class EnhancedChatRenderer implements ChatRenderer { case "separator" -> result = result.append(separator); case "player_prefix" -> result = result.append(playerPrefix); case "player" -> result = result.append(player); + case "player_suffix" -> result = result.append(playerSuffix); case "message" -> result = result.append(message); default -> result = result.append(Component.text("{" + placeholder + "}")); } @@ -117,6 +119,20 @@ public class EnhancedChatRenderer implements ChatRenderer { return Component.empty(); } + private Component buildPlayerSuffix(Player player) { + com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; + com.loki.lochat.gradient.GradientModule gradientModule = loChat.getGradientModule(); + + if (gradientModule != null && gradientModule.isEnabled()) { + String suffix = gradientModule.getSuffix(player); + if (!suffix.isEmpty()) { + return com.loki.lochat.utils.ChatFormatter.parse(suffix); + } + } + + return Component.empty(); + } + private Component buildPrefix() { com.loki.lochat.config.AppearanceConfig appearanceCfg = cfg.getAppearanceConfig(); diff --git a/src/main/resources/badwords.txt b/src/main/resources/badwords.txt new file mode 100644 index 0000000..734afd5 --- /dev/null +++ b/src/main/resources/badwords.txt @@ -0,0 +1,58 @@ +# Список запрещенных слов (по одному на строку) +# Строки начинающиеся с # игнорируются +# Фильтр автоматически ловит замены букв (х->x, о->o и т.д.) + +# Основной мат +хуй +хуя +хуе +хую +хуем +хуи +пизда +пизде +пизду +пиздой +пизды +ебать +ебал +ебет +ебут +ебала +ебали +ебаный +ебанный +ебучий +ебучая +ебучее +блять +блядь +бляди +блядей +сука +суки +сучка +сучки +пидор +пидора +пидоры +пидорас +пидорасы +гандон +гандоны +мудак +мудаки +мудила +мудилы +долбоеб +долбоебы +уебан +уебаны +уебок +уебки +говно +говна +говном +дерьмо +дерьма +дерьмом diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index aede1c2..f6eb1e2 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,58 +1,143 @@ -# LoChat Configuration v1.5.5 -config-version: 4 +# ╔══════════════════════════════════════════════════════════════════════════════╗ +# ║ LoChat Configuration ║ +# ║ Единый конфиг плагина ║ +# ╚══════════════════════════════════════════════════════════════════════════════╝ + +config-version: 5 prefix: "�D4FF[LoChat]" -# Базовые настройки чата -# Детальные настройки (префиксы, цвета, форматы) в config/*.yml - +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Режимы чата │ +# └─────────────────────────────────────────────────────────────────────────────┘ chat: - # Режим чата: - # - Оба включены = локальный (по умолчанию) + глобальный (с "!") - # - Только global = единый чат без префиксов для всех + # Глобальный чат (все видят, активируется через "!") global: enabled: true cooldown: 3 + + # Локальный чат (только в радиусе) local: enabled: true cooldown: 1 + radius: 100 + + # Личные сообщения pm: enabled: true cooldown: 2 + + # Лимиты сообщений + min_message_length: 1 + max_message_length: 2000 + clear_lines: 100 -# Hub команда -hub_command: - enabled: true - server: "hub" +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Внешний вид чата (префиксы, форматы, цвета) │ +# └─────────────────────────────────────────────────────────────────────────────┘ +appearance: + # Префиксы чата (GLOBAL / LOCAL) + prefixes: + global: + emoji: "✦" + text: "GLOBAL" + colors: ["#B798A8", "#7858E9"] + separator: + text: " ▶ " + color: "#9878C9" + message-color: "#E8E0F0" + + local: + emoji: "◆" + text: "LOCAL" + colors: ["#9878C9", "#7858E9"] + separator: + text: " ▶ " + color: "#8B6BD6" + message-color: "#E8E0F0" + + # Формат сообщений + # {emoji} {prefix} {separator} {player_prefix} {player} {player_suffix} {message} + chat-format: + global: "{emoji} {prefix} {separator} {player_prefix}{player}{player_suffix}: {message}" + local: "{emoji} {prefix} {separator} {player_prefix}{player}{player_suffix}: {message}" + + # Hover при наведении на ник + hover: + enabled: true + format: + - "ẲE9▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬" + - "&#B798A8✦ &f{player}" + - "" + - "⚖C9⏱ &fПинг: ẲE9{ping}ms" + - "⚖C9❤ &fЗдоровье: ẲE9{health}/20" + - "⚖C9🍖 &fГолод: ẲE9{food}/20" + - "" + - "ẲE9▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬" + - "&#B798A8✦ &fНажми чтобы написать" + + # Эмодзи (текстовые замены) + emojis: + ":heart:": "❤" + ":star:": "★" + ":diamond:": "♦" + ":check:": "✓" + ":cross:": "✗" + ":arrow:": "→" + ":smile:": ":)" + ":sad:": ":(" + ":shrug:": "¯\\_(ツ)_/¯" + + # Цвета системных сообщений + colors: + nobody_heard: "#9878C9" + error: "#CF6679" + success: "#9878C9" + warning: "#B798A8" + info: "#A485BC" -# Градиентные ники -lopreff: +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Градиентные ники и префиксы │ +# └─────────────────────────────────────────────────────────────────────────────┘ +gradient: enabled: true use-gradient-name: true use-custom-prefix: true - -display: - update-display-name: true - update-tab-list: true - -# Градиенты (детали в config/gradient.yml) -gradient: - storage-type: "file" + + # Настройки отображения + display: + update-display-name: true + update-tab-list: true + + # Лимиты min-colors: 1 max-colors: 7 + max-prefix-length: 16 + + # Цены (PlayerPoints) price-per-color: 50 prefix-price: 500 prefix-one-time-purchase: true use-permission-instead-of-cost: false - max-prefix-length: 16 + + # Кулдауны (секунды) color-cooldown: 60 prefix-cooldown: 120 + + # Форматирование prefix-format: "[{prefix}] " gradient-on-prefix: true gradient-on-luckperms-prefix: true + gradient-on-luckperms-suffix: true continuous-gradient: true use-legacy-rgb-format: true + + # Хранилище (file / sqlite) + storage-type: "file" + sqlite-file: "gradient-data.db" -# RP команды (детали в config/rp.yml) +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ RP команды (/me, /do, /try, /roll) │ +# └─────────────────────────────────────────────────────────────────────────────┘ rp: radius: 100 cooldowns: @@ -61,6 +146,126 @@ rp: do: 3 roll: 2 -# Модерация: после /lmute выполнить команду от консоли (SimpleVoiceChat: voice mute {player}) +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Фильтры чата │ +# └─────────────────────────────────────────────────────────────────────────────┘ +filters: + # Мат-фильтр + swear: + enabled: true + replace: false + replacement: "***" + words: + - "хуй" + - "пизда" + - "ебать" + + # URL фильтр + url: + enabled: true + replacement: "[ссылка скрыта]" + + # IP фильтр + ip: + enabled: true + replacement: "[IP скрыт]" + + # Скрытые URL (discord.gg и т.д.) + hidden-urls: + enabled: true + patterns: + - "discord\\.gg" + - "dsc\\.gg" + + # Анти-спам + spam: + enabled: true + max-repeating-chars: 5 + + # Анти-флуд + flood: + enabled: true + max-same-messages: 3 + + # Капс + caps: + enabled: true + max-caps-percent: 70 + min-length: 5 + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Модерация (муты, баны, варны) │ +# └─────────────────────────────────────────────────────────────────────────────┘ moderation: + # Дефолтные значения + default-mute-duration: "7d" + default-mute-reason: "Без причины" + default-ban-reason: "Без причины" + + # Команда после мута (для голосового чата) voice-mute-console-command: "" + + # Сообщение при кике забаненного + ban-kick-message: "&#CF6679Вы забанены! &#B798A8Причина: ẲE9{reason}" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Звуки │ +# └─────────────────────────────────────────────────────────────────────────────┘ +sounds: + # Упоминание в чате + mention: + enabled: true + sound: "BLOCK_NOTE_BLOCK_PLING" + volume: 1.0 + pitch: 1.5 + + # Личное сообщение + pm: + enabled: true + sound: "ENTITY_EXPERIENCE_ORB_PICKUP" + volume: 0.8 + pitch: 1.2 + + # Команды + command: + enabled: false + sound: "UI_BUTTON_CLICK" + volume: 0.5 + pitch: 1.0 + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Сообщения входа/выхода/смерти │ +# └─────────────────────────────────────────────────────────────────────────────┘ +messages: + # Сообщение при входе + # {player} - ник игрока + join: + enabled: true + format: "⚖C9✦ &#B798A8{player} ẲE9зашел на сервер" + + # Сообщение при выходе + quit: + enabled: true + format: "⚖C9✦ &#B798A8{player} ẲE9вышел с сервера" + + # Сообщение при смерти + # {player} - ник игрока + # {killer} - ник убийцы (если есть) + # {weapon} - название оружия (если есть) + # {death_message} - стандартное сообщение смерти + death: + enabled: true + # Убийство игроком + player_kill: "&#CF6679☠ &#B798A8{player} ẲE9был убит игроком &#B798A8{killer} ⚖C9({weapon})" + # Смерть от моба + mob_kill: "&#CF6679☠ &#B798A8{player} ẲE9был убит мобом &#B798A8{killer}" + # Обычная смерть + default: "&#CF6679☠ &#B798A8{player} ẲE9{death_message}" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Прочее │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Hub команда +hub_command: + enabled: true + server: "hub" diff --git a/src/main/resources/config/appearance.yml b/src/main/resources/config/appearance.yml index 4626e44..a76805e 100644 --- a/src/main/resources/config/appearance.yml +++ b/src/main/resources/config/appearance.yml @@ -44,16 +44,17 @@ prefixes: # {separator} — separator (e.g. ▶) # {player_prefix} — LuckPerms / gradient prefix from LoPreff # {player} — display name + hover (click to /msg) +# {player_suffix} — LuckPerms / gradient suffix from LoPreff # {message} — message body # # Examples: # Default: {emoji} {prefix} {separator} {player} : {message} # Minimal: {player_prefix} {player} ▶ {message} -# Custom: ✦ {player_prefix} {player} ▶ {message} +# Custom: ✦ {player_prefix} {player} {player_suffix} ▶ {message} chat-format: - global: "{emoji} {prefix} {separator} {player} : {message}" - local: "{emoji} {prefix} {separator} {player} : {message}" + global: "{emoji} {prefix} {separator} {player_prefix}{player}{player_suffix} : {message}" + local: "{emoji} {prefix} {separator} {player_prefix}{player}{player_suffix} : {message}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Hover shown when pointing at the player name in chat │ diff --git a/src/main/resources/config/discord.yml b/src/main/resources/config/discord.yml index d0ca13b..1070b6a 100644 --- a/src/main/resources/config/discord.yml +++ b/src/main/resources/config/discord.yml @@ -40,6 +40,7 @@ events: enabled: true # Настройки входа на сервер + # {player} - ник игрока join: enabled: true title: "🟢 Игрок зашел на сервер" @@ -47,7 +48,8 @@ events: color: "00FF00" thumbnail: "https://mc-heads.net/avatar/{player}/64" - # Настройки выхода с сервера + # Настройки выхода с сервера + # {player} - ник игрока quit: enabled: true title: "🔴 Игрок покинул сервер" @@ -56,11 +58,27 @@ events: thumbnail: "https://mc-heads.net/avatar/{player}/64" # Настройки смерти игрока + # {player} - ник игрока + # {killer} - ник убийцы (если есть) + # {weapon} - название оружия (если есть) + # {death_message} - стандартное сообщение смерти death: enabled: true - title: "💀 Смерть игрока" - description: "**{player}** {death_message}" - color: "800080" + # Убийство игроком + player_kill: + title: "⚔️ PvP убийство" + description: "**{player}** был убит игроком **{killer}** с помощью **{weapon}**" + color: "FF0000" + # Смерть от моба + mob_kill: + title: "💀 Смерть от моба" + description: "**{player}** был убит мобом **{killer}**" + color: "800080" + # Обычная смерть + default: + title: "💀 Смерть игрока" + description: "**{player}** {death_message}" + color: "800080" thumbnail: "https://mc-heads.net/avatar/{player}/64" # Настройки фильтрации diff --git a/src/main/resources/config/filters.yml b/src/main/resources/config/filters.yml index 5812333..029c0f0 100644 --- a/src/main/resources/config/filters.yml +++ b/src/main/resources/config/filters.yml @@ -1,286 +1,81 @@ # ╔══════════════════════════════════════════════════════════════════════════════╗ -# ║ LoChat Filters Configuration ║ -# ║ Конфигурация фильтров ║ -# ║ Версия: 1.5.5-1.20.1 ║ +# ║ LoChat — Фильтры чата ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР ЗАГЛАВНЫХ БУКВ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -caps: - # Включить фильтр заглавных букв - enabled: true - - # Максимальный процент заглавных букв (0-100) - max-percent: 70 - - # Минимальная длина сообщения для проверки - min-length: 5 - - # Автоматически переводить в нижний регистр - auto-lowercase: true - - # Действие при нарушении: block, warn, lowercase - action: "lowercase" - - # Сообщение при блокировке - block-message: "&#FF6B6BСлишком много заглавных букв!" - - # Сообщение-предупреждение - warn-message: "&#FFA726Не используйте слишком много заглавных букв" - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР РЕКЛАМЫ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -advertising: - # Включить фильтр рекламы - enabled: true - - # Блокировать скрытые URL (bit.ly, tinyurl и т.д.) - block-hidden-urls: true - - # Блокировать домены - block-domains: true - - # Блокировать IP адреса - block-ips: true - - # Действие при обнаружении: block, warn, replace - action: "block" - - # Сообщение при блокировке - block-message: "&#FF6B6BРеклама запрещена!" - - # Замена для заблокированного контента - replacement: "[РЕКЛАМА УДАЛЕНА]" - - # Белый список доменов (разрешенные) - whitelist: - - "youtube.com" - - "youtu.be" - - "discord.gg" - - "twitch.tv" - - "imgur.com" - - "gyazo.com" - - "prntscr.com" - - # Черный список доменов (запрещенные) - blacklist: - - "scam-site.com" - - "virus-site.ru" - - "fake-minecraft.net" - - # Регулярные выражения для поиска рекламы - patterns: - - "(?i)\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}(?::[0-9]{1,5})?\\b" # IP адреса - - "(?i)\\b[a-z0-9.-]+\\.(com|net|org|ru|de|uk)\\b" # Домены - - "(?i)\\b(server|сервер)\\s*[:-]?\\s*[a-z0-9.-]+\\b" # Серверы - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР IP АДРЕСОВ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -ip: - # Включить фильтр IP адресов - enabled: true - - # Блокировать сообщение или заменять IP - block: false - - # Замена для IP адресов - replacement: "[IP СКРЫТ]" - - # Сообщение при блокировке - block-message: "&#FF6B6BIP адреса запрещены!" - - # Белый список IP (разрешенные) - whitelist: - - "127.0.0.1" - - "localhost" - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР ПОВТОРОВ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -repeat: - # Включить фильтр повторяющихся символов - enabled: true - - # Максимальное количество повторов подряд - max-repeats: 3 - - # Действие: block, warn, replace - action: "replace" - - # Сообщение при блокировке - block-message: "&#FF6B6BНе повторяйте символы!" - - # Автоматически сокращать повторы - auto-reduce: true - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР СПАМА │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -spam: - # Включить антиспам - enabled: true - - # Максимальное количество одинаковых сообщений - max-similar-messages: 3 - - # Время для сброса счетчика (секунды) - reset-time: 60 - - # Процент схожести сообщений (0-100) - similarity-threshold: 80 - - # Действие при спаме: block, warn, mute - action: "warn" - - # Сообщение при блокировке - block-message: "&#FF6B6BНе спамьте!" - - # Время мута за спам (секунды) - mute-duration: 300 - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР МАТА │ +# │ Мат-фильтр │ # └─────────────────────────────────────────────────────────────────────────────┘ +# Использует badwords.txt для списка слов +# Автоматически ловит замены букв: х->x, о->o, е->e и т.д. +# Обход: lochat.bypass.swear swear: - # Включить фильтр мата enabled: true - - # Использовать внешний файл со словами - use-external-file: true - - # Файл со словами (относительно папки плагина) - words-file: "filter-words.json" - - # Действие: block, warn, replace, mute - action: "replace" - - # Символ для замены - replacement-char: "*" - - # Сообщение при блокировке - block-message: "&#FF6B6BМат запрещен!" - - # Время мута за мат (секунды) - mute-duration: 600 - - # Встроенный список слов (если не используется внешний файл) - words: - - "плохое_слово1" - - "плохое_слово2" - - # Проверять фрагменты слов - check-fragments: true - - # Игнорировать регистр - ignore-case: true - - # Проверять транслитерацию - check-transliteration: true + replace: false # true = заменять на ***, false = блокировать сообщение + replacement: "***" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТР ФЛУДА │ +# │ URL фильтр │ # └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует ссылки в чате +# Обход: lochat.bypass.urlfilter + +url: + enabled: true + replacement: "[ссылка скрыта]" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ IP фильтр │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует IP адреса в чате +# Обход: lochat.bypass.ipfilter + +ip: + enabled: true + replacement: "[IP скрыт]" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Скрытые URL (discord.gg и т.д.) │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует популярные сокращенные ссылки +# Обход: lochat.bypass.hiddenurls + +hidden-urls: + enabled: true + patterns: + - "discord\\.gg" + - "dsc\\.gg" + - "bit\\.ly" + - "tinyurl\\.com" + - "goo\\.gl" + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Анти-спам │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует повторяющиеся символы (ааааааа, хххххх) +# Обход: lochat.bypass.spam + +spam: + enabled: true + max-repeating-chars: 5 # Максимум повторений одного символа + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ Анти-флуд │ +# └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует одинаковые сообщения подряд +# Обход: lochat.bypass.flood flood: - # Включить антифлуд enabled: true - - # Максимальное количество сообщений в период - max-messages: 5 - - # Период времени (секунды) - time-period: 10 - - # Действие при флуде: block, warn, mute - action: "warn" - - # Сообщение при блокировке - block-message: "&#FF6B6BНе флудите!" - - # Время мута за флуд (секунды) - mute-duration: 180 + max-same-messages: 3 # Сколько раз можно отправить одно и то же # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ДОПОЛНИТЕЛЬНЫЕ ФИЛЬТРЫ │ +# │ Капс-фильтр │ # └─────────────────────────────────────────────────────────────────────────────┘ +# Блокирует сообщения с большим количеством заглавных букв +# Обход: lochat.bypass.caps -additional: - # Фильтр эмодзи - emoji: - enabled: false - max-per-message: 5 - block-message: "&#FF6B6BСлишком много эмодзи!" - - # Фильтр длинных сообщений - length: - enabled: true - max-length: 256 - action: "truncate" # truncate или block - block-message: "&#FF6B6BСообщение слишком длинное!" - - # Фильтр пустых сообщений - empty: - enabled: true - block-message: "&#FF6B6BПустые сообщения запрещены!" - - # Фильтр только пробелов - whitespace: - enabled: true - block-message: "&#FF6B6BСообщение не может состоять только из пробелов!" - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ НАСТРОЙКИ ОБХОДА │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -bypass: - # Права для обхода фильтров - permissions: - caps: "lochat.bypass.caps" - advertising: "lochat.bypass.advertising" - ip: "lochat.bypass.ip" - repeat: "lochat.bypass.repeat" - spam: "lochat.bypass.spam" - swear: "lochat.bypass.swear" - flood: "lochat.bypass.flood" - all: "lochat.bypass.filter" - - # Группы, которые обходят все фильтры - groups: - - "admin" - - "moderator" - - # Миры, где фильтры отключены - worlds: - - "creative_world" - -# ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ЛОГИРОВАНИЕ │ -# └─────────────────────────────────────────────────────────────────────────────┘ - -logging: - # Логировать заблокированные сообщения +caps: enabled: true - - # Файл для логов - file: "plugins/LoChat/logs/filter.log" - - # Формат лога - format: "[{time}] {player} ({filter}): {message}" - - # Логировать в консоль - console: true - - # Уведомлять администраторов - notify-admins: true - - # Право для получения уведомлений - notify-permission: "lochat.filter.notify" \ No newline at end of file + max-caps-percent: 70 # Максимум % заглавных букв + min-length: 5 # Минимальная длина для проверки diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 452cc4d..4cd0418 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -1,16 +1,16 @@ # ╔══════════════════════════════════════════════════════════════════════════════╗ # ║ LoChat — messages.yml ║ -# ║ Default palette (Spacelegacy): #B798A8 (dusty rose) → #7858E9 (violet) ║ +# ║ Default palette (Spacelegacy): #7858E9 (dusty rose) → #7858E9 (violet) ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # # Color roles (reference when editing strings below): -# &#B798A8 — primary labels / body (dusty rose) +# ẲE9 — primary labels / body (dusty rose) # ⚖C9 — accents / separators (mid violet) # ẲE9 — emphasis / values (deep violet) # &f / #E8E0F0 — message body white # &#CF6679 — errors (muted red, fits the palette) -prefix: "&#B798A8[⚖C9LB6BD6oẲE9CⅵDChB6BD6a⚖C9t&#B798A8]" +prefix: "ẲE9[⚖C9LB6BD6oẲE9CⅵDChB6BD6a⚖C9tẲE9]" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Global chat (toggle messages, server-disabled notice) │ @@ -18,7 +18,7 @@ prefix: "&#B798A8[⚖C9LB6BD6oẲE9CⅵDChB6BD6a⚖C9t&#B798A8 global: toggled-on: "⚖C9Глобальный чат ẲE9включён" - toggled-off: "⚖C9Глобальный чат &#B798A8отключён" + toggled-off: "⚖C9Глобальный чат ẲE9отключён" disabled: "&#CF6679Глобальный чат отключён на этом сервере" # ┌─────────────────────────────────────────────────────────────────────────────┐ @@ -26,19 +26,19 @@ global: # └─────────────────────────────────────────────────────────────────────────────┘ local: - no-players: "&#B798A8Рядом нет игроков" - nobody-heard: "⚖C9Вас никто не услышал &#B798A8— рядом нет игроков" + no-players: "ẲE9Рядом нет игроков" + nobody-heard: "⚖C9Вас никто не услышал ẲE9— рядом нет игроков" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Private messages (/msg, /reply) │ # └─────────────────────────────────────────────────────────────────────────────┘ pm: - format-sent: "&#B798A8[⚖C9→&#B798A8] ⚖C9Вы &#B798A8→ {receiver_display}&#B798A8: &f{message}" - format-received: "&#B798A8[⚖C9←&#B798A8] {sender_display} &#B798A8→ ⚖C9Вы&#B798A8: &f{message}" + format-sent: "ẲE9[⚖C9→ẲE9] ⚖C9Вы ẲE9→ {receiver_display}ẲE9: &f{message}" + format-received: "ẲE9[⚖C9←ẲE9] {sender_display} ẲE9→ ⚖C9ВыẲE9: &f{message}" use-gradient-names: true - sent: "&#B798A8[⚖C9→&#B798A8] ⚖C9Вы &#B798A8→ &f{player}&#B798A8: &f{message}" - received: "&#B798A8[⚖C9←&#B798A8] &f{player} &#B798A8→ ⚖C9Вы&#B798A8: &f{message}" + sent: "ẲE9[⚖C9→ẲE9] ⚖C9Вы ẲE9→ &f{player}ẲE9: &f{message}" + received: "ẲE9[⚖C9←ẲE9] &f{player} ẲE9→ ⚖C9ВыẲE9: &f{message}" no-reply: "&#CF6679Некому ответить" ignored: "&#CF6679Этот игрок вас игнорирует" player-offline: "&#CF6679Игрок не в сети" @@ -51,12 +51,12 @@ pm: ignore: added: "⚖C9Игрок ẲE9{player} ⚖C9добавлен в игнор" removed: "⚖C9Игрок ẲE9{player} ⚖C9убран из игнора" - already-ignored: "&#B798A8Игрок ẲE9{player} &#B798A8уже в игноре" - not-ignored: "&#B798A8Игрок ẲE9{player} &#B798A8не в игноре" + already-ignored: "ẲE9Игрок ẲE9{player} ẲE9уже в игноре" + not-ignored: "ẲE9Игрок ẲE9{player} ẲE9не в игноре" self: "&#CF6679Нельзя игнорировать самого себя" - list-empty: "&#B798A8Ваш список игнора пуст" + list-empty: "ẲE9Ваш список игнора пуст" list-header: "ẲE9▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬" - list-footer: "&#B798A8Всего: ẲE9{count} &#B798A8игроков" + list-footer: "ẲE9Всего: ẲE9{count} ẲE9игроков" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Announcements │ @@ -71,9 +71,9 @@ announce: spy: enabled: "⚖C9Режим шпиона ẲE9включён" - disabled: "⚖C9Режим шпиона &#B798A8отключён" - format: "&#B798A8[⚖C9SPY&#B798A8] ẲE9{sender} &#B798A8→ ⚖C9{receiver}&#B798A8: &f{message}" - chat-format: "&#B798A8[⚖C9SPY&#B798A8] &#B798A8{type} ẲE9{sender}&#B798A8: &f{message}" + disabled: "⚖C9Режим шпиона ẲE9отключён" + format: "ẲE9[⚖C9SPYẲE9] ẲE9{sender} ẲE9→ ⚖C9{receiver}ẲE9: &f{message}" + chat-format: "ẲE9[⚖C9SPYẲE9] ẲE9{type} ẲE9{sender}ẲE9: &f{message}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Message filters │ @@ -81,14 +81,14 @@ spy: filter: blocked: "&#CF6679Сообщение заблокировано" - warning: "&#B798A8Предупреждение" + warning: "ẲE9Предупреждение" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Cooldowns │ # └─────────────────────────────────────────────────────────────────────────────┘ cooldown: - wait: "&#B798A8Подождите ẲE9{time} &#B798A8сек" + wait: "ẲE9Подождите ẲE9{time} ẲE9сек" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Anti-spam (caps, repeat, similar messages) │ @@ -104,7 +104,7 @@ antispam: # └─────────────────────────────────────────────────────────────────────────────┘ clearchat: - cleared: "⚖C9Чат очищен &#B798A8администратором ẲE9{player}" + cleared: "⚖C9Чат очищен ẲE9администратором ẲE9{player}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Command / usage errors │ @@ -113,7 +113,7 @@ clearchat: errors: no-permission: "&#CF6679Нет прав" player-not-found: "&#CF6679Игрок не найден" - invalid-usage: "&#B798A8Использование: ẲE9{usage}" + invalid-usage: "ẲE9Использование: ẲE9{usage}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ General (permissions, reload, hub, etc.) │ @@ -124,7 +124,7 @@ general: players-only: "&#CF6679Только для игроков!" config-reloaded: "⚖C9Конфиг ẲE9перезагружен!" invalid-player: "&#CF6679Игрок ẲE9{player} &#CF6679не найден!" - cooldown-active: "&#B798A8Подождите ẲE9{time}с" + cooldown-active: "ẲE9Подождите ẲE9{time}с" command-disabled: "&#CF6679Команда отключена!" hub: @@ -137,9 +137,9 @@ hub: mute: muted: "⚖C9Игрок ẲE9{player} ⚖C9замучен на ẲE9{time}" unmuted: "⚖C9Игрок ẲE9{player} ⚖C9размучен" - already-muted: "&#B798A8Игрок ẲE9{player} &#B798A8уже замучен" - not-muted: "&#B798A8Игрок ẲE9{player} &#B798A8не замучен" - you-muted: "&#CF6679Вы замучены! &#B798A8Осталось: ẲE9{time}" + already-muted: "ẲE9Игрок ẲE9{player} ẲE9уже замучен" + not-muted: "ẲE9Игрок ẲE9{player} ẲE9не замучен" + you-muted: "&#CF6679Вы замучены! ẲE9Осталось: ẲE9{time}" permanent: "&#CF6679Вы замучены навсегда!" # ┌─────────────────────────────────────────────────────────────────────────────┐ @@ -149,32 +149,32 @@ mute: gradient: color-set: "⚖C9Градиент ẲE9установлен!" color-enabled: "⚖C9Градиентный ник ẲE9включён" - color-disabled: "&#B798A8Градиентный ник ⚖C9отключён" - color-reset: "&#B798A8Градиент сброшен" + color-disabled: "ẲE9Градиентный ник ⚖C9отключён" + color-reset: "ẲE9Градиент сброшен" prefix-set: "⚖C9Префикс ẲE9установлен!" - prefix-reset: "&#B798A8Префикс сброшен" + prefix-reset: "ẲE9Префикс сброшен" prefix-reset-success: "⚖C9Префикс успешно ẲE9сброшен!" prefix-enabled: "⚖C9Префикс ẲE9включён" - prefix-disabled: "&#B798A8Префикс ⚖C9отключён" + prefix-disabled: "ẲE9Префикс ⚖C9отключён" invalid-hex: "&#CF6679Неверный HEX цвет: ẲE9{color}" invalid-color: "&#CF6679Неверный цвет: ẲE9{color}" too-many-colors: "&#CF6679Слишком много цветов! Максимум: ẲE9{max}" too-few-colors: "&#CF6679Слишком мало цветов! Минимум: ẲE9{min}" not-enough-colors: "&#CF6679Слишком мало цветов! Минимум: ẲE9{min}" - cooldown: "&#B798A8Подождите ẲE9{time} &#B798A8сек" + cooldown: "ẲE9Подождите ẲE9{time} ẲE9сек" not-enough-points: "&#CF6679Недостаточно поинтов! Нужно: ẲE9{price}" not-enough-points-color: "&#CF6679Недостаточно поинтов! Нужно: ẲE9{price}" not-enough-points-prefix: "&#CF6679Недостаточно поинтов! Нужно: ẲE9{price}" player-not-found: "&#CF6679Игрок не найден" - color-help: "&#B798A8Использование: ẲE9/color " + color-help: "ẲE9Использование: ẲE9/color " color-success: "⚖C9Градиент успешно ẲE9установлен!" - color-no-colors: "&#B798A8У вас нет установленных цветов" - color-copy-usage: "&#B798A8Использование: ẲE9/color copy <игрок>" - color-copy-no-colors: "&#B798A8У игрока ẲE9{player} &#B798A8нет цветов" - prefix-help: "&#B798A8Использование: ẲE9/prefix <текст> &#B798A8или ẲE9/prefix reset" - prefix-success: "⚖C9Префикс установлен! &#B798A8Стоимость: ẲE9{price} &#B798A8поинтов" + color-no-colors: "ẲE9У вас нет установленных цветов" + color-copy-usage: "ẲE9Использование: ẲE9/color copy <игрок>" + color-copy-no-colors: "ẲE9У игрока ẲE9{player} ẲE9нет цветов" + prefix-help: "ẲE9Использование: ẲE9/prefix <текст> ẲE9или ẲE9/prefix reset" + prefix-success: "⚖C9Префикс установлен! ẲE9Стоимость: ẲE9{price} ẲE9поинтов" prefix-success-free: "⚖C9Префикс ẲE9установлен!" - prefix-no-prefix: "&#B798A8У вас нет префикса" + prefix-no-prefix: "ẲE9У вас нет префикса" prefix-too-long: "&#CF6679Префикс слишком длинный! Максимум: ẲE9{max}" prefix-no-colors: "&#CF6679Префикс не может содержать цветовые коды!" prefix-invalid-chars: "&#CF6679Недопустимые символы в префиксе!" @@ -182,30 +182,30 @@ gradient: only-player: "&#CF6679Только для игроков!" no-permission: "&#CF6679Нет прав!" reload-success: "⚖C9Конфигурация ẲE9перезагружена!" - info-header: "ẲE9▬▬▬▬▬▬▬▬ &#B798A8{player} ẲE9▬▬▬▬▬▬▬▬" - info-prefix: "&#B798A8Префикс: &f{prefix}" - info-prefix-enabled: "&#B798A8Префикс активен: &f{enabled}" - info-colors: "&#B798A8Цвета: &f{colors}" - info-color-enabled: "&#B798A8Цвет активен: &f{enabled}" - info-prefix-purchased: "&#B798A8Куплен: &f{purchased}" + info-header: "ẲE9▬▬▬▬▬▬▬▬ ẲE9{player} ẲE9▬▬▬▬▬▬▬▬" + info-prefix: "ẲE9Префикс: &f{prefix}" + info-prefix-enabled: "ẲE9Префикс активен: &f{enabled}" + info-colors: "ẲE9Цвета: &f{colors}" + info-color-enabled: "ẲE9Цвет активен: &f{enabled}" + info-prefix-purchased: "ẲE9Куплен: &f{purchased}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ Custom nicknames (/nick) │ # └─────────────────────────────────────────────────────────────────────────────┘ nick: - current: "&#B798A8Ваш ник: ẲE9{nick}" - usage: "&#B798A8Использование: ẲE9/nick <ник> &#B798A8или ẲE9/nick reset" - reset-usage: "&#B798A8Используйте ẲE9/nick reset &#B798A8для сброса" - set-usage: "&#B798A8Используйте ẲE9/nick <ник> &#B798A8для установки" - no-nick: "&#B798A8У вас нет кастомного ника" + current: "ẲE9Ваш ник: ẲE9{nick}" + usage: "ẲE9Использование: ẲE9/nick <ник> ẲE9или ẲE9/nick reset" + reset-usage: "ẲE9Используйте ẲE9/nick reset ẲE9для сброса" + set-usage: "ẲE9Используйте ẲE9/nick <ник> ẲE9для установки" + no-nick: "ẲE9У вас нет кастомного ника" reset-success: "⚖C9Ник ẲE9сброшен!" set-success: "⚖C9Ник изменён на: &f{nick}" invalid: "&#CF6679Невалидный ник!" - requirements-header: "&#B798A8Требования:" + requirements-header: "ẲE9Требования:" requirements-length: "⚖C9◆ &fДлина: 3-16 символов" requirements-chars: "⚖C9◆ &fБуквы, цифры, _, русские буквы" - requirements-colors: "⚖C9◆ &fЦвета: ẲE9&#RRGGBB&#B798A8текст" + requirements-colors: "⚖C9◆ &fЦвета: ẲE9&#RRGGBBẲE9текст" taken: "&#CF6679Этот ник уже занят!" error: "&#CF6679Не удалось установить ник!" @@ -214,12 +214,12 @@ nick: # └─────────────────────────────────────────────────────────────────────────────┘ rp: - me: "* {player} {action}" - try-format: "* {player} пытается {action}... {result}" + me: "* {player} {action}" + try-format: "* {player} пытается {action}... {result}" try-success: "[Успех!]" try-fail: "[Провал!]" - do-format: "[Сцена] {description} ({player})" - roll-format: "* {player} бросает кубик и получает {result} из {max}" + do-format: "[Сцена] {description} ({player})" + roll-format: "* {player} бросает кубик и получает {result} из {max}" roll-min-error: "Минимальное значение кубика: 2" roll-max-error: "Максимальное значение: 1000000" roll-invalid-number: "Укажите число: /roll [максимум]"