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
|
|
@ -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