Remove TextDisplay, display name updates, mute system, and filter/censorship

This commit is contained in:
loki5512344 2026-01-01 23:40:02 +01:00
parent a87a942b28
commit 712a8a3353
15 changed files with 2 additions and 1064 deletions

View file

@ -20,14 +20,12 @@ public final class LoChat extends JavaPlugin {
private PMManager pmManager; private PMManager pmManager;
private IgnoreManager ignoreManager; private IgnoreManager ignoreManager;
private AutoMessageManager autoMessageManager; private AutoMessageManager autoMessageManager;
private FilterManager filterManager;
private AntiSpamManager antiSpamManager; private AntiSpamManager antiSpamManager;
private CooldownManager cooldownManager; private CooldownManager cooldownManager;
private MentionManager mentionManager; private MentionManager mentionManager;
private SpyManager spyManager; private SpyManager spyManager;
private EmojiManager emojiManager; private EmojiManager emojiManager;
private CustomCommandManager customCommandManager; private CustomCommandManager customCommandManager;
private MuteManager muteManager;
private GradientModule gradientModule; private GradientModule gradientModule;
@Override @Override
@ -42,13 +40,11 @@ public final class LoChat extends JavaPlugin {
emojiManager = new EmojiManager(this); emojiManager = new EmojiManager(this);
ignoreManager = new IgnoreManager(this); ignoreManager = new IgnoreManager(this);
pmManager = new PMManager(); pmManager = new PMManager();
filterManager = new FilterManager(this);
antiSpamManager = new AntiSpamManager(this); antiSpamManager = new AntiSpamManager(this);
cooldownManager = new CooldownManager(); cooldownManager = new CooldownManager();
mentionManager = new MentionManager(this); mentionManager = new MentionManager(this);
spyManager = new SpyManager(this); spyManager = new SpyManager(this);
customCommandManager = new CustomCommandManager(this); customCommandManager = new CustomCommandManager(this);
muteManager = new MuteManager(this);
chatManager = new ChatManager(this); chatManager = new ChatManager(this);
autoMessageManager = new AutoMessageManager(this); autoMessageManager = new AutoMessageManager(this);
@ -84,9 +80,6 @@ public final class LoChat extends JavaPlugin {
if (ignoreManager != null) { if (ignoreManager != null) {
ignoreManager.save(); ignoreManager.save();
} }
if (muteManager != null) {
muteManager.saveData();
}
if (gradientModule != null) { if (gradientModule != null) {
gradientModule.shutdown(); gradientModule.shutdown();
} }
@ -104,8 +97,6 @@ public final class LoChat extends JavaPlugin {
getCommand("announce").setExecutor(new AnnounceCommand(this)); getCommand("announce").setExecutor(new AnnounceCommand(this));
getCommand("chatspy").setExecutor(new ChatSpyCommand(this)); getCommand("chatspy").setExecutor(new ChatSpyCommand(this));
getCommand("clearchat").setExecutor(new ClearChatCommand(this)); getCommand("clearchat").setExecutor(new ClearChatCommand(this));
getCommand("mute").setExecutor(new MuteCommand(this));
getCommand("unmute").setExecutor(new UnmuteCommand(this));
LoChatCommand loChatCommand = new LoChatCommand(this); LoChatCommand loChatCommand = new LoChatCommand(this);
getCommand("lochat").setExecutor(loChatCommand); getCommand("lochat").setExecutor(loChatCommand);
@ -124,9 +115,6 @@ public final class LoChat extends JavaPlugin {
if (customCommandManager != null) { if (customCommandManager != null) {
customCommandManager.reload(); customCommandManager.reload();
} }
if (filterManager != null) {
filterManager.reload();
}
} }
public static LoChat getInstance() { public static LoChat getInstance() {
@ -157,9 +145,6 @@ public final class LoChat extends JavaPlugin {
return autoMessageManager; return autoMessageManager;
} }
public FilterManager getFilterManager() {
return filterManager;
}
public AntiSpamManager getAntiSpamManager() { public AntiSpamManager getAntiSpamManager() {
return antiSpamManager; return antiSpamManager;
@ -189,7 +174,4 @@ public final class LoChat extends JavaPlugin {
return customCommandManager; return customCommandManager;
} }
public MuteManager getMuteManager() {
return muteManager;
}
} }

View file

@ -1,122 +0,0 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MuteCommand implements CommandExecutor, TabCompleter {
private final LoChat plugin;
public MuteCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!sender.hasPermission("chat.mute")) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getNoPermission()));
return true;
}
if (args.length < 2) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/mute <игрок> <время> [причина]")));
sender.sendMessage(ChatFormatter.parse("&#808080Время: 1m, 1h, 1d, permanent"));
return true;
}
String playerName = args[0];
String timeStr = args[1];
String reason = args.length > 2 ? String.join(" ", Arrays.copyOfRange(args, 2, args.length)) : "Нарушение правил чата";
Player target = Bukkit.getPlayer(playerName);
if (target == null) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
return true;
}
// Парсим время
long duration = parseTime(timeStr);
if (duration == -1) {
sender.sendMessage(ChatFormatter.parse("&#FF0000Неверный формат времени! Используйте: 1m, 1h, 1d, permanent"));
return true;
}
// Мутим игрока
plugin.getMuteManager().mutePlayer(target.getUniqueId(), duration, reason, sender.getName());
// Уведомления
String timeDisplay = duration == 0 ? "навсегда" : formatTime(duration);
sender.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("mute.muted", "{player}", target.getName(), "{time}", timeDisplay)
));
if (target.isOnline()) {
target.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("mute.you-muted", "{time}", timeDisplay)
));
}
return true;
}
private long parseTime(String timeStr) {
if (timeStr.equalsIgnoreCase("permanent") || timeStr.equalsIgnoreCase("perm")) {
return 0; // 0 = permanent
}
try {
char unit = timeStr.charAt(timeStr.length() - 1);
int amount = Integer.parseInt(timeStr.substring(0, timeStr.length() - 1));
return switch (unit) {
case 'm' -> amount * 60L * 1000L; // минуты
case 'h' -> amount * 60L * 60L * 1000L; // часы
case 'd' -> amount * 24L * 60L * 60L * 1000L; // дни
default -> -1;
};
} catch (NumberFormatException | StringIndexOutOfBoundsException e) {
return -1;
}
}
private String formatTime(long millis) {
long seconds = millis / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if (days > 0) return days + "д";
if (hours > 0) return hours + "ч";
if (minutes > 0) return minutes + "м";
return seconds + "с";
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
if (args.length == 1) {
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList());
} else if (args.length == 2) {
List<String> times = Arrays.asList("1m", "5m", "10m", "30m", "1h", "2h", "6h", "12h", "1d", "3d", "7d", "permanent");
return times.stream()
.filter(time -> time.toLowerCase().startsWith(args[1].toLowerCase()))
.collect(Collectors.toList());
}
return new ArrayList<>();
}
}

View file

@ -1,76 +0,0 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Collectors;
public class UnmuteCommand implements CommandExecutor, TabCompleter {
private final LoChat plugin;
public UnmuteCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!sender.hasPermission("chat.mute")) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getNoPermission()));
return true;
}
if (args.length != 1) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/unmute <игрок>")));
return true;
}
String playerName = args[0];
Player target = Bukkit.getPlayer(playerName);
if (target == null) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
return true;
}
if (!plugin.getMuteManager().isMuted(target.getUniqueId())) {
sender.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("mute.not-muted", "{player}", target.getName())
));
return true;
}
// Размучиваем
plugin.getMuteManager().unmutePlayer(target.getUniqueId());
// Уведомления
sender.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("mute.unmuted", "{player}", target.getName())
));
if (target.isOnline()) {
target.sendMessage(ChatFormatter.parse("&#00FF00Вы были размучены!"));
}
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
if (args.length == 1) {
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList());
}
return null;
}
}

View file

@ -12,7 +12,6 @@ import com.loki.lochat.gradient.listeners.GradientGUIListener;
import com.loki.lochat.gradient.listeners.GradientPlayerListener; import com.loki.lochat.gradient.listeners.GradientPlayerListener;
import com.loki.lochat.gradient.util.DisplayNameUtil; import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.GradientUtil; import com.loki.lochat.gradient.util.GradientUtil;
import com.loki.lochat.gradient.util.TextDisplayManager;
import org.black_ixx.playerpoints.PlayerPoints; import org.black_ixx.playerpoints.PlayerPoints;
import org.black_ixx.playerpoints.PlayerPointsAPI; import org.black_ixx.playerpoints.PlayerPointsAPI;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -30,7 +29,6 @@ public class GradientModule {
private GradientDataManager dataManager; private GradientDataManager dataManager;
private GradientLuckPermsHook luckPermsHook; private GradientLuckPermsHook luckPermsHook;
private PlayerPointsAPI playerPointsAPI; private PlayerPointsAPI playerPointsAPI;
private TextDisplayManager textDisplayManager;
private boolean enabled; private boolean enabled;
public GradientModule(JavaPlugin plugin) { public GradientModule(JavaPlugin plugin) {
@ -66,8 +64,6 @@ public class GradientModule {
// Подключение LuckPerms // Подключение LuckPerms
luckPermsHook = new GradientLuckPermsHook(plugin); luckPermsHook = new GradientLuckPermsHook(plugin);
// Инициализация TextDisplayManager
textDisplayManager = new TextDisplayManager(this);
// Регистрация команд // Регистрация команд
registerCommands(); registerCommands();
@ -105,9 +101,6 @@ public class GradientModule {
* Выключение модуля * Выключение модуля
*/ */
public void shutdown() { public void shutdown() {
if (textDisplayManager != null) {
textDisplayManager.removeAllDisplays();
}
if (dataManager != null) { if (dataManager != null) {
dataManager.saveAll(); dataManager.saveAll();
} }
@ -268,7 +261,6 @@ public class GradientModule {
public GradientDataManager getDataManager() { return dataManager; } public GradientDataManager getDataManager() { return dataManager; }
public GradientLuckPermsHook getLuckPermsHook() { return luckPermsHook; } public GradientLuckPermsHook getLuckPermsHook() { return luckPermsHook; }
public PlayerPointsAPI getPlayerPointsAPI() { return playerPointsAPI; } public PlayerPointsAPI getPlayerPointsAPI() { return playerPointsAPI; }
public TextDisplayManager getTextDisplayManager() { return textDisplayManager; }
public boolean hasPlayerPoints() { return playerPointsAPI != null; } public boolean hasPlayerPoints() { return playerPointsAPI != null; }
public boolean isEnabled() { return enabled; } public boolean isEnabled() { return enabled; }
} }

View file

@ -87,9 +87,6 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
case "updateall" -> { case "updateall" -> {
handleUpdateAll(sender, msg); handleUpdateAll(sender, msg);
} }
case "toggledisplay" -> {
handleToggleDisplay(sender, msg);
}
default -> sendHelp(sender); default -> sendHelp(sender);
} }
@ -334,36 +331,6 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§aОбновлены display names для §f" + count + " §aигроков"); sender.sendMessage("§aОбновлены display names для §f" + count + " §aигроков");
} }
private void handleToggleDisplay(CommandSender sender, GradientMessages msg) {
// Переключаем режим TextDisplay в конфиге
boolean currentMode = module.getConfig().isUseTextDisplay();
boolean newMode = !currentMode;
// Обновляем конфиг (временно в памяти)
module.getConfig().setUseTextDisplay(newMode);
if (newMode) {
// Переключаемся на TextDisplay
sender.sendMessage("§aВключен режим TextDisplay");
// Удаляем все обычные display names и создаем TextDisplay
for (Player player : Bukkit.getOnlinePlayers()) {
player.displayName(null); // Сбрасываем обычный display name
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
FoliaUtil.runEntityTask(module.getPlugin(), player,
() -> module.getTextDisplayManager().updatePlayerDisplay(player));
}
} else {
// Переключаемся на обычный display name
sender.sendMessage("§cВыключен режим TextDisplay");
// Удаляем все TextDisplay и обновляем обычные display names
module.getTextDisplayManager().removeAllDisplays();
for (Player player : Bukkit.getOnlinePlayers()) {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
FoliaUtil.runEntityTask(module.getPlugin(), player,
() -> DisplayNameUtil.updateDisplayName(module, player, data));
}
}
}
@Override @Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@ -372,7 +339,7 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
if (args.length == 1) { if (args.length == 1) {
return Arrays.asList("reload", "info", "setcolor", "setprefix", "coloron", "coloroff", return Arrays.asList("reload", "info", "setcolor", "setprefix", "coloron", "coloroff",
"prefixon", "prefixoff", "resetcolor", "resetprefix", "updateall", "toggledisplay").stream() "prefixon", "prefixoff", "resetcolor", "resetprefix", "updateall").stream()
.filter(s -> s.startsWith(args[0].toLowerCase())) .filter(s -> s.startsWith(args[0].toLowerCase()))
.toList(); .toList();
} }

View file

@ -83,7 +83,6 @@ public class GradientConfig {
.anyMatch(blacklisted -> lowerPrefix.contains(blacklisted.toLowerCase())); .anyMatch(blacklisted -> lowerPrefix.contains(blacklisted.toLowerCase()));
} }
// Display settings - читаем из main config.yml, если есть, иначе из gradient-config.yml
public boolean isUpdateDisplayName() { public boolean isUpdateDisplayName() {
if (mainConfig.contains("display.update-display-name")) { if (mainConfig.contains("display.update-display-name")) {
return mainConfig.getBoolean("display.update-display-name", true); return mainConfig.getBoolean("display.update-display-name", true);
@ -97,44 +96,4 @@ public class GradientConfig {
} }
return config.getBoolean("display.update-tab-list", true); return config.getBoolean("display.update-tab-list", true);
} }
public boolean isUseTextDisplay() {
if (mainConfig.contains("display.use-text-display")) {
return mainConfig.getBoolean("display.use-text-display", true);
}
return config.getBoolean("display.use-text-display", true);
}
public double getTextDisplayHeight() {
if (mainConfig.contains("display.text-display-height")) {
return mainConfig.getDouble("display.text-display-height", 2.8);
}
return config.getDouble("display.text-display-height", 2.8);
}
public float getTextDisplayScale() {
if (mainConfig.contains("display.text-display-scale")) {
return (float) mainConfig.getDouble("display.text-display-scale", 0.8);
}
return (float) config.getDouble("display.text-display-scale", 0.8);
}
public boolean isTextDisplaySeeThrough() {
if (mainConfig.contains("display.text-display-see-through")) {
return mainConfig.getBoolean("display.text-display-see-through", false);
}
return config.getBoolean("display.text-display-see-through", false);
}
public String getTextDisplayFormat() {
if (mainConfig.contains("display.text-display-format")) {
return mainConfig.getString("display.text-display-format", "{prefix}{player}");
}
return config.getString("display.text-display-format", "{prefix}{player}");
}
// Метод для временного изменения настройки TextDisplay
public void setUseTextDisplay(boolean useTextDisplay) {
config.set("display.use-text-display", useTextDisplay);
}
} }

View file

@ -1,8 +1,6 @@
package com.loki.lochat.gradient.listeners; package com.loki.lochat.gradient.listeners;
import com.loki.lochat.gradient.GradientModule; import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.FoliaUtil; import com.loki.lochat.gradient.util.FoliaUtil;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -10,7 +8,6 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
/** /**
* Слушатель событий игроков для градиентного модуля * Слушатель событий игроков для градиентного модуля
@ -26,69 +23,15 @@ public class GradientPlayerListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
FoliaUtil.runAsync(module.getPlugin(), () -> { FoliaUtil.runAsync(module.getPlugin(), () -> {
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId()); module.getDataManager().getPlayerData(player.getUniqueId());
FoliaUtil.runEntityTask(module.getPlugin(), player, () -> {
if (!player.isOnline()) return;
// Обновляем display name если включено
if (module.getConfig().isUpdateDisplayName()) {
DisplayNameUtil.updateDisplayName(module, player, data);
}
if (data.hasPrefix() && data.isPrefixEnabled()) {
module.getLuckPermsHook().setPrefix(player,
DisplayNameUtil.buildColoredPrefix(module, data));
}
});
}); });
} }
@EventHandler @EventHandler
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
// Удаляем TextDisplay если есть
if (module.getTextDisplayManager() != null) {
module.getTextDisplayManager().removePlayerDisplay(player.getUniqueId());
}
FoliaUtil.runAsync(module.getPlugin(), FoliaUtil.runAsync(module.getPlugin(),
() -> module.getDataManager().savePlayerData(player.getUniqueId())); () -> module.getDataManager().savePlayerData(player.getUniqueId()));
} }
@EventHandler
public void onPlayerToggleSneak(org.bukkit.event.player.PlayerToggleSneakEvent event) {
Player player = event.getPlayer();
// Скрываем/показываем TextDisplay при приседании
if (module.getTextDisplayManager() != null && module.getTextDisplayManager().hasDisplay(player.getUniqueId())) {
FoliaUtil.runEntityTask(module.getPlugin(), player, () -> {
if (event.isSneaking()) {
// Игрок начал приседать - скрываем TextDisplay
module.getTextDisplayManager().hidePlayerDisplay(player.getUniqueId());
} else {
// Игрок перестал приседать - показываем TextDisplay
module.getTextDisplayManager().showPlayerDisplay(player.getUniqueId());
}
});
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerTeleport(PlayerTeleportEvent event) {
if (event.isCancelled()) return;
Player player = event.getPlayer();
// Обновляем TextDisplay после телепортации
if (module.getTextDisplayManager() != null && module.getTextDisplayManager().hasDisplay(player.getUniqueId())) {
FoliaUtil.runEntityTask(module.getPlugin(), player, () -> {
if (!player.isOnline()) return;
module.getTextDisplayManager().updateDisplayPosition(player);
});
}
}
} }

View file

@ -25,13 +25,6 @@ public final class DisplayNameUtil {
public static void updateDisplayName(GradientModule module, Player player, GradientPlayerData data) { public static void updateDisplayName(GradientModule module, Player player, GradientPlayerData data) {
GradientConfig cfg = module.getConfig(); GradientConfig cfg = module.getConfig();
// Если включен TextDisplay, используем его вместо обычного display name
if (cfg.isUseTextDisplay()) {
module.getTextDisplayManager().updatePlayerDisplay(player);
return;
}
String prefix = null; String prefix = null;
String prefixFormat = cfg.getPrefixFormat(); String prefixFormat = cfg.getPrefixFormat();

View file

@ -1,165 +0,0 @@
package com.loki.lochat.gradient.util;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.data.GradientPlayerData;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay;
import org.bukkit.util.Transformation;
import org.joml.AxisAngle4f;
import org.joml.Vector3f;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class TextDisplayManager {
private final GradientModule module;
private final Map<UUID, TextDisplay> playerDisplays = new ConcurrentHashMap<>();
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
public TextDisplayManager(GradientModule module) {
this.module = module;
}
public void updatePlayerDisplay(Player player) {
if (!module.getConfig().isUpdateDisplayName() || !module.getConfig().isUseTextDisplay()) {
return;
}
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
String displayText = getDisplayText(player, data);
Component textComponent = MINI_MESSAGE.deserialize(displayText);
TextDisplay display = playerDisplays.get(player.getUniqueId());
if (display == null || !display.isValid()) {
createTextDisplay(player, textComponent);
} else {
updateTextDisplay(display, player, textComponent);
}
}
private void createTextDisplay(Player player, Component textComponent) {
removePlayerDisplay(player.getUniqueId());
double height = module.getConfig().getTextDisplayHeight();
float scale = module.getConfig().getTextDisplayScale();
boolean seeThrough = module.getConfig().isTextDisplaySeeThrough();
TextDisplay display = player.getWorld().spawn(
player.getLocation().add(0, height, 0),
TextDisplay.class,
entity -> {
entity.text(textComponent);
entity.setBillboard(org.bukkit.entity.Display.Billboard.CENTER);
entity.setViewRange(64.0f);
entity.setSeeThrough(seeThrough);
entity.setShadowRadius(0.0f);
entity.setShadowStrength(0.0f);
entity.setTransformation(new Transformation(
new Vector3f(0, 0, 0),
new AxisAngle4f(0, 0, 0, 1),
new Vector3f(scale, scale, scale),
new AxisAngle4f(0, 0, 0, 1)
));
entity.setVisibleByDefault(true);
}
);
player.addPassenger(display);
playerDisplays.put(player.getUniqueId(), display);
}
private void updateTextDisplay(TextDisplay display, Player player, Component textComponent) {
display.text(textComponent);
if (!player.getPassengers().contains(display)) {
player.addPassenger(display);
}
}
private String getDisplayText(Player player, GradientPlayerData data) {
String format = module.getConfig().getTextDisplayFormat();
String prefix = getPlayerPrefix(player, data);
String playerName = getPlayerName(player, data);
String result = format
.replace("{prefix}", prefix != null ? prefix : "")
.replace("{player}", playerName)
.replace("{name}", playerName);
return convertLegacyFormats(result);
}
private String convertLegacyFormats(String message) {
if (message == null) return "";
message = message.replaceAll("&#([0-9a-fA-F]{6})", "<#$1>");
message = message.replaceAll("(?<!<)#([0-9a-fA-F]{6})(?![^<]*>)", "<#$1>");
return message;
}
private String getPlayerPrefix(Player player, GradientPlayerData data) {
String prefixFormat = module.getConfig().getPrefixFormat();
if (data.isPrefixEnabled() && data.hasPrefix()) {
String prefix = prefixFormat.replace("{prefix}", data.getPrefix());
if (data.isColorEnabled() && data.hasColors() && module.getConfig().isGradientOnPrefix()) {
return GradientUtil.applyGradient(prefix, data.getColors(), false);
}
return prefix;
} else if (module.getLuckPermsHook().isEnabled()) {
String lpPrefix = module.getLuckPermsHook().getActivePrefix(player);
if (lpPrefix != null && !lpPrefix.isEmpty()) {
if (data.isColorEnabled() && data.hasColors() && module.getConfig().isGradientOnLuckPermsPrefix()) {
String cleanPrefix = stripColors(lpPrefix);
return GradientUtil.applyGradient(cleanPrefix, data.getColors(), false);
}
return lpPrefix;
}
}
return "";
}
private String getPlayerName(Player player, GradientPlayerData data) {
if (data.isColorEnabled() && data.hasColors()) {
return GradientUtil.applyGradient(player.getName(), data.getColors(), false);
}
return player.getName();
}
private String stripColors(String text) {
if (text == null) return "";
return text.replaceAll("(?i)(┬зx(┬з[0-9a-f]){6}|┬з[0-9a-fk-or]|&[0-9a-fk-or]|&#[0-9a-f]{6}|<[^>]+>)", "");
}
public void removePlayerDisplay(UUID playerId) {
TextDisplay display = playerDisplays.remove(playerId);
if (display != null && display.isValid()) {
display.remove();
}
}
public void removeAllDisplays() {
for (TextDisplay display : playerDisplays.values()) {
if (display != null && display.isValid()) {
display.remove();
}
}
playerDisplays.clear();
}
public void updateDisplayPosition(Player player) {
TextDisplay display = playerDisplays.get(player.getUniqueId());
if (display != null && display.isValid()) {
if (!player.getPassengers().contains(display)) {
player.addPassenger(display);
}
}
}
public boolean hasDisplay(UUID playerId) {
TextDisplay display = playerDisplays.get(playerId);
return display != null && display.isValid();
}
public void hidePlayerDisplay(UUID playerId) {
TextDisplay display = playerDisplays.get(playerId);
if (display != null && display.isValid()) {
display.setViewRange(0.0f);
}
}
public void showPlayerDisplay(UUID playerId) {
TextDisplay display = playerDisplays.get(playerId);
if (display != null && display.isValid()) {
display.setViewRange(64.0f);
}
}
}

View file

@ -57,17 +57,6 @@ public class PlaceholderAPIHook extends PlaceholderExpansion {
} }
case "spy_enabled" -> String.valueOf(plugin.getSpyManager().isSpying(uuid)); case "spy_enabled" -> String.valueOf(plugin.getSpyManager().isSpying(uuid));
// Mute placeholders
case "muted" -> String.valueOf(plugin.getMuteManager().isMuted(uuid));
case "mute_time" -> {
if (!plugin.getMuteManager().isMuted(uuid)) yield "";
yield plugin.getMuteManager().formatRemainingTime(uuid);
}
case "mute_reason" -> {
var muteData = plugin.getMuteManager().getMuteData(uuid);
yield muteData != null ? muteData.reason : "";
}
// Gradient placeholders (совместимость с LoPreff) // Gradient placeholders (совместимость с LoPreff)
// Используем TAB формат §x§R§R§G§G§B§B для совместимости с TAB плагином // Используем TAB формат §x§R§R§G§G§B§B для совместимости с TAB плагином
case "gradient_full", "full" -> { case "gradient_full", "full" -> {

View file

@ -2,7 +2,6 @@ package com.loki.lochat.listeners;
import com.loki.lochat.LoChat; import com.loki.lochat.LoChat;
import com.loki.lochat.managers.AntiSpamManager; import com.loki.lochat.managers.AntiSpamManager;
import com.loki.lochat.managers.FilterManager;
import io.papermc.paper.event.player.AsyncChatEvent; import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.TextReplacementConfig;
@ -13,8 +12,6 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import java.util.regex.Pattern;
public class ChatListener implements Listener { public class ChatListener implements Listener {
private final LoChat plugin; private final LoChat plugin;
@ -40,25 +37,6 @@ public class ChatListener implements Listener {
} }
private void processChat(Player player, Component message, String plainMessage) { private void processChat(Player player, Component message, String plainMessage) {
// ===== MUTE =====
if (plugin.getMuteManager().isMuted(player.getUniqueId())) {
String timeLeft = plugin.getMuteManager()
.formatRemainingTime(player.getUniqueId());
if (timeLeft.equals("навсегда")) {
player.sendMessage(
plugin.getMessageConfig().getComponent("mute.permanent")
);
} else {
player.sendMessage(
plugin.getMessageConfig()
.getComponent("mute.you-muted", "{time}", timeLeft)
);
}
return;
}
// ===== GLOBAL / LOCAL ===== // ===== GLOBAL / LOCAL =====
String globalSymbol = plugin.getConfigManager().getGlobalSymbol(); String globalSymbol = plugin.getConfigManager().getGlobalSymbol();
boolean isGlobal = plainMessage.startsWith(globalSymbol) boolean isGlobal = plainMessage.startsWith(globalSymbol)
@ -125,27 +103,6 @@ public class ChatListener implements Listener {
} }
} }
// ===== FILTER =====
if (!player.hasPermission("chat.bypass.filter")) {
FilterManager.FilterResult filter =
plugin.getFilterManager().checkMessage(processedPlain);
switch (filter) {
case BLOCKED -> {
player.sendMessage(
plugin.getMessageConfig().getComponent("filter.blocked")
);
return;
}
case WARNED -> {
player.sendMessage(
plugin.getMessageConfig().getComponent("filter.warned")
);
processedMessage = censor(processedMessage);
}
case CENSORED -> processedMessage = censor(processedMessage);
}
}
plugin.getCooldownManager().setCooldown(player.getUniqueId(), chatType); plugin.getCooldownManager().setCooldown(player.getUniqueId(), chatType);
@ -157,13 +114,6 @@ public class ChatListener implements Listener {
} }
} }
private Component censor(Component component) {
return component.replaceText(TextReplacementConfig.builder()
.match(Pattern.compile("(?i)badword"))
.replacement("***")
.build());
}
@EventHandler @EventHandler
public void onQuit(PlayerQuitEvent event) { public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();

View file

@ -1,302 +0,0 @@
package com.loki.lochat.managers;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.loki.lochat.LoChat;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class FilterManager {
private final LoChat plugin;
private List<String> badWords = new ArrayList<>();
private List<String> fragments = new ArrayList<>();
public FilterManager(LoChat plugin) {
this.plugin = plugin;
loadFilterWords();
}
/**
* Загружает плохие слова из JSON файла
*/
private void loadFilterWords() {
badWords.clear();
fragments.clear();
try {
// Сначала пытаемся загрузить из data folder (если есть кастомный)
File customFile = new File(plugin.getDataFolder(), "filter-words.json");
if (customFile.exists()) {
try (FileReader reader = new FileReader(customFile, StandardCharsets.UTF_8)) {
JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
loadFromJsonObject(json);
plugin.getLogger().info("Загружены плохие слова из filter-words.json (data folder): " + badWords.size() + " слов, " + fragments.size() + " фрагментов");
return;
}
}
// Если кастомного нет, копируем из ресурсов
plugin.saveResource("filter-words.json", false);
// Теперь загружаем из data folder
if (customFile.exists()) {
try (FileReader reader = new FileReader(customFile, StandardCharsets.UTF_8)) {
JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
loadFromJsonObject(json);
plugin.getLogger().info("Загружено " + badWords.size() + " плохих слов и " + fragments.size() + " фрагментов из filter-words.json");
return;
}
}
// Fallback на старый способ из config.yml
List<String> configWords = plugin.getConfigManager().getFilterWords();
if (configWords != null && !configWords.isEmpty()) {
badWords.addAll(configWords);
plugin.getLogger().warning("Используются слова из config.yml (filter-words.json не найден)");
}
} catch (Exception e) {
plugin.getLogger().severe("Ошибка загрузки filter-words.json: " + e.getMessage());
e.printStackTrace();
// Fallback на старый способ
List<String> configWords = plugin.getConfigManager().getFilterWords();
if (configWords != null && !configWords.isEmpty()) {
badWords.addAll(configWords);
plugin.getLogger().info("Используются слова из config.yml (fallback)");
}
}
}
/**
* Загружает данные из JSON объекта
*/
private void loadFromJsonObject(JsonObject json) {
if (json.has("words")) {
JsonArray wordsArray = json.getAsJsonArray("words");
for (JsonElement element : wordsArray) {
String word = element.getAsString();
if (word != null && !word.trim().isEmpty()) {
badWords.add(word.trim().toLowerCase());
}
}
}
if (json.has("fragments")) {
JsonArray fragmentsArray = json.getAsJsonArray("fragments");
for (JsonElement element : fragmentsArray) {
String fragment = element.getAsString();
if (fragment != null && !fragment.trim().isEmpty()) {
fragments.add(fragment.trim().toLowerCase());
}
}
}
}
/**
* Перезагружает список плохих слов
*/
public void reload() {
loadFilterWords();
}
public enum FilterResult {
ALLOWED,
CENSORED,
BLOCKED,
WARNED
}
/**
* Проверяет сообщение на наличие запрещенных слов
*/
public FilterResult checkMessage(String message) {
if (!plugin.getConfigManager().isFilterEnabled()) {
return FilterResult.ALLOWED;
}
if (message == null || message.trim().isEmpty()) {
return FilterResult.ALLOWED;
}
if (badWords.isEmpty() && fragments.isEmpty()) {
return FilterResult.ALLOWED;
}
// Нормализуем сообщение (убираем обход фильтра)
String normalizedMessage = normalizeMessage(message);
String lowerMessage = normalizedMessage.toLowerCase();
String originalLower = message.toLowerCase();
// Проверяем полные слова
for (String word : badWords) {
if (word == null || word.trim().isEmpty()) {
continue;
}
String lowerWord = word.toLowerCase().trim();
// Проверяем точное совпадение слова (с границами слов)
Pattern wordPattern = Pattern.compile("\\b" + Pattern.quote(lowerWord) + "\\b", Pattern.CASE_INSENSITIVE);
if (wordPattern.matcher(normalizedMessage).find() || wordPattern.matcher(originalLower).find()) {
return getFilterResult();
}
// Также проверяем как подстроку
if (lowerMessage.contains(lowerWord) || originalLower.contains(lowerWord)) {
return getFilterResult();
}
}
// Проверяем фрагменты (поиск по фрагментам типа "пизд" найдет "пизда", "пиздец" и т.д.)
for (String fragment : fragments) {
if (fragment == null || fragment.trim().isEmpty()) {
continue;
}
String lowerFragment = fragment.toLowerCase().trim();
// Ищем фрагмент в нормализованном сообщении
if (lowerMessage.contains(lowerFragment) || originalLower.contains(lowerFragment)) {
return getFilterResult();
}
}
return FilterResult.ALLOWED;
}
/**
* Возвращает результат фильтрации на основе настроек
*/
private FilterResult getFilterResult() {
String action = plugin.getConfigManager().getFilterAction();
return switch (action.toLowerCase()) {
case "block" -> FilterResult.BLOCKED;
case "warn" -> FilterResult.WARNED;
default -> FilterResult.CENSORED;
};
}
/**
* Цензурирует сообщение, заменяя запрещенные слова
*/
public String censorMessage(String message) {
if (!plugin.getConfigManager().isFilterEnabled()) {
return message;
}
if (message == null || message.trim().isEmpty()) {
return message;
}
if (badWords.isEmpty() && fragments.isEmpty()) {
return message;
}
String replacement = plugin.getConfigManager().getFilterReplacement();
if (replacement == null) {
replacement = "***";
}
String result = message;
String normalizedMessage = normalizeMessage(message);
// Заменяем полные слова
for (String word : badWords) {
if (word == null || word.trim().isEmpty()) {
continue;
}
// Заменяем точные совпадения слов
Pattern wordPattern = Pattern.compile("\\b" + Pattern.quote(word) + "\\b", Pattern.CASE_INSENSITIVE);
result = wordPattern.matcher(result).replaceAll(replacement);
// Также заменяем как подстроку
Pattern substringPattern = Pattern.compile("(?i)" + Pattern.quote(word));
result = substringPattern.matcher(result).replaceAll(replacement);
}
// Заменяем фрагменты (находим слова содержащие фрагмент и заменяем их)
for (String fragment : fragments) {
if (fragment == null || fragment.trim().isEmpty()) {
continue;
}
// Ищем слова содержащие фрагмент и заменяем весь фрагмент
Pattern fragmentPattern = Pattern.compile("(?i)" + Pattern.quote(fragment));
result = fragmentPattern.matcher(result).replaceAll(replacement);
// Также заменяем слова начинающиеся с фрагмента
Pattern wordStartPattern = Pattern.compile("\\b" + Pattern.quote(fragment) + "\\w*", Pattern.CASE_INSENSITIVE);
result = wordStartPattern.matcher(result).replaceAll(replacement);
}
return result;
}
/**
* Нормализует сообщение, убирая попытки обхода фильтра
* Заменяет специальные символы на обычные буквы
* Например: "пизд@" -> "пизда", "х*й" -> "хай" (но "хуй" найдет)
*/
private String normalizeMessage(String message) {
if (message == null) {
return "";
}
// Заменяем символы обхода на буквы (для обнаружения х*й, х_уй, пизд@ и т.д.)
String normalized = message;
// Заменяем символы обхода на похожие буквы
normalized = normalized.replace('*', 'а');
normalized = normalized.replace('_', 'а');
normalized = normalized.replace('-', 'а');
normalized = normalized.replace('.', 'а');
normalized = normalized.replace(',', 'а');
normalized = normalized.replace('|', 'и');
normalized = normalized.replace('1', 'и');
normalized = normalized.replace('!', 'и');
normalized = normalized.replace('0', 'о');
normalized = normalized.replace('3', 'з');
normalized = normalized.replace('4', 'ч');
normalized = normalized.replace('5', 'с');
normalized = normalized.replace('6', 'б');
normalized = normalized.replace('7', 'т');
normalized = normalized.replace('8', 'в');
normalized = normalized.replace('9', 'д');
normalized = normalized.replace('@', 'а'); // пизд@ -> пизда
normalized = normalized.replace('#', 'х');
normalized = normalized.replace('$', 'с');
normalized = normalized.replace('%', 'о');
normalized = normalized.replace('^', 'х');
normalized = normalized.replace('&', 'а');
normalized = normalized.replace('~', 'а');
normalized = normalized.replace('`', 'а');
normalized = normalized.replace('=', 'а');
normalized = normalized.replace('+', 'а');
normalized = normalized.replace('[', 'а');
normalized = normalized.replace(']', 'а');
normalized = normalized.replace('{', 'а');
normalized = normalized.replace('}', 'а');
normalized = normalized.replace('(', 'а');
normalized = normalized.replace(')', 'а');
normalized = normalized.replace('/', 'а');
normalized = normalized.replace('\\', 'а');
normalized = normalized.replace('?', 'а');
normalized = normalized.replace('<', 'а');
normalized = normalized.replace('>', 'а');
// Убираем множественные пробелы
normalized = normalized.replaceAll("\\s+", " ");
return normalized;
}
}

View file

@ -1,147 +0,0 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MuteManager {
private final LoChat plugin;
private final Map<UUID, MuteData> mutedPlayers;
private File dataFile;
private FileConfiguration dataConfig;
public MuteManager(LoChat plugin) {
this.plugin = plugin;
this.mutedPlayers = new HashMap<>();
loadData();
}
private void loadData() {
dataFile = new File(plugin.getDataFolder(), "mutes.yml");
if (!dataFile.exists()) {
try {
dataFile.createNewFile();
} catch (IOException e) {
plugin.getLogger().severe("Не удалось создать файл mutes.yml: " + e.getMessage());
}
}
dataConfig = YamlConfiguration.loadConfiguration(dataFile);
// Загружаем муты из файла
for (String key : dataConfig.getKeys(false)) {
try {
UUID uuid = UUID.fromString(key);
long expiry = dataConfig.getLong(key + ".expiry");
String reason = dataConfig.getString(key + ".reason", "Нарушение правил");
String mutedBy = dataConfig.getString(key + ".muted_by", "Система");
// Проверяем не истёк ли мут
if (expiry > 0 && System.currentTimeMillis() > expiry) {
dataConfig.set(key, null);
continue;
}
mutedPlayers.put(uuid, new MuteData(expiry, reason, mutedBy));
} catch (IllegalArgumentException ignored) {
// Неверный UUID, пропускаем
}
}
saveData();
}
public void saveData() {
for (Map.Entry<UUID, MuteData> entry : mutedPlayers.entrySet()) {
String key = entry.getKey().toString();
MuteData data = entry.getValue();
dataConfig.set(key + ".expiry", data.expiry);
dataConfig.set(key + ".reason", data.reason);
dataConfig.set(key + ".muted_by", data.mutedBy);
}
try {
dataConfig.save(dataFile);
} catch (IOException e) {
plugin.getLogger().severe("Не удалось сохранить mutes.yml: " + e.getMessage());
}
}
public void mutePlayer(UUID playerId, long duration, String reason, String mutedBy) {
long expiry = duration == 0 ? 0 : System.currentTimeMillis() + duration;
mutedPlayers.put(playerId, new MuteData(expiry, reason, mutedBy));
saveData();
}
public void unmutePlayer(UUID playerId) {
mutedPlayers.remove(playerId);
dataConfig.set(playerId.toString(), null);
saveData();
}
public boolean isMuted(UUID playerId) {
MuteData data = mutedPlayers.get(playerId);
if (data == null) return false;
// Проверяем не истёк ли мут
if (data.expiry > 0 && System.currentTimeMillis() > data.expiry) {
unmutePlayer(playerId);
return false;
}
return true;
}
public MuteData getMuteData(UUID playerId) {
return mutedPlayers.get(playerId);
}
public long getRemainingTime(UUID playerId) {
MuteData data = mutedPlayers.get(playerId);
if (data == null || data.expiry == 0) return 0;
long remaining = data.expiry - System.currentTimeMillis();
return Math.max(0, remaining);
}
public String formatRemainingTime(UUID playerId) {
long remaining = getRemainingTime(playerId);
if (remaining == 0) {
MuteData data = getMuteData(playerId);
if (data != null && data.expiry == 0) {
return "навсегда";
}
return "0с";
}
long seconds = remaining / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if (days > 0) return days + "д " + (hours % 24) + "ч";
if (hours > 0) return hours + "ч " + (minutes % 60) + "м";
if (minutes > 0) return minutes + "м " + (seconds % 60) + "с";
return seconds + "с";
}
public static class MuteData {
public final long expiry; // 0 = permanent
public final String reason;
public final String mutedBy;
public MuteData(long expiry, String reason, String mutedBy) {
this.expiry = expiry;
this.reason = reason;
this.mutedBy = mutedBy;
}
}
}

View file

@ -40,16 +40,6 @@ display:
update-display-name: true update-display-name: true
# Обновлять tab list # Обновлять tab list
update-tab-list: true update-tab-list: true
# Использовать TextDisplay вместо обычного display name (1.19.4+)
use-text-display: true
# Высота TextDisplay над игроком (в блоках)
text-display-height: 2.8
# Размер TextDisplay (от 0.1 до 2.0)
text-display-scale: 0.8
# Видно ли TextDisplay через стены
text-display-see-through: false
# Формат TextDisplay (поддерживает &b&l и другие коды, &#RRGGBB, #RRGGBB)
text-display-format: "{prefix}{player}"
# Упоминания @ник # Упоминания @ник
mentions: mentions:

View file

@ -49,21 +49,6 @@ display:
update-display-name: true update-display-name: true
# Обновлять tab list # Обновлять tab list
update-tab-list: true update-tab-list: true
# Использовать TextDisplay вместо обычного display name (1.19.4+)
use-text-display: true
# Высота TextDisplay над игроком (в блоках)
text-display-height: 2.8
# Размер TextDisplay (от 0.1 до 2.0)
text-display-scale: 0.8
# Видно ли TextDisplay через стены
text-display-see-through: false
# Формат TextDisplay (поддерживает плейсхолдеры и различные форматы цветов)
# Поддерживаемые форматы:
# <#RRGGBB> - MiniMessage HEX
# &#RRGGBB - Legacy HEX
# #RRGGBB - Простой HEX
# <red>, <blue> и т.д. - именованные цвета
text-display-format: "{prefix}{player}"
# Тип хранилища: YAML или SQLITE # Тип хранилища: YAML или SQLITE
storage-type: YAML storage-type: YAML