refactor: Remove all legacy managers, complete SOLID migration
This commit is contained in:
parent
74482851aa
commit
4ee5393a5e
14 changed files with 147 additions and 1151 deletions
0
496
Normal file
0
496
Normal file
|
|
@ -42,18 +42,20 @@
|
||||||
|
|
||||||
### Размер JAR
|
### Размер JAR
|
||||||
- **До очистки:** 508 829 байт
|
- **До очистки:** 508 829 байт
|
||||||
- **После очистки:** 496 726 байт
|
- **После удаления legacy:** 473 495 байт
|
||||||
- **Экономия:** 12 103 байт (2.4%)
|
- **Экономия:** 35 334 байт (6.9%)
|
||||||
|
|
||||||
### Строки кода
|
### Строки кода
|
||||||
- **Удалено:** ~620 строк
|
- **Удалено эмодзи и фильтров:** ~620 строк
|
||||||
- **Добавлено:** ~35 строк (новый ChatEventListener)
|
- **Удалено legacy менеджеров:** 998 строк
|
||||||
- **Чистая экономия:** ~585 строк
|
- **Удалено тестов legacy:** 97 строк
|
||||||
|
- **Добавлено новых сервисов:** ~800 строк
|
||||||
|
- **Чистая экономия:** ~915 строк
|
||||||
|
|
||||||
### Файлы
|
### Файлы
|
||||||
- **Удалено:** 7 файлов
|
- **Удалено:** 16 файлов (7 эмодзи/фильтры + 7 менеджеры + 2 теста)
|
||||||
- **Создано:** 1 файл (ChatEventListener)
|
- **Создано:** 19 файлов (9 интерфейсов + 9 реализаций + 1 слушатель)
|
||||||
- **Чистая экономия:** 6 файлов
|
- **Чистая разница:** +3 файла (но лучшая структура)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -87,57 +89,54 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ Legacy код (ещё остался)
|
## ✅ Legacy код удалён
|
||||||
|
|
||||||
### Менеджеры (нужно мигрировать)
|
### Удалённые менеджеры (заменены на сервисы)
|
||||||
```
|
```
|
||||||
⚠️ MuteManager.java (409 строк) - используется в командах
|
✅ MuteManager.java (447 строк) → MuteService
|
||||||
⚠️ ChatManager.java (131 строка) - используется в командах
|
✅ ChatManager.java (162 строки) → ChatService
|
||||||
⚠️ AntiSpamManager.java - используется в командах
|
✅ CooldownManager.java (45 строк) → CooldownService
|
||||||
⚠️ CooldownManager.java - используется в командах
|
✅ PMManager.java (27 строк) → PMService
|
||||||
⚠️ PMManager.java - используется в командах
|
✅ IgnoreManager.java (105 строк) → IgnoreService
|
||||||
⚠️ IgnoreManager.java - используется в командах
|
✅ SpyManager.java (71 строка) → SpyService
|
||||||
⚠️ SpyManager.java - используется в командах
|
✅ MentionManager.java (141 строка) → MentionService
|
||||||
⚠️ MentionManager.java (123 строки) - используется в командах
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Команды (используют legacy менеджеры)
|
**Экономия:** 998 строк кода
|
||||||
|
|
||||||
|
### Команды (мигрированы на новые сервисы)
|
||||||
```
|
```
|
||||||
⚠️ MuteCommand.java
|
✅ MuteCommand.java → MuteService
|
||||||
⚠️ UnmuteCommand.java
|
✅ UnmuteCommand.java → MuteService
|
||||||
⚠️ MuteListCommand.java
|
✅ MuteListCommand.java → MuteService
|
||||||
⚠️ MuteHistoryCommand.java
|
✅ MuteHistoryCommand.java → MuteService
|
||||||
⚠️ MuteBlameCommand.java
|
✅ MuteBlameCommand.java → MuteService
|
||||||
⚠️ GlobalChatCommand.java
|
✅ GlobalChatCommand.java → ChatService
|
||||||
⚠️ LocalChatCommand.java
|
✅ LocalChatCommand.java → ChatService
|
||||||
⚠️ MsgCommand.java
|
✅ MsgCommand.java → PMService, IgnoreService
|
||||||
⚠️ ReplyCommand.java
|
✅ ReplyCommand.java → PMService, IgnoreService
|
||||||
⚠️ IgnoreCommand.java
|
✅ IgnoreCommand.java → IgnoreService
|
||||||
⚠️ UnignoreCommand.java
|
✅ UnignoreCommand.java → IgnoreService
|
||||||
⚠️ ChatSpyCommand.java
|
✅ ChatSpyCommand.java → SpyService
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 Следующие шаги (по плану REFACTORING_PLAN.md)
|
## 🎯 Следующие шаги
|
||||||
|
|
||||||
### Этап 4: Создать новые сервисы
|
### ✅ Завершено
|
||||||
1. [ ] PMService - личные сообщения
|
1. ✅ Удалены эмодзи (320+ строк)
|
||||||
2. [ ] IgnoreService - игнорирование
|
2. ✅ Удалена фильтрация чата (150+ строк)
|
||||||
3. [ ] SpyService - шпионаж
|
3. ✅ Создана SOLID архитектура (ServiceRegistry + DI)
|
||||||
4. [ ] MentionService - упоминания
|
4. ✅ Созданы все сервисы (9 интерфейсов + 9 реализаций)
|
||||||
|
5. ✅ Мигрированы все команды на новые сервисы
|
||||||
|
6. ✅ Удалены все legacy менеджеры (998 строк)
|
||||||
|
7. ✅ Плагин собирается и работает
|
||||||
|
|
||||||
### Этап 5: Мигрировать команды
|
### 🔄 Опционально (для дальнейшей оптимизации)
|
||||||
1. [ ] Mute команды (5 штук) → MuteService
|
1. [ ] Разбить ConfigManager на категории (если станет >200 строк)
|
||||||
2. [ ] Chat команды (2 штуки) → ChatService
|
2. [ ] Добавить больше unit-тестов для новых сервисов
|
||||||
3. [ ] PM команды (2 штуки) → PMService
|
3. [ ] Интеграционные тесты для полного флоу
|
||||||
4. [ ] Ignore команды (2 штуки) → IgnoreService
|
|
||||||
5. [ ] Spy команды (1 штука) → SpyService
|
|
||||||
|
|
||||||
### Этап 6: Удалить legacy
|
|
||||||
1. [ ] Удалить 8 legacy менеджеров (~1500 строк)
|
|
||||||
2. [ ] Очистить LoChat.java от legacy геттеров
|
|
||||||
3. [ ] Оптимизировать большие классы
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -172,14 +171,18 @@
|
||||||
2. Удалена фильтрация чата (150+ строк)
|
2. Удалена фильтрация чата (150+ строк)
|
||||||
3. Удалён старый ChatListener (185 строк)
|
3. Удалён старый ChatListener (185 строк)
|
||||||
4. Создана новая архитектура (ServiceRegistry + DI)
|
4. Создана новая архитектура (ServiceRegistry + DI)
|
||||||
5. Плагин собирается и работает
|
5. Созданы все сервисы (9 интерфейсов + 9 реализаций)
|
||||||
6. Все тесты проходят
|
6. Мигрированы все команды на новые сервисы
|
||||||
|
7. Удалены все legacy менеджеры (998 строк)
|
||||||
|
8. Плагин собирается и работает
|
||||||
|
9. Все тесты проходят (37 тестов)
|
||||||
|
|
||||||
### 🎯 Результат
|
### 🎯 Результат
|
||||||
- **Код стал чище:** -585 строк
|
- **Код стал чище:** -915 строк чистой экономии
|
||||||
- **JAR стал меньше:** -12 KB
|
- **JAR стал меньше:** -35 KB (6.9%)
|
||||||
- **Архитектура улучшена:** SOLID принципы
|
- **Архитектура улучшена:** SOLID принципы + DI
|
||||||
- **Готово к дальнейшему рефакторингу**
|
- **Все legacy менеджеры удалены**
|
||||||
|
- **Все команды используют новые сервисы**
|
||||||
|
|
||||||
### 📋 Следующий шаг
|
### 📋 Рефакторинг завершён
|
||||||
Создать 4 новых сервиса (PM, Ignore, Spy, Mention) и начать миграцию команд согласно плану в `REFACTORING_PLAN.md`.
|
Все основные задачи из `REFACTORING_PLAN.md` выполнены. Плагин полностью переведён на новую архитектуру.
|
||||||
|
|
|
||||||
BIN
bin/main/com/loki/lochat/listeners/UniversalChatListener.class
Normal file
BIN
bin/main/com/loki/lochat/listeners/UniversalChatListener.class
Normal file
Binary file not shown.
BIN
bin/main/com/loki/lochat/utils/AdventureManager.class
Normal file
BIN
bin/main/com/loki/lochat/utils/AdventureManager.class
Normal file
Binary file not shown.
|
|
@ -1,162 +0,0 @@
|
||||||
package com.loki.lochat.managers;
|
|
||||||
|
|
||||||
import com.loki.lochat.LoChat;
|
|
||||||
import com.loki.lochat.api.service.MentionService;
|
|
||||||
import com.loki.lochat.utils.ChatFormatter;
|
|
||||||
import com.loki.lochat.utils.DistanceUtil;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Менеджер чата - использует MentionService для обработки упоминаний
|
|
||||||
*/
|
|
||||||
public class ChatManager {
|
|
||||||
|
|
||||||
private final LoChat plugin;
|
|
||||||
private final MentionService mentionService;
|
|
||||||
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
|
|
||||||
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
|
|
||||||
private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText();
|
|
||||||
|
|
||||||
public ChatManager(LoChat plugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.mentionService = plugin.getServiceRegistry().get(MentionService.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendGlobalMessage(Player sender, Component message) {
|
|
||||||
if (!plugin.getConfigManager().isGlobalEnabled()) {
|
|
||||||
sender.sendMessage(plugin.getMessageConfig().getComponent("global.disabled"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Конвертируем Component в String для обработки упоминаний
|
|
||||||
String messageText = PLAIN.serialize(message);
|
|
||||||
|
|
||||||
// Обработка упоминаний @ник
|
|
||||||
Set<Player> mentionedPlayers = new HashSet<>();
|
|
||||||
String processedMessage = mentionService.processMentions(messageText, mentionedPlayers);
|
|
||||||
|
|
||||||
String format = plugin.getConfigManager().getGlobalFormat();
|
|
||||||
String prefix = plugin.getConfigManager().getGlobalPrefix();
|
|
||||||
|
|
||||||
// Получаем отображаемое имя игрока (с градиентом если есть)
|
|
||||||
String playerDisplay = getPlayerDisplay(sender);
|
|
||||||
|
|
||||||
// Отправка каждому игроку персонализированное сообщение
|
|
||||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
|
||||||
if (isGlobalChatDisabled(player.getUniqueId())) continue;
|
|
||||||
|
|
||||||
// Персонализируем сообщение для этого игрока (выделяем его ник)
|
|
||||||
String personalizedMsg = mentionService.getPersonalizedMessage(processedMessage, player);
|
|
||||||
|
|
||||||
// Формируем итоговую строку
|
|
||||||
String formatted = format
|
|
||||||
.replace("<prefix>", prefix)
|
|
||||||
.replace("<player>", playerDisplay)
|
|
||||||
.replace("<message>", personalizedMsg);
|
|
||||||
|
|
||||||
// Парсим в Component и отправляем
|
|
||||||
Component component = MINI_MESSAGE.deserialize(formatted);
|
|
||||||
player.sendMessage(component);
|
|
||||||
|
|
||||||
// Проверяем упоминание ника без @ и уведомляем
|
|
||||||
mentionService.notifyIfMentioned(messageText, player, sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Уведомление упомянутых через @
|
|
||||||
mentionService.notifyMentioned(mentionedPlayers);
|
|
||||||
|
|
||||||
plugin.getLogger().info("[G] " + sender.getName() + ": " + ChatFormatter.stripTags(messageText));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendLocalMessage(Player sender, Component message) {
|
|
||||||
if (!plugin.getConfigManager().isLocalEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Конвертируем Component в String для обработки упоминаний
|
|
||||||
String messageText = PLAIN.serialize(message);
|
|
||||||
|
|
||||||
// Обработка упоминаний @ник
|
|
||||||
Set<Player> mentionedPlayers = new HashSet<>();
|
|
||||||
String processedMessage = mentionService.processMentions(messageText, mentionedPlayers);
|
|
||||||
|
|
||||||
int radius = plugin.getConfigManager().getLocalRadius();
|
|
||||||
String format = plugin.getConfigManager().getLocalFormat();
|
|
||||||
String prefix = plugin.getConfigManager().getLocalPrefix();
|
|
||||||
|
|
||||||
// Получаем отображаемое имя игрока (с градиентом если есть)
|
|
||||||
String playerDisplay = getPlayerDisplay(sender);
|
|
||||||
|
|
||||||
boolean foundRecipient = false;
|
|
||||||
Set<Player> recipients = new HashSet<>();
|
|
||||||
|
|
||||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
|
||||||
if (DistanceUtil.isInRange(sender, player, radius)) {
|
|
||||||
// Персонализируем сообщение для этого игрока
|
|
||||||
String personalizedMsg = mentionService.getPersonalizedMessage(processedMessage, player);
|
|
||||||
|
|
||||||
// Формируем итоговую строку
|
|
||||||
String formatted = prefix + " " + format
|
|
||||||
.replace("<player>", playerDisplay)
|
|
||||||
.replace("<message>", personalizedMsg);
|
|
||||||
|
|
||||||
// Парсим в Component и отправляем
|
|
||||||
Component component = MINI_MESSAGE.deserialize(formatted);
|
|
||||||
player.sendMessage(component);
|
|
||||||
|
|
||||||
recipients.add(player);
|
|
||||||
|
|
||||||
// Проверяем упоминание ника без @
|
|
||||||
mentionService.notifyIfMentioned(messageText, player, sender);
|
|
||||||
|
|
||||||
if (!player.equals(sender)) {
|
|
||||||
foundRecipient = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Уведомление упомянутых через @ (только тех кто в радиусе)
|
|
||||||
mentionedPlayers.retainAll(recipients);
|
|
||||||
mentionService.notifyMentioned(mentionedPlayers);
|
|
||||||
|
|
||||||
if (!foundRecipient) {
|
|
||||||
sender.sendMessage(plugin.getMessageConfig().getComponent("local.no-players"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получает отображаемое имя игрока с градиентом (если включено)
|
|
||||||
* Возвращает строку в MiniMessage формате
|
|
||||||
*/
|
|
||||||
private String getPlayerDisplay(Player player) {
|
|
||||||
if (plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) {
|
|
||||||
String name = plugin.getGradientModule().getFormattedName(player);
|
|
||||||
// Конвертируем &#RRGGBB формат в MiniMessage
|
|
||||||
return ChatFormatter.convertAllColors(name);
|
|
||||||
}
|
|
||||||
return player.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean toggleGlobalChat(UUID player) {
|
|
||||||
if (globalChatDisabled.contains(player)) {
|
|
||||||
globalChatDisabled.remove(player);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
globalChatDisabled.add(player);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isGlobalChatDisabled(UUID player) {
|
|
||||||
return globalChatDisabled.contains(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package com.loki.lochat.managers;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class CooldownManager {
|
|
||||||
|
|
||||||
private final Map<UUID, Long> globalCooldowns = new ConcurrentHashMap<>();
|
|
||||||
private final Map<UUID, Long> localCooldowns = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public boolean isOnCooldown(UUID player, String chatType, int cooldownSeconds) {
|
|
||||||
Map<UUID, Long> cooldowns = chatType.equals("global") ? globalCooldowns : localCooldowns;
|
|
||||||
Long lastTime = cooldowns.get(player);
|
|
||||||
|
|
||||||
if (lastTime == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
|
|
||||||
return elapsed < cooldownSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRemainingCooldown(UUID player, String chatType, int cooldownSeconds) {
|
|
||||||
Map<UUID, Long> cooldowns = chatType.equals("global") ? globalCooldowns : localCooldowns;
|
|
||||||
Long lastTime = cooldowns.get(player);
|
|
||||||
|
|
||||||
if (lastTime == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
|
|
||||||
return Math.max(0, cooldownSeconds - (int) elapsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCooldown(UUID player, String chatType) {
|
|
||||||
Map<UUID, Long> cooldowns = chatType.equals("global") ? globalCooldowns : localCooldowns;
|
|
||||||
cooldowns.put(player, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeCooldown(UUID player) {
|
|
||||||
globalCooldowns.remove(player);
|
|
||||||
localCooldowns.remove(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
package com.loki.lochat.managers;
|
|
||||||
|
|
||||||
import com.loki.lochat.LoChat;
|
|
||||||
import com.loki.lochat.gradient.util.FoliaUtil;
|
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class IgnoreManager {
|
|
||||||
|
|
||||||
private final LoChat plugin;
|
|
||||||
private final File ignoreFile;
|
|
||||||
private final Map<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public IgnoreManager(LoChat plugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.ignoreFile = new File(plugin.getDataFolder(), "ignores.yml");
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load() {
|
|
||||||
if (!ignoreFile.exists()) return;
|
|
||||||
|
|
||||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(ignoreFile);
|
|
||||||
for (String key : config.getKeys(false)) {
|
|
||||||
try {
|
|
||||||
UUID uuid = UUID.fromString(key);
|
|
||||||
List<String> ignoredList = config.getStringList(key);
|
|
||||||
Set<UUID> ignored = ConcurrentHashMap.newKeySet();
|
|
||||||
|
|
||||||
for (String ignoredStr : ignoredList) {
|
|
||||||
try {
|
|
||||||
ignored.add(UUID.fromString(ignoredStr));
|
|
||||||
} catch (Exception ignored2) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ignored.isEmpty()) {
|
|
||||||
ignoreMap.put(uuid, ignored);
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
YamlConfiguration config = new YamlConfiguration();
|
|
||||||
|
|
||||||
for (Map.Entry<UUID, Set<UUID>> entry : ignoreMap.entrySet()) {
|
|
||||||
List<String> ignoredList = new ArrayList<>();
|
|
||||||
for (UUID ignored : entry.getValue()) {
|
|
||||||
ignoredList.add(ignored.toString());
|
|
||||||
}
|
|
||||||
config.set(entry.getKey().toString(), ignoredList);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
config.save(ignoreFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().severe("Не удалось сохранить игноры: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIgnoring(UUID player, UUID target) {
|
|
||||||
Set<UUID> ignored = ignoreMap.get(player);
|
|
||||||
return ignored != null && ignored.contains(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addIgnore(UUID player, UUID target) {
|
|
||||||
Set<UUID> ignored = ignoreMap.computeIfAbsent(player, k -> ConcurrentHashMap.newKeySet());
|
|
||||||
boolean added = ignored.add(target);
|
|
||||||
if (added) saveAsync();
|
|
||||||
return added;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeIgnore(UUID player, UUID target) {
|
|
||||||
Set<UUID> ignored = ignoreMap.get(player);
|
|
||||||
if (ignored == null) return false;
|
|
||||||
boolean removed = ignored.remove(target);
|
|
||||||
if (removed) saveAsync();
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<UUID> getIgnoredPlayers(UUID player) {
|
|
||||||
return ignoreMap.getOrDefault(player, Collections.emptySet());
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIgnoredCount(UUID player) {
|
|
||||||
Set<UUID> ignored = ignoreMap.get(player);
|
|
||||||
return ignored == null ? 0 : ignored.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearIgnores(UUID player) {
|
|
||||||
ignoreMap.remove(player);
|
|
||||||
saveAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Асинхронное сохранение данных (для Folia совместимости)
|
|
||||||
*/
|
|
||||||
private void saveAsync() {
|
|
||||||
FoliaUtil.runAsync(plugin, this::save);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
package com.loki.lochat.managers;
|
|
||||||
|
|
||||||
import com.loki.lochat.LoChat;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Sound;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class MentionManager {
|
|
||||||
|
|
||||||
private final LoChat plugin;
|
|
||||||
private static final Pattern MENTION_PATTERN = Pattern.compile("@(\\w+)");
|
|
||||||
|
|
||||||
// Хранит для какого игрока какие части сообщения выделить
|
|
||||||
private final Map<Player, Set<String>> highlightedNicks = new HashMap<>();
|
|
||||||
|
|
||||||
public MentionManager(LoChat plugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Обрабатывает упоминания в сообщении
|
|
||||||
* Возвращает сообщение с подсвеченными никами
|
|
||||||
*/
|
|
||||||
public String processMentions(String message, Set<Player> mentionedPlayers) {
|
|
||||||
if (!plugin.getConfigManager().isMentionsEnabled()) {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
highlightedNicks.clear();
|
|
||||||
String highlight = plugin.getConfigManager().getMentionHighlight();
|
|
||||||
Matcher matcher = MENTION_PATTERN.matcher(message);
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
|
|
||||||
while (matcher.find()) {
|
|
||||||
String name = matcher.group(1);
|
|
||||||
Player mentioned = Bukkit.getPlayer(name);
|
|
||||||
|
|
||||||
if (mentioned != null && mentioned.isOnline()) {
|
|
||||||
mentionedPlayers.add(mentioned);
|
|
||||||
String replacement = highlight.replace("{player}", mentioned.getName());
|
|
||||||
matcher.appendReplacement(result, replacement);
|
|
||||||
|
|
||||||
// Запоминаем что этот игрок упомянут
|
|
||||||
highlightedNicks.computeIfAbsent(mentioned, k -> new HashSet<>()).add(mentioned.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matcher.appendTail(result);
|
|
||||||
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Создаёт персонализированное сообщение для игрока
|
|
||||||
* Если его ник упомянут — выделяет его особым образом
|
|
||||||
*/
|
|
||||||
public String getPersonalizedMessage(String message, Player viewer) {
|
|
||||||
if (!plugin.getConfigManager().isMentionsEnabled()) {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
String viewerName = viewer.getName();
|
|
||||||
String result = message;
|
|
||||||
|
|
||||||
// Проверяем упоминание через @ник
|
|
||||||
String highlight = plugin.getConfigManager().getMentionHighlight();
|
|
||||||
String highlightedName = highlight.replace("{player}", viewerName);
|
|
||||||
|
|
||||||
// Если в сообщении есть выделенный ник этого игрока — добавляем дополнительное выделение
|
|
||||||
if (result.contains(highlightedName)) {
|
|
||||||
// Уже выделено через processMentions, добавляем эффект
|
|
||||||
String extraHighlight = "<bold>" + highlightedName + "</bold>";
|
|
||||||
result = result.replace(highlightedName, extraHighlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Также проверяем просто ник в сообщении (без @)
|
|
||||||
// Регистронезависимый поиск
|
|
||||||
Pattern nickPattern = Pattern.compile("(?i)\\b" + Pattern.quote(viewerName) + "\\b");
|
|
||||||
Matcher nickMatcher = nickPattern.matcher(result);
|
|
||||||
|
|
||||||
if (nickMatcher.find()) {
|
|
||||||
// Ник найден в сообщении — выделяем для этого игрока
|
|
||||||
String selfHighlight = plugin.getConfigManager().getSelfMentionHighlight();
|
|
||||||
result = nickMatcher.replaceAll(selfHighlight.replace("{player}", viewerName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Проверяет, упомянут ли игрок в сообщении (по нику без @)
|
|
||||||
*/
|
|
||||||
public boolean isPlayerMentioned(String message, Player player) {
|
|
||||||
String name = player.getName();
|
|
||||||
Pattern pattern = Pattern.compile("(?i)\\b" + Pattern.quote(name) + "\\b");
|
|
||||||
return pattern.matcher(message).find();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Уведомляет упомянутых игроков звуком
|
|
||||||
*/
|
|
||||||
public void notifyMentioned(Set<Player> players) {
|
|
||||||
if (!plugin.getConfigManager().isMentionSoundEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String soundName = plugin.getConfigManager().getMentionSoundType();
|
|
||||||
Sound sound;
|
|
||||||
try {
|
|
||||||
sound = Sound.valueOf(soundName);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
sound = Sound.BLOCK_NOTE_BLOCK_PLING;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Player player : players) {
|
|
||||||
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Уведомляет игрока если его ник упомянут (без @)
|
|
||||||
*/
|
|
||||||
public void notifyIfMentioned(String message, Player viewer, Player sender) {
|
|
||||||
if (viewer.equals(sender)) return;
|
|
||||||
if (!plugin.getConfigManager().isMentionSoundEnabled()) return;
|
|
||||||
|
|
||||||
if (isPlayerMentioned(message, viewer)) {
|
|
||||||
String soundName = plugin.getConfigManager().getMentionSoundType();
|
|
||||||
try {
|
|
||||||
Sound sound = Sound.valueOf(soundName);
|
|
||||||
viewer.playSound(viewer.getLocation(), sound, 1.0f, 1.0f);
|
|
||||||
} catch (IllegalArgumentException ignored) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,447 +0,0 @@
|
||||||
package com.loki.lochat.managers;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import com.loki.lochat.LoChat;
|
|
||||||
import com.loki.lochat.gradient.util.FoliaUtil;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Менеджер мутов для LoChat
|
|
||||||
*/
|
|
||||||
public class MuteManager {
|
|
||||||
|
|
||||||
private final LoChat plugin;
|
|
||||||
private final Map<UUID, MuteData> mutes = new ConcurrentHashMap<>();
|
|
||||||
private final Map<UUID, List<MuteHistoryEntry>> history = new ConcurrentHashMap<>();
|
|
||||||
private final File dataFile;
|
|
||||||
private final File historyFile;
|
|
||||||
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
|
||||||
|
|
||||||
public MuteManager(LoChat plugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.dataFile = new File(plugin.getDataFolder(), "mutes.json");
|
|
||||||
this.historyFile = new File(plugin.getDataFolder(), "mute-history.json");
|
|
||||||
load();
|
|
||||||
loadHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Замутить игрока
|
|
||||||
* @param uuid UUID игрока
|
|
||||||
* @param playerName имя игрока
|
|
||||||
* @param duration длительность в миллисекундах (0 = перманентный)
|
|
||||||
* @param reason причина
|
|
||||||
* @param mutedBy кто замутил
|
|
||||||
*/
|
|
||||||
public void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy) {
|
|
||||||
long endTime = duration > 0 ? System.currentTimeMillis() + duration : 0;
|
|
||||||
MuteData data = new MuteData(endTime, reason, mutedBy, System.currentTimeMillis(), playerName);
|
|
||||||
mutes.put(uuid, data);
|
|
||||||
|
|
||||||
// Добавляем в историю
|
|
||||||
addToHistory(uuid, playerName, duration, reason, mutedBy);
|
|
||||||
|
|
||||||
saveAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Замутить игрока (без имени - для совместимости)
|
|
||||||
*/
|
|
||||||
public void mute(UUID uuid, long duration, String reason, String mutedBy) {
|
|
||||||
mute(uuid, null, duration, reason, mutedBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Размутить игрока
|
|
||||||
* @param uuid UUID игрока
|
|
||||||
* @param unmutedBy кто размутил (null если автоматически)
|
|
||||||
* @return true если игрок был замучен
|
|
||||||
*/
|
|
||||||
public boolean unmute(UUID uuid, String unmutedBy) {
|
|
||||||
MuteData removed = mutes.remove(uuid);
|
|
||||||
if (removed != null) {
|
|
||||||
// Обновляем историю - помечаем как размученный
|
|
||||||
updateHistoryUnmute(uuid, unmutedBy);
|
|
||||||
saveAsync();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Размутить игрока (для совместимости)
|
|
||||||
*/
|
|
||||||
public boolean unmute(UUID uuid) {
|
|
||||||
return unmute(uuid, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Проверить, замучен ли игрок
|
|
||||||
*/
|
|
||||||
public boolean isMuted(UUID uuid) {
|
|
||||||
MuteData data = mutes.get(uuid);
|
|
||||||
if (data == null) return false;
|
|
||||||
|
|
||||||
// Проверяем истёк ли мут
|
|
||||||
if (data.endTime > 0 && System.currentTimeMillis() > data.endTime) {
|
|
||||||
mutes.remove(uuid);
|
|
||||||
saveAsync();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получить данные мута
|
|
||||||
*/
|
|
||||||
public MuteData getMuteData(UUID uuid) {
|
|
||||||
if (!isMuted(uuid)) return null;
|
|
||||||
return mutes.get(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получить оставшееся время мута
|
|
||||||
*/
|
|
||||||
public long getRemainingTime(UUID uuid) {
|
|
||||||
MuteData data = mutes.get(uuid);
|
|
||||||
if (data == null) return 0;
|
|
||||||
if (data.endTime == 0) return -1; // Перманентный
|
|
||||||
return Math.max(0, data.endTime - System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Форматировать время
|
|
||||||
*/
|
|
||||||
public String formatTime(long millis) {
|
|
||||||
if (millis <= 0) return "0с";
|
|
||||||
|
|
||||||
long seconds = millis / 1000;
|
|
||||||
long minutes = seconds / 60;
|
|
||||||
long hours = minutes / 60;
|
|
||||||
long days = hours / 24;
|
|
||||||
|
|
||||||
if (days > 0) {
|
|
||||||
return days + "д " + (hours % 24) + "ч";
|
|
||||||
} else if (hours > 0) {
|
|
||||||
return hours + "ч " + (minutes % 60) + "м";
|
|
||||||
} else if (minutes > 0) {
|
|
||||||
return minutes + "м " + (seconds % 60) + "с";
|
|
||||||
} else {
|
|
||||||
return seconds + "с";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Парсинг времени из строки (1d, 2h, 30m, 60s)
|
|
||||||
*/
|
|
||||||
public long parseTime(String timeStr) {
|
|
||||||
if (timeStr == null || timeStr.isEmpty()) return 0;
|
|
||||||
|
|
||||||
timeStr = timeStr.toLowerCase();
|
|
||||||
long multiplier = 1000; // базово в миллисекундах
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (timeStr.endsWith("d")) {
|
|
||||||
return Long.parseLong(timeStr.replace("d", "")) * 24 * 60 * 60 * multiplier;
|
|
||||||
} else if (timeStr.endsWith("h")) {
|
|
||||||
return Long.parseLong(timeStr.replace("h", "")) * 60 * 60 * multiplier;
|
|
||||||
} else if (timeStr.endsWith("m")) {
|
|
||||||
return Long.parseLong(timeStr.replace("m", "")) * 60 * multiplier;
|
|
||||||
} else if (timeStr.endsWith("s")) {
|
|
||||||
return Long.parseLong(timeStr.replace("s", "")) * multiplier;
|
|
||||||
} else {
|
|
||||||
// Если без суффикса - считаем минутами
|
|
||||||
return Long.parseLong(timeStr) * 60 * multiplier;
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return -1; // Ошибка парсинга
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void load() {
|
|
||||||
if (!dataFile.exists()) return;
|
|
||||||
|
|
||||||
try (Reader reader = new FileReader(dataFile)) {
|
|
||||||
Type type = new TypeToken<Map<String, MuteData>>(){}.getType();
|
|
||||||
Map<String, MuteData> loaded = gson.fromJson(reader, type);
|
|
||||||
if (loaded != null) {
|
|
||||||
loaded.forEach((key, value) -> {
|
|
||||||
try {
|
|
||||||
mutes.put(UUID.fromString(key), value);
|
|
||||||
} catch (IllegalArgumentException ignored) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().warning("Ошибка загрузки мутов: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
try {
|
|
||||||
if (!dataFile.getParentFile().exists()) {
|
|
||||||
dataFile.getParentFile().mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, MuteData> toSave = new ConcurrentHashMap<>();
|
|
||||||
mutes.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
|
|
||||||
|
|
||||||
try (Writer writer = new FileWriter(dataFile)) {
|
|
||||||
gson.toJson(toSave, writer);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().warning("Ошибка сохранения мутов: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadHistory() {
|
|
||||||
if (!historyFile.exists()) return;
|
|
||||||
|
|
||||||
try (Reader reader = new FileReader(historyFile)) {
|
|
||||||
Type type = new TypeToken<Map<String, List<MuteHistoryEntry>>>(){}.getType();
|
|
||||||
Map<String, List<MuteHistoryEntry>> loaded = gson.fromJson(reader, type);
|
|
||||||
if (loaded != null) {
|
|
||||||
loaded.forEach((key, value) -> {
|
|
||||||
try {
|
|
||||||
history.put(UUID.fromString(key), new ArrayList<>(value));
|
|
||||||
} catch (IllegalArgumentException ignored) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().warning("Ошибка загрузки истории мутов: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveHistory() {
|
|
||||||
try {
|
|
||||||
if (!historyFile.getParentFile().exists()) {
|
|
||||||
historyFile.getParentFile().mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, List<MuteHistoryEntry>> toSave = new ConcurrentHashMap<>();
|
|
||||||
history.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
|
|
||||||
|
|
||||||
try (Writer writer = new FileWriter(historyFile)) {
|
|
||||||
gson.toJson(toSave, writer);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().warning("Ошибка сохранения истории мутов: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveHistoryAsync() {
|
|
||||||
FoliaUtil.runAsync(plugin, this::saveHistory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveAsync() {
|
|
||||||
FoliaUtil.runAsync(plugin, () -> {
|
|
||||||
save();
|
|
||||||
saveHistory();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Добавить запись в историю
|
|
||||||
*/
|
|
||||||
private void addToHistory(UUID uuid, String playerName, long duration, String reason, String mutedBy) {
|
|
||||||
List<MuteHistoryEntry> playerHistory = history.computeIfAbsent(uuid, k -> new ArrayList<>());
|
|
||||||
playerHistory.add(new MuteHistoryEntry(
|
|
||||||
playerName,
|
|
||||||
duration,
|
|
||||||
reason,
|
|
||||||
mutedBy,
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
0
|
|
||||||
));
|
|
||||||
saveHistoryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Обновить историю при размуте
|
|
||||||
*/
|
|
||||||
private void updateHistoryUnmute(UUID uuid, String unmutedBy) {
|
|
||||||
List<MuteHistoryEntry> playerHistory = history.get(uuid);
|
|
||||||
if (playerHistory != null && !playerHistory.isEmpty()) {
|
|
||||||
// Находим последний активный мут
|
|
||||||
for (int i = playerHistory.size() - 1; i >= 0; i--) {
|
|
||||||
MuteHistoryEntry entry = playerHistory.get(i);
|
|
||||||
if (!entry.unmuted) {
|
|
||||||
entry.unmuted = true;
|
|
||||||
entry.unmutedBy = unmutedBy;
|
|
||||||
entry.unmutedAt = System.currentTimeMillis();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
saveHistoryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получить список всех активных мутов
|
|
||||||
*/
|
|
||||||
public Map<UUID, MuteData> getActiveMutes() {
|
|
||||||
// Очищаем истёкшие муты
|
|
||||||
mutes.entrySet().removeIf(entry -> {
|
|
||||||
MuteData data = entry.getValue();
|
|
||||||
return data.endTime > 0 && System.currentTimeMillis() > data.endTime;
|
|
||||||
});
|
|
||||||
return new HashMap<>(mutes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получить историю мутов игрока
|
|
||||||
*/
|
|
||||||
public List<MuteHistoryEntry> getPlayerHistory(UUID uuid) {
|
|
||||||
return history.getOrDefault(uuid, new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получить UUID по имени из истории
|
|
||||||
*/
|
|
||||||
public UUID getUUIDByName(String name) {
|
|
||||||
for (Map.Entry<UUID, List<MuteHistoryEntry>> entry : history.entrySet()) {
|
|
||||||
for (MuteHistoryEntry historyEntry : entry.getValue()) {
|
|
||||||
if (historyEntry.playerName != null && historyEntry.playerName.equalsIgnoreCase(name)) {
|
|
||||||
return entry.getKey();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получить историю мутов выданных модератором
|
|
||||||
*/
|
|
||||||
public List<MuteHistoryEntry> getMutesByOperator(String operatorName) {
|
|
||||||
List<MuteHistoryEntry> result = new ArrayList<>();
|
|
||||||
for (List<MuteHistoryEntry> playerHistory : history.values()) {
|
|
||||||
for (MuteHistoryEntry entry : playerHistory) {
|
|
||||||
if (entry.mutedBy != null && entry.mutedBy.equalsIgnoreCase(operatorName)) {
|
|
||||||
result.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Сортируем по дате (новые первые)
|
|
||||||
result.sort((a, b) -> Long.compare(b.mutedAt, a.mutedAt));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получить максимальную доступную длительность для игрока
|
|
||||||
* @param player игрок (модератор)
|
|
||||||
* @return длительность в миллисекундах, 0 = перманентный, -1 = нет прав
|
|
||||||
*/
|
|
||||||
public long getMaxDuration(org.bukkit.entity.Player player) {
|
|
||||||
if (player.hasPermission("lochat.mute.dur.perm")) {
|
|
||||||
return 0; // Перманентный
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем права от большего к меньшему
|
|
||||||
String[] durations = {"30d", "14d", "7d", "3d", "1d", "12h", "6h", "1h", "30m", "10m"};
|
|
||||||
for (String dur : durations) {
|
|
||||||
if (player.hasPermission("lochat.mute.dur." + dur)) {
|
|
||||||
return parseTime(dur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1; // Нет прав
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Проверить, может ли игрок выдать мут на указанную длительность
|
|
||||||
*/
|
|
||||||
public boolean canMuteForDuration(org.bukkit.entity.Player player, long duration) {
|
|
||||||
if (player.hasPermission("lochat.mute.dur.perm")) {
|
|
||||||
return true; // Может всё
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration == 0) {
|
|
||||||
return false; // Перманентный мут требует lochat.mute.dur.perm
|
|
||||||
}
|
|
||||||
|
|
||||||
long maxDuration = getMaxDuration(player);
|
|
||||||
if (maxDuration == -1) return false;
|
|
||||||
if (maxDuration == 0) return true; // Перманентный = может всё
|
|
||||||
|
|
||||||
return duration <= maxDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Форматировать сообщение с плейсхолдерами
|
|
||||||
*/
|
|
||||||
public String formatMessage(String message, String player, String operator, String duration, String reason) {
|
|
||||||
return message
|
|
||||||
.replace("%player%", player != null ? player : "")
|
|
||||||
.replace("%operator%", operator != null ? operator : "")
|
|
||||||
.replace("%duration%", duration != null ? duration : "")
|
|
||||||
.replace("%reason%", reason != null ? reason : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Данные о муте
|
|
||||||
*/
|
|
||||||
public static class MuteData {
|
|
||||||
public long endTime; // 0 = перманентный
|
|
||||||
public String reason;
|
|
||||||
public String mutedBy;
|
|
||||||
public long mutedAt;
|
|
||||||
public String playerName;
|
|
||||||
|
|
||||||
public MuteData() {}
|
|
||||||
|
|
||||||
public MuteData(long endTime, String reason, String mutedBy, long mutedAt) {
|
|
||||||
this(endTime, reason, mutedBy, mutedAt, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MuteData(long endTime, String reason, String mutedBy, long mutedAt, String playerName) {
|
|
||||||
this.endTime = endTime;
|
|
||||||
this.reason = reason;
|
|
||||||
this.mutedBy = mutedBy;
|
|
||||||
this.mutedAt = mutedAt;
|
|
||||||
this.playerName = playerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPermanent() {
|
|
||||||
return endTime == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Запись истории мута
|
|
||||||
*/
|
|
||||||
public static class MuteHistoryEntry {
|
|
||||||
public String playerName;
|
|
||||||
public long duration; // 0 = перманентный
|
|
||||||
public String reason;
|
|
||||||
public String mutedBy;
|
|
||||||
public long mutedAt;
|
|
||||||
public boolean unmuted;
|
|
||||||
public String unmutedBy;
|
|
||||||
public long unmutedAt;
|
|
||||||
|
|
||||||
public MuteHistoryEntry() {}
|
|
||||||
|
|
||||||
public MuteHistoryEntry(String playerName, long duration, String reason, String mutedBy,
|
|
||||||
long mutedAt, boolean unmuted, String unmutedBy, long unmutedAt) {
|
|
||||||
this.playerName = playerName;
|
|
||||||
this.duration = duration;
|
|
||||||
this.reason = reason;
|
|
||||||
this.mutedBy = mutedBy;
|
|
||||||
this.mutedAt = mutedAt;
|
|
||||||
this.unmuted = unmuted;
|
|
||||||
this.unmutedBy = unmutedBy;
|
|
||||||
this.unmutedAt = unmutedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPermanent() {
|
|
||||||
return duration == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package com.loki.lochat.managers;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class PMManager {
|
|
||||||
|
|
||||||
// UUID игрока -> UUID последнего собеседника
|
|
||||||
private final Map<UUID, UUID> lastConversation = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public void setLastConversation(UUID player, UUID target) {
|
|
||||||
lastConversation.put(player, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getLastConversation(UUID player) {
|
|
||||||
return lastConversation.get(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeConversation(UUID player) {
|
|
||||||
lastConversation.remove(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasConversation(UUID player) {
|
|
||||||
return lastConversation.containsKey(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package com.loki.lochat.managers;
|
|
||||||
|
|
||||||
import com.loki.lochat.LoChat;
|
|
||||||
import com.loki.lochat.utils.ChatFormatter;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class SpyManager {
|
|
||||||
|
|
||||||
private final LoChat plugin;
|
|
||||||
private final Set<UUID> spyEnabled = ConcurrentHashMap.newKeySet();
|
|
||||||
|
|
||||||
public SpyManager(LoChat plugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean toggleSpy(UUID player) {
|
|
||||||
if (spyEnabled.contains(player)) {
|
|
||||||
spyEnabled.remove(player);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
spyEnabled.add(player);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSpying(UUID player) {
|
|
||||||
return spyEnabled.contains(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void broadcastPM(Player sender, Player receiver, String message) {
|
|
||||||
String format = plugin.getMessageConfig().get("spy.format");
|
|
||||||
|
|
||||||
for (UUID spyUuid : spyEnabled) {
|
|
||||||
Player spy = Bukkit.getPlayer(spyUuid);
|
|
||||||
if (spy != null && spy.isOnline() && !spy.equals(sender) && !spy.equals(receiver)) {
|
|
||||||
spy.sendMessage(ChatFormatter.parse(format
|
|
||||||
.replace("{sender}", sender.getName())
|
|
||||||
.replace("{receiver}", receiver.getName())
|
|
||||||
.replace("{message}", message)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendToSpies(Player sender, net.kyori.adventure.text.Component message, boolean isGlobal) {
|
|
||||||
if (spyEnabled.isEmpty()) return;
|
|
||||||
|
|
||||||
String chatType = isGlobal ? "Global" : "Local";
|
|
||||||
String plainMessage = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(message);
|
|
||||||
String format = plugin.getMessageConfig().get("spy.chat-format",
|
|
||||||
"§7[SPY] §e{type} §7{sender}: §f{message}");
|
|
||||||
|
|
||||||
for (UUID spyUuid : spyEnabled) {
|
|
||||||
Player spy = Bukkit.getPlayer(spyUuid);
|
|
||||||
if (spy != null && spy.isOnline() && !spy.equals(sender)) {
|
|
||||||
spy.sendMessage(ChatFormatter.parse(format
|
|
||||||
.replace("{type}", chatType)
|
|
||||||
.replace("{sender}", sender.getName())
|
|
||||||
.replace("{message}", plainMessage)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeSpy(UUID player) {
|
|
||||||
spyEnabled.remove(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
88
src/main/java/com/loki/lochat/utils/AdventureManager.java
Normal file
88
src/main/java/com/loki/lochat/utils/AdventureManager.java
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.loki.lochat.utils;
|
||||||
|
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Менеджер для работы с Adventure API на Arclight/Forge серверах
|
||||||
|
*/
|
||||||
|
public class AdventureManager {
|
||||||
|
|
||||||
|
private static boolean adventureAvailable = false;
|
||||||
|
private static Object audiences;
|
||||||
|
|
||||||
|
public static void init(Plugin plugin) {
|
||||||
|
try {
|
||||||
|
// Проверяем наличие Adventure API
|
||||||
|
Class.forName("net.kyori.adventure.text.Component");
|
||||||
|
Class<?> audiencesClass = Class.forName("net.kyori.adventure.platform.bukkit.BukkitAudiences");
|
||||||
|
|
||||||
|
// Создаем BukkitAudiences через рефлексию
|
||||||
|
Object audiencesInstance = audiencesClass.getMethod("create", Plugin.class).invoke(null, plugin);
|
||||||
|
audiences = audiencesInstance;
|
||||||
|
adventureAvailable = true;
|
||||||
|
|
||||||
|
plugin.getLogger().info("Adventure API обнаружен - используем нативную поддержку");
|
||||||
|
} catch (Exception e) {
|
||||||
|
adventureAvailable = false;
|
||||||
|
plugin.getLogger().info("Adventure API не найден - используем legacy режим");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void shutdown() {
|
||||||
|
if (audiences != null && adventureAvailable) {
|
||||||
|
try {
|
||||||
|
audiences.getClass().getMethod("close").invoke(audiences);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Игнорируем ошибки при закрытии
|
||||||
|
}
|
||||||
|
audiences = null;
|
||||||
|
}
|
||||||
|
adventureAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessage(CommandSender sender, Object message) {
|
||||||
|
if (adventureAvailable && audiences != null) {
|
||||||
|
try {
|
||||||
|
// Используем Adventure API
|
||||||
|
Object senderAudience = audiences.getClass().getMethod("sender", CommandSender.class).invoke(audiences, sender);
|
||||||
|
senderAudience.getClass().getMethod("sendMessage", Class.forName("net.kyori.adventure.text.Component")).invoke(senderAudience, message);
|
||||||
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Fallback на legacy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy fallback
|
||||||
|
if (message instanceof String) {
|
||||||
|
sender.sendMessage((String) message);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(message.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessage(Player player, Object message) {
|
||||||
|
if (adventureAvailable && audiences != null) {
|
||||||
|
try {
|
||||||
|
// Используем Adventure API
|
||||||
|
Object playerAudience = audiences.getClass().getMethod("player", Player.class).invoke(audiences, player);
|
||||||
|
playerAudience.getClass().getMethod("sendMessage", Class.forName("net.kyori.adventure.text.Component")).invoke(playerAudience, message);
|
||||||
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Fallback на legacy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy fallback
|
||||||
|
if (message instanceof String) {
|
||||||
|
player.sendMessage((String) message);
|
||||||
|
} else {
|
||||||
|
player.sendMessage(message.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAdventureAvailable() {
|
||||||
|
return adventureAvailable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package com.loki.lochat.managers;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class CooldownManagerTest {
|
|
||||||
|
|
||||||
private CooldownManager cooldownManager;
|
|
||||||
private UUID testPlayer;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
cooldownManager = new CooldownManager();
|
|
||||||
testPlayer = UUID.randomUUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void isOnCooldown_noCooldown() {
|
|
||||||
assertFalse(cooldownManager.isOnCooldown(testPlayer, "global", 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void isOnCooldown_afterSet() {
|
|
||||||
cooldownManager.setCooldown(testPlayer, "global");
|
|
||||||
assertTrue(cooldownManager.isOnCooldown(testPlayer, "global", 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getRemainingCooldown_noCooldown() {
|
|
||||||
assertEquals(0, cooldownManager.getRemainingCooldown(testPlayer, "global", 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void removeCooldown() {
|
|
||||||
cooldownManager.setCooldown(testPlayer, "global");
|
|
||||||
cooldownManager.removeCooldown(testPlayer);
|
|
||||||
assertFalse(cooldownManager.isOnCooldown(testPlayer, "global", 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void separateCooldowns() {
|
|
||||||
cooldownManager.setCooldown(testPlayer, "global");
|
|
||||||
assertTrue(cooldownManager.isOnCooldown(testPlayer, "global", 5));
|
|
||||||
assertFalse(cooldownManager.isOnCooldown(testPlayer, "local", 5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
package com.loki.lochat.managers;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class PMManagerTest {
|
|
||||||
|
|
||||||
private PMManager pmManager;
|
|
||||||
private UUID player1;
|
|
||||||
private UUID player2;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
pmManager = new PMManager();
|
|
||||||
player1 = UUID.randomUUID();
|
|
||||||
player2 = UUID.randomUUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void setAndGetLastConversation() {
|
|
||||||
pmManager.setLastConversation(player1, player2);
|
|
||||||
assertEquals(player2, pmManager.getLastConversation(player1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getLastConversation_noConversation() {
|
|
||||||
assertNull(pmManager.getLastConversation(player1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void hasConversation() {
|
|
||||||
assertFalse(pmManager.hasConversation(player1));
|
|
||||||
pmManager.setLastConversation(player1, player2);
|
|
||||||
assertTrue(pmManager.hasConversation(player1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void removeConversation() {
|
|
||||||
pmManager.setLastConversation(player1, player2);
|
|
||||||
pmManager.removeConversation(player1);
|
|
||||||
assertFalse(pmManager.hasConversation(player1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue