fix command and refactoring config

This commit is contained in:
loki5512344 2026-03-10 16:58:10 +01:00
parent b922f588d0
commit fe64b3d51f
109 changed files with 4103 additions and 5403 deletions

View file

@ -59,6 +59,7 @@ public final class LoChat extends JavaPlugin {
// Команды и слушатели
registerCommands();
registerListeners();
registerPluginChannels();
// Автосообщения
autoMessageManager.start();
@ -108,9 +109,11 @@ public final class LoChat extends JavaPlugin {
getCommand("reply").setExecutor(new ReplyCommand(this));
getCommand("ignore").setExecutor(new IgnoreCommand(this));
getCommand("unignore").setExecutor(new UnignoreCommand(this));
getCommand("ignorelist").setExecutor(new IgnoreListCommand(this));
getCommand("announce").setExecutor(new AnnounceCommand(this));
getCommand("chatspy").setExecutor(new ChatSpyCommand(this));
getCommand("clearchat").setExecutor(new ClearChatCommand(this));
getCommand("hub").setExecutor(new HubCommand(this));
NickCommand nickCmd = new NickCommand(this);
getCommand("nick").setExecutor(nickCmd);
@ -149,6 +152,11 @@ public final class LoChat extends JavaPlugin {
getLogger().info("Слушатели зарегистрированы (новая архитектура)");
}
private void registerPluginChannels() {
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
getLogger().info("BungeeCord plugin messaging channel registered");
}
public void reload() {
configManager.reload();
messageConfig.reload();

View file

@ -0,0 +1,107 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.commands.custom.*;
import org.bukkit.command.CommandExecutor;
import java.util.HashMap;
import java.util.Map;
/**
* Менеджер кастомных команд
*/
public class CustomCommandManager {
private final LoChat plugin;
private final Map<String, CommandExecutor> commands;
public CustomCommandManager(LoChat plugin) {
this.plugin = plugin;
this.commands = new HashMap<>();
registerCommands();
}
/**
* Регистрирует все кастомные команды
*/
private void registerCommands() {
// Информационные команды
commands.put("help", new HelpCommand(plugin));
commands.put("rules", new RulesCommand(plugin));
commands.put("discord", new DiscordCommand(plugin));
// Утилиты
commands.put("coords", new CoordsCommand(plugin));
commands.put("ping", new PingCommand(plugin));
commands.put("stats", new StatsCommand(plugin));
// Чат команды
commands.put("me", new MeCommand(plugin));
commands.put("sounds", new SoundsCommand(plugin));
// TODO: Добавить остальные команды
// commands.put("website", new WebsiteCommand(plugin));
// commands.put("time", new TimeCommand(plugin));
// commands.put("list", new ListCommand(plugin));
// commands.put("top", new TopCommand(plugin));
// commands.put("afk", new AfkCommand(plugin));
// commands.put("back", new BackCommand(plugin));
// commands.put("broadcast", new BroadcastCommand(plugin));
// commands.put("maintenance", new MaintenanceCommand(plugin));
// commands.put("motd", new MotdCommand(plugin));
// commands.put("tps", new TpsCommand(plugin));
// commands.put("memory", new MemoryCommand(plugin));
// commands.put("version", new VersionCommand(plugin));
// commands.put("reload", new ReloadCommand(plugin));
// commands.put("automessage", new AutoMessageCommand(plugin));
}
/**
* Регистрирует команды в Bukkit
*/
public void registerBukkitCommands() {
for (Map.Entry<String, CommandExecutor> entry : commands.entrySet()) {
String commandName = entry.getKey();
CommandExecutor executor = entry.getValue();
if (plugin.getCommand(commandName) != null) {
plugin.getCommand(commandName).setExecutor(executor);
plugin.getLogger().info("Зарегистрирована кастомная команда: /" + commandName);
} else {
plugin.getLogger().warning("Не удалось зарегистрировать команду: /" + commandName +
" (не найдена в plugin.yml)");
}
}
}
/**
* Получить исполнителя команды
*/
public CommandExecutor getCommandExecutor(String command) {
return commands.get(command.toLowerCase());
}
/**
* Проверить, является ли команда кастомной
*/
public boolean isCustomCommand(String command) {
return commands.containsKey(command.toLowerCase());
}
/**
* Получить все зарегистрированные команды
*/
public Map<String, CommandExecutor> getCommands() {
return new HashMap<>(commands);
}
/**
* Перезагрузить команды
*/
public void reload() {
commands.clear();
registerCommands();
registerBukkitCommands();
plugin.getLogger().info("Кастомные команды перезагружены!");
}
}

View file

@ -0,0 +1,52 @@
package com.loki.lochat.commands;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class HubCommand implements CommandExecutor {
private final LoChat plugin;
public HubCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("general.players-only")));
return true;
}
// Check if hub command is enabled
if (!plugin.getConfigManager().getBoolean("hub_command.enabled", true)) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("general.command-disabled")));
return true;
}
String hubServer = plugin.getConfigManager().getString("hub_command.server", "hub");
// Send player to hub server via BungeeCord
sendToServer(player, hubServer);
String message = plugin.getMessageConfig().get("hub.connecting")
.replace("%server%", hubServer);
player.sendMessage(ChatFormatter.parse(message));
return true;
}
private void sendToServer(Player player, String server) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(server);
player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray());
}
}

View file

@ -0,0 +1,60 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.IgnoreService;
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.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
import java.util.UUID;
/**
* Команда /ignorelist - показывает список игнорируемых игроков
*/
public class IgnoreListCommand implements CommandExecutor {
private final LoChat plugin;
private final IgnoreService ignoreService;
public IgnoreListCommand(LoChat plugin) {
this.plugin = plugin;
this.ignoreService = plugin.getServiceRegistry().get(IgnoreService.class);
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Только для игроков!");
return true;
}
Set<UUID> ignoredPlayers = ignoreService.getIgnoredPlayers(player.getUniqueId());
if (ignoredPlayers.isEmpty()) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.list-empty")));
return true;
}
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.list-header")));
for (UUID ignoredUuid : ignoredPlayers) {
String playerName = com.loki.lochat.utils.PlayerUtil.getPlayerName(ignoredUuid);
if (playerName != null) {
player.sendMessage(ChatFormatter.parse("&#87CEEB• &#FFFFFF" + playerName));
}
}
player.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("ignore.list-footer")
.replace("{count}", String.valueOf(ignoredPlayers.size()))
));
return true;
}
}

View file

@ -0,0 +1,45 @@
package com.loki.lochat.commands.custom;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда координат игрока
*/
public class CoordsCommand implements CommandExecutor {
private final LoChat plugin;
public CoordsCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(ChatFormatter.parse("<red>Эта команда только для игроков!</red>"));
return true;
}
int x = (int) player.getLocation().getX();
int y = (int) player.getLocation().getY();
int z = (int) player.getLocation().getZ();
String world = player.getWorld().getName();
String message = String.format("""
<gradient:#FFA726:#FF9800>Ваши координаты:</gradient>
<gradient:#87CEEB:#4682B4>X:</gradient> <white>%d</white>
<gradient:#87CEEB:#4682B4>Y:</gradient> <white>%d</white>
<gradient:#87CEEB:#4682B4>Z:</gradient> <white>%d</white>
<gradient:#87CEEB:#4682B4>Мир:</gradient> <white>%s</white>
""", x, y, z, world);
player.sendMessage(ChatFormatter.parse(message));
return true;
}
}

View file

@ -0,0 +1,41 @@
package com.loki.lochat.commands.custom;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
/**
* Команда Discord сервера
*/
public class DiscordCommand implements CommandExecutor {
private final LoChat plugin;
public DiscordCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
String discordLink = plugin.getConfig().getString("discord.invite-link", "discord.gg/example");
String message = String.format("""
<gradient:#7289DA:#5865F2>═══════ Наш Discord ═══════</gradient>
<gray>Присоединяйтесь к нашему сообществу!</gray>
<click:open_url:'https://%s'><gradient:#87CEEB:#4682B4>🔗 %s</gradient></click>
<gray>В Discord вы можете:</gray>
<white>•</white> <gray>Общаться с игроками</gray>
<white>•</white> <gray>Получать новости сервера</gray>
<white>•</white> <gray>Участвовать в событиях</gray>
<white>•</white> <gray>Получать поддержку</gray>
""", discordLink, discordLink);
sender.sendMessage(ChatFormatter.parse(message));
return true;
}
}

View file

@ -0,0 +1,44 @@
package com.loki.lochat.commands.custom;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда помощи сервера
*/
public class HelpCommand implements CommandExecutor {
private final LoChat plugin;
public HelpCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
String message = """
<gradient:#FFD700:#FFA500>═══════ Помощь по серверу ═══════</gradient>
<gradient:#87CEEB:#4682B4>/rules</gradient> <gray>- правила сервера</gray>
<gradient:#87CEEB:#4682B4>/discord</gradient> <gray>- наш Discord</gray>
<gradient:#87CEEB:#4682B4>/website</gradient> <gray>- официальный сайт</gray>
<gradient:#87CEEB:#4682B4>/coords</gradient> <gray>- ваши координаты</gray>
<gradient:#87CEEB:#4682B4>/time</gradient> <gray>- время на сервере</gray>
<gradient:#87CEEB:#4682B4>/ping</gradient> <gray>- ваш пинг</gray>
<gradient:#87CEEB:#4682B4>/list</gradient> <gray>- список игроков</gray>
<gradient:#87CEEB:#4682B4>/me <действие></gradient> <gray>- действие от третьего лица</gray>
<gradient:#87CEEB:#4682B4>/msg <игрок> <сообщение></gradient> <gray>- личное сообщение</gray>
<gradient:#87CEEB:#4682B4>/reply <сообщение></gradient> <gray>- ответить на ЛС</gray>
<gradient:#87CEEB:#4682B4>/ignore <игрок></gradient> <gray>- игнорировать игрока</gray>
<gradient:#87CEEB:#4682B4>/color <цвета></gradient> <gray>- градиентный ник</gray>
<gradient:#87CEEB:#4682B4>/prefix <текст></gradient> <gray>- кастомный префикс</gray>
""";
sender.sendMessage(ChatFormatter.parse(message));
return true;
}
}

View file

@ -0,0 +1,75 @@
package com.loki.lochat.commands.custom;
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.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда действия от третьего лица
*/
public class MeCommand implements CommandExecutor {
private final LoChat plugin;
public MeCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(ChatFormatter.parse("<red>Эта команда только для игроков!</red>"));
return true;
}
if (!player.hasPermission("chat.me")) {
player.sendMessage(ChatFormatter.parse("<red>У вас нет прав для использования этой команды!</red>"));
return true;
}
if (args.length == 0) {
player.sendMessage(ChatFormatter.parse("<red>Использование: /me <действие></red>"));
return true;
}
// Проверяем мут
if (plugin.getMuteService() != null && plugin.getMuteService().isMuted(player.getUniqueId())) {
String muteMessage = plugin.getMessagesConfig().getString("mute.you-muted",
"<red>Вы замучены!</red>");
player.sendMessage(ChatFormatter.parse(muteMessage));
return true;
}
// Собираем действие из аргументов
String action = String.join(" ", args);
// Получаем отображаемое имя игрока (с градиентом если есть)
String displayName = player.getDisplayName();
if (plugin.getGradientModule() != null) {
displayName = plugin.getGradientModule().getFormattedName(player);
}
// Форматируем сообщение действия
String message = String.format("<gray>* %s %s</gray>", displayName, action);
// Отправляем всем игрокам в радиусе (как локальный чат)
int radius = plugin.getConfig().getInt("chat.local.radius", 100);
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (onlinePlayer.getWorld().equals(player.getWorld()) &&
onlinePlayer.getLocation().distance(player.getLocation()) <= radius) {
onlinePlayer.sendMessage(ChatFormatter.parse(message));
}
}
// Логируем действие
plugin.getLogger().info(String.format("[ME] %s: %s", player.getName(), action));
return true;
}
}

View file

@ -0,0 +1,74 @@
package com.loki.lochat.commands.custom;
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.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда пинга игрока
*/
public class PingCommand implements CommandExecutor {
private final LoChat plugin;
public PingCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(ChatFormatter.parse("<red>Эта команда только для игроков!</red>"));
return true;
}
// Если указан аргумент - показать пинг другого игрока
if (args.length > 0) {
if (!player.hasPermission("lochat.ping.others")) {
player.sendMessage(ChatFormatter.parse("<red>У вас нет прав для просмотра пинга других игроков!</red>"));
return true;
}
Player target = Bukkit.getPlayer(args[0]);
if (target == null || !target.isOnline()) {
player.sendMessage(ChatFormatter.parse("<red>Игрок не найден или не в сети!</red>"));
return true;
}
int ping = target.getPing();
String color = getPingColor(ping);
String message = String.format(
"<gradient:#87CEEB:#4682B4>Пинг игрока %s:</gradient> <%s>%dмс</%s>",
target.getName(), color, ping, color
);
player.sendMessage(ChatFormatter.parse(message));
} else {
// Показать свой пинг
int ping = player.getPing();
String color = getPingColor(ping);
String message = String.format(
"<gradient:#87CEEB:#4682B4>Ваш пинг:</gradient> <%s>%dмс</%s>",
color, ping, color
);
player.sendMessage(ChatFormatter.parse(message));
}
return true;
}
private String getPingColor(int ping) {
if (ping < 50) return "green";
if (ping < 100) return "yellow";
if (ping < 200) return "gold";
return "red";
}
}

View file

@ -0,0 +1,41 @@
package com.loki.lochat.commands.custom;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
/**
* Команда правил сервера
*/
public class RulesCommand implements CommandExecutor {
private final LoChat plugin;
public RulesCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
String message = """
<gradient:#FF6B6B:#FF4757>═══════ Правила сервера ═══════</gradient>
<white>1.</white> <gray>Не используйте читы и моды</gray>
<white>2.</white> <gray>Уважайте других игроков</gray>
<white>3.</white> <gray>Не спамьте в чате</gray>
<white>4.</white> <gray>Не стройте рядом с чужими постройками</gray>
<white>5.</white> <gray>Не используйте мат и оскорбления</gray>
<white>6.</white> <gray>Не рекламируйте другие серверы</gray>
<white>7.</white> <gray>Слушайтесь администрацию</gray>
<white>8.</white> <gray>Не злоупотребляйте багами</gray>
<gradient:#FFA726:#FF9800>Нарушение правил карается мутом или баном!</gradient>
<gradient:#87CEEB:#4682B4>За помощью обращайтесь к администрации</gradient>
""";
sender.sendMessage(ChatFormatter.parse(message));
return true;
}
}

View file

@ -0,0 +1,79 @@
package com.loki.lochat.commands.custom;
import com.loki.lochat.LoChat;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда управления звуками
*/
public class SoundsCommand implements CommandExecutor {
private final LoChat plugin;
public SoundsCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(ChatFormatter.parse("<red>Эта команда только для игроков!</red>"));
return true;
}
String playerUUID = player.getUniqueId().toString();
boolean currentState = plugin.getPlayerData().getBoolean("players." + playerUUID + ".settings.sounds-enabled", true);
if (args.length == 0) {
// Показать текущее состояние
String status = currentState ? "<green>включены</green>" : "<red>отключены</red>";
String message = String.format(
"<gradient:#87CEEB:#4682B4>Звуки сейчас %s</gradient>\n" +
"<gray>Используйте /sounds on или /sounds off для изменения</gray>",
status
);
player.sendMessage(ChatFormatter.parse(message));
return true;
}
String action = args[0].toLowerCase();
boolean newState;
switch (action) {
case "on", "включить", "вкл" -> {
newState = true;
player.sendMessage(ChatFormatter.parse("<green>Звуки включены!</green>"));
}
case "off", "отключить", "выкл" -> {
newState = false;
player.sendMessage(ChatFormatter.parse("<red>Звуки отключены!</red>"));
}
case "toggle", "переключить" -> {
newState = !currentState;
String status = newState ? "<green>включены</green>" : "<red>отключены</red>";
player.sendMessage(ChatFormatter.parse(String.format("Звуки %s!", status)));
}
default -> {
player.sendMessage(ChatFormatter.parse("""
<red>Неверное использование!</red>
<gray>Доступные команды:</gray>
<white>/sounds on</white> <gray>- включить звуки</gray>
<white>/sounds off</white> <gray>- отключить звуки</gray>
<white>/sounds toggle</white> <gray>- переключить</gray>
"""));
return true;
}
}
// Сохраняем настройку
plugin.getPlayerData().set("players." + playerUUID + ".settings.sounds-enabled", newState);
plugin.savePlayerData();
return true;
}
}

View file

@ -0,0 +1,114 @@
package com.loki.lochat.commands.custom;
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.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда статистики чата
*/
public class StatsCommand implements CommandExecutor {
private final LoChat plugin;
public StatsCommand(LoChat plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!sender.hasPermission("lochat.stats")) {
sender.sendMessage(ChatFormatter.parse("<red>У вас нет прав для просмотра статистики!</red>"));
return true;
}
if (args.length > 0) {
// Статистика конкретного игрока
Player target = Bukkit.getPlayer(args[0]);
if (target == null) {
sender.sendMessage(ChatFormatter.parse("<red>Игрок не найден!</red>"));
return true;
}
showPlayerStats(sender, target);
} else {
// Общая статистика сервера
showServerStats(sender);
}
return true;
}
private void showPlayerStats(CommandSender sender, Player target) {
// Получаем статистику игрока из конфига или базы данных
int messagesSent = plugin.getPlayerData().getInt("players." + target.getUniqueId() + ".messages-sent", 0);
int commandsUsed = plugin.getPlayerData().getInt("players." + target.getUniqueId() + ".commands-used", 0);
long playtime = plugin.getPlayerData().getLong("players." + target.getUniqueId() + ".total-playtime", 0);
String playtimeFormatted = formatTime(playtime);
String message = String.format("""
<gradient:#FFD700:#FFA500>═══ Статистика игрока %s ═══</gradient>
<gradient:#87CEEB:#4682B4>Сообщений отправлено:</gradient> <white>%d</white>
<gradient:#87CEEB:#4682B4>Команд использовано:</gradient> <white>%d</white>
<gradient:#87CEEB:#4682B4>Время в игре:</gradient> <white>%s</white>
<gradient:#87CEEB:#4682B4>Статус:</gradient> <green>В сети</green>
<gradient:#87CEEB:#4682B4>Пинг:</gradient> <yellow>%dмс</yellow>
""", target.getName(), messagesSent, commandsUsed, playtimeFormatted, target.getPing());
sender.sendMessage(ChatFormatter.parse(message));
}
private void showServerStats(CommandSender sender) {
// Получаем общую статистику сервера
int totalMessages = plugin.getStatisticsData().getInt("global.total-messages", 0);
int messagesToday = plugin.getStatisticsData().getInt("global.messages-today", 0);
int onlinePlayers = Bukkit.getOnlinePlayers().size();
int maxPlayers = Bukkit.getMaxPlayers();
String message = String.format("""
<gradient:#FFD700:#FFA500>═══ Статистика сервера ═══</gradient>
<gradient:#87CEEB:#4682B4>Всего сообщений:</gradient> <white>%d</white>
<gradient:#87CEEB:#4682B4>Сообщений сегодня:</gradient> <white>%d</white>
<gradient:#87CEEB:#4682B4>Игроков онлайн:</gradient> <white>%d/%d</white>
<gradient:#87CEEB:#4682B4>Активных мутов:</gradient> <white>%d</white>
<gradient:#87CEEB:#4682B4>TPS:</gradient> <green>%.1f</green>
""", totalMessages, messagesToday, onlinePlayers, maxPlayers,
getActiveMutesCount(), getCurrentTPS());
sender.sendMessage(ChatFormatter.parse(message));
}
private String formatTime(long milliseconds) {
long seconds = milliseconds / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if (days > 0) {
return String.format("%dд %dч %dм", days, hours % 24, minutes % 60);
} else if (hours > 0) {
return String.format("%dч %dм", hours, minutes % 60);
} else {
return String.format("%dм", minutes);
}
}
private int getActiveMutesCount() {
return plugin.getMuteData().getConfigurationSection("active-mutes") != null ?
plugin.getMuteData().getConfigurationSection("active-mutes").getKeys(false).size() : 0;
}
private double getCurrentTPS() {
try {
return Bukkit.getTPS()[0];
} catch (Exception e) {
return 20.0;
}
}
}

View file

@ -22,25 +22,69 @@ public class AdvancedMessageFilter {
"(.)\\1{3,}"
);
// Паттерн для скрытых ссылок (h t t p, w w w и т.д.)
private static final Pattern HIDDEN_URL_PATTERN = Pattern.compile(
"(?i)(h\\s*t\\s*t\\s*p|w\\s*w\\s*w|d\\s*o\\s*t\\s*c\\s*o\\s*m)",
Pattern.CASE_INSENSITIVE
);
// Паттерн для доменов без http
private static final Pattern DOMAIN_PATTERN = Pattern.compile(
"\\b([a-zA-Z0-9-]+\\.(com|net|org|ru|gg|me|io|xyz|tk|ml|ga|cf|gq))\\b",
Pattern.CASE_INSENSITIVE
);
private final FileConfiguration config;
private final List<String> whitelistedDomains;
private final List<String> blacklistedDomains;
private final CapsFilter capsFilter;
public AdvancedMessageFilter(FileConfiguration config) {
this.config = config;
this.whitelistedDomains = config.getStringList("filters.url.whitelist");
this.blacklistedDomains = config.getStringList("filters.url.blacklist");
this.whitelistedDomains = config.getStringList("filters.advertising.whitelist");
this.blacklistedDomains = config.getStringList("filters.advertising.blacklist");
// Инициализация капс фильтра
int maxCapsPercent = config.getInt("filters.caps.max-percent", 70);
int minLength = config.getInt("filters.caps.min-length", 5);
boolean autoLowercase = config.getBoolean("filters.caps.auto-lowercase", true);
boolean blockMessage = config.getBoolean("filters.caps.block", false);
this.capsFilter = new CapsFilter(maxCapsPercent, minLength, autoLowercase, blockMessage);
}
public FilterResult filterMessage(Player player, String message) {
if (config.getBoolean("filters.url.enabled", true)) {
// Капс фильтр
if (config.getBoolean("filters.caps.enabled", true)) {
String filtered = capsFilter.filter(player, message);
if (filtered == null) {
return new FilterResult(false, message, "&#FF6B6BНе кричите в чате!");
}
message = filtered;
}
// Анти-реклама (объединенная проверка)
if (config.getBoolean("filters.advertising.enabled", true)) {
// URL фильтр
FilterResult urlResult = filterUrls(player, message);
if (!urlResult.allowed()) {
return urlResult;
}
message = urlResult.filteredMessage();
// Скрытые ссылки
FilterResult hiddenResult = filterHiddenUrls(player, message);
if (!hiddenResult.allowed()) {
return hiddenResult;
}
// Домены без http
FilterResult domainResult = filterDomains(player, message);
if (!domainResult.allowed()) {
return domainResult;
}
}
// IP фильтр
if (config.getBoolean("filters.ip.enabled", true)) {
FilterResult ipResult = filterIPs(player, message);
if (!ipResult.allowed()) {
@ -49,6 +93,7 @@ public class AdvancedMessageFilter {
message = ipResult.filteredMessage();
}
// Повторяющиеся символы
if (config.getBoolean("filters.repeat.enabled", true)) {
message = filterRepeatingChars(message);
}
@ -138,6 +183,70 @@ public class AdvancedMessageFilter {
return url;
}
/**
* Фильтрует скрытые ссылки (h t t p, w w w и т.д.)
*/
private FilterResult filterHiddenUrls(Player player, String message) {
if (!config.getBoolean("filters.advertising.block-hidden-urls", true)) {
return new FilterResult(true, message, null);
}
if (player.hasPermission("lochat.bypass.hiddenurls")) {
return new FilterResult(true, message, null);
}
Matcher matcher = HIDDEN_URL_PATTERN.matcher(message);
if (matcher.find()) {
return new FilterResult(false, message,
config.getString("filters.advertising.blocked-message", "&#FF6B6BРеклама запрещена!"));
}
return new FilterResult(true, message, null);
}
/**
* Фильтрует домены без http (example.com, discord.gg и т.д.)
*/
private FilterResult filterDomains(Player player, String message) {
if (!config.getBoolean("filters.advertising.block-domains", true)) {
return new FilterResult(true, message, null);
}
if (player.hasPermission("lochat.bypass.domains")) {
return new FilterResult(true, message, null);
}
Matcher matcher = DOMAIN_PATTERN.matcher(message);
while (matcher.find()) {
String domain = matcher.group();
// Проверяем whitelist
if (!whitelistedDomains.isEmpty()) {
boolean isWhitelisted = whitelistedDomains.stream()
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
if (!isWhitelisted) {
return new FilterResult(false, message,
config.getString("filters.advertising.blocked-message", "&#FF6B6BРеклама запрещена!"));
}
}
// Проверяем blacklist
if (!blacklistedDomains.isEmpty()) {
boolean isBlacklisted = blacklistedDomains.stream()
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
if (isBlacklisted) {
return new FilterResult(false, message,
config.getString("filters.advertising.blocked-message", "&#FF6B6BРеклама запрещена!"));
}
}
}
return new FilterResult(true, message, null);
}
public record FilterResult(boolean allowed, String filteredMessage, String blockReason) {
}
}

View file

@ -0,0 +1,83 @@
package com.loki.lochat.core.filter;
import org.bukkit.entity.Player;
/**
* Фильтр капса (заглавных букв)
*/
public class CapsFilter {
private final int maxCapsPercent;
private final int minLength;
private final boolean autoLowercase;
private final boolean blockMessage;
public CapsFilter(int maxCapsPercent, int minLength, boolean autoLowercase, boolean blockMessage) {
this.maxCapsPercent = maxCapsPercent;
this.minLength = minLength;
this.autoLowercase = autoLowercase;
this.blockMessage = blockMessage;
}
/**
* Проверяет и обрабатывает сообщение с капсом
*
* @return обработанное сообщение или null если заблокировано
*/
public String filter(Player player, String message) {
// Bypass для админов
if (player.hasPermission("lochat.bypass.caps")) {
return message;
}
// Игнорируем короткие сообщения
if (message.length() < minLength) {
return message;
}
// Подсчитываем процент заглавных букв
int totalLetters = 0;
int capsLetters = 0;
for (char c : message.toCharArray()) {
if (Character.isLetter(c)) {
totalLetters++;
if (Character.isUpperCase(c)) {
capsLetters++;
}
}
}
// Если букв мало, пропускаем
if (totalLetters < 3) {
return message;
}
int capsPercent = (capsLetters * 100) / totalLetters;
// Если капса больше лимита
if (capsPercent > maxCapsPercent) {
if (blockMessage) {
return null; // Блокируем сообщение
} else if (autoLowercase) {
// Понижаем регистр (первая буква заглавная)
return capitalizeFirst(message.toLowerCase());
}
}
return message;
}
/**
* Делает первую букву заглавной
*/
private String capitalizeFirst(String text) {
if (text == null || text.isEmpty()) {
return text;
}
return Character.toUpperCase(text.charAt(0)) + text.substring(1);
}
public int getMaxCapsPercent() {
return maxCapsPercent;
}
}

View file

@ -32,20 +32,40 @@ public class MuteServiceImpl implements MuteService {
@Override
public void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy) {
if (uuid == null) {
plugin.getLogger().warning("Attempted to mute player with null UUID");
return;
}
if (playerName == null || playerName.isEmpty()) {
plugin.getLogger().warning("Attempted to mute player with null or empty name");
return;
}
long endTime = duration > 0 ? System.currentTimeMillis() + duration : 0;
MuteData data = new MuteData(uuid, playerName, endTime, reason, mutedBy);
mutes.put(uuid, data);
historyManager.addEntry(uuid, playerName, duration, reason, mutedBy);
saveAsync();
plugin.getLogger().info("Player " + playerName + " muted by " + mutedBy +
" for " + (duration > 0 ? formatTime(duration) : "permanent") +
(reason != null ? " (reason: " + reason + ")" : ""));
}
@Override
public boolean unmute(UUID uuid, String unmutedBy) {
if (uuid == null) {
plugin.getLogger().warning("Attempted to unmute player with null UUID");
return false;
}
MuteData removed = mutes.remove(uuid);
if (removed != null) {
historyManager.updateUnmute(uuid, unmutedBy);
saveAsync();
plugin.getLogger().info("Player " + removed.getPlayerName() + " unmuted by " + unmutedBy);
return true;
}
return false;
@ -53,6 +73,10 @@ public class MuteServiceImpl implements MuteService {
@Override
public boolean isMuted(UUID uuid) {
if (uuid == null) {
return false;
}
MuteData data = mutes.get(uuid);
if (data == null) {
return false;
@ -61,6 +85,7 @@ public class MuteServiceImpl implements MuteService {
if (isExpired(data)) {
mutes.remove(uuid);
saveAsync();
plugin.getLogger().info("Mute expired for player " + data.getPlayerName());
return false;
}
return true;

View file

@ -19,9 +19,9 @@ public class ChatMessage {
}
public static ChatMessage create(Player sender, String rawMessage) {
// Определяем тип чата
boolean isGlobal = rawMessage.startsWith("!") &&
sender.hasPermission("chat.global.use");
// С "!" = глобальный чат
// Без "!" = локальный чат
boolean isGlobal = rawMessage.startsWith("!");
String content = isGlobal ?
rawMessage.substring(1).trim() :

View file

@ -28,100 +28,88 @@ public class GradientConfig {
}
private void loadConfig() {
configFile = new File(plugin.getDataFolder(), "gradient-config.yml");
if (!configFile.exists()) {
plugin.saveResource("gradient-config.yml", false);
}
config = YamlConfiguration.loadConfiguration(configFile);
// Загружаем дефолты
InputStream defStream = plugin.getResource("gradient-config.yml");
if (defStream != null) {
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(
new InputStreamReader(defStream, StandardCharsets.UTF_8));
config.setDefaults(defConfig);
}
// Читаем из главного config.yml вместо отдельного файла
config = plugin.getConfig();
plugin.getLogger().info("Gradient конфиг загружен из config.yml");
}
public void reload() {
plugin.reloadConfig();
this.mainConfig = plugin.getConfig();
loadConfig();
this.config = plugin.getConfig();
}
public void save() {
try {
config.save(configFile);
} catch (IOException e) {
plugin.getLogger().warning("Ошибка сохранения gradient-config.yml: " + e.getMessage());
}
// Сохранение не требуется, используем главный конфиг
plugin.saveConfig();
}
public boolean isEnabled() {
return config.getBoolean("enabled", true);
return config.getBoolean("gradient.enabled", true);
}
public int getMaxColors() {
return config.getInt("max-colors", 7);
return config.getInt("gradient.max-colors", 7);
}
public int getMinColors() {
return config.getInt("min-colors", 1);
return config.getInt("gradient.min-colors", 1);
}
public int getPricePerColor() {
return config.getInt("price-per-color", 50);
return config.getInt("gradient.price-per-color", 50);
}
public int getPrefixPrice() {
return config.getInt("prefix-price", 500);
return config.getInt("gradient.prefix-price", 500);
}
public boolean isPrefixOneTimePurchase() {
return config.getBoolean("prefix-one-time-purchase", true);
return config.getBoolean("gradient.prefix-one-time-purchase", true);
}
public int getColorCooldown() {
return config.getInt("color-cooldown", 60);
return config.getInt("gradient.color-cooldown", 60);
}
public int getPrefixCooldown() {
return config.getInt("prefix-cooldown", 300);
return config.getInt("gradient.prefix-cooldown", 120);
}
public int getMaxPrefixLength() {
return config.getInt("max-prefix-length", 7);
return config.getInt("gradient.max-prefix-length", 16);
}
public String getPrefixFormat() {
return config.getString("prefix-format", "[{prefix}] ");
return config.getString("gradient.prefix-format", "[{prefix}] ");
}
public boolean isGradientOnPrefix() {
return config.getBoolean("gradient-on-prefix", true);
return config.getBoolean("gradient.gradient-on-prefix", true);
}
public boolean isGradientOnLuckPermsPrefix() {
return config.getBoolean("gradient-on-luckperms-prefix", true);
return config.getBoolean("gradient.gradient-on-luckperms-prefix", true);
}
public boolean isContinuousGradient() {
return config.getBoolean("continuous-gradient", true);
return config.getBoolean("gradient.continuous-gradient", true);
}
public boolean isUseLegacyRgbFormat() {
return config.getBoolean("use-legacy-rgb-format", true);
return config.getBoolean("gradient.use-legacy-rgb-format", true);
}
public String getStorageType() {
return config.getString("storage-type", "YAML");
return config.getString("gradient.storage-type", "file");
}
public String getSqliteFile() {
return config.getString("sqlite-file", "gradient-data.db");
return config.getString("gradient.sqlite-file", "gradient-data.db");
}
public List<String> getPrefixBlacklist() {
return config.getStringList("prefix-blacklist");
return config.getStringList("gradient.prefix-blacklist");
}
public boolean isPrefixBlacklisted(String prefix) {

View file

@ -27,13 +27,13 @@ public class GradientMessages {
}
private void loadMessages() {
messagesFile = new File(plugin.getDataFolder(), "gradient-messages.yml");
messagesFile = new File(plugin.getDataFolder(), "messages.yml");
if (!messagesFile.exists()) {
plugin.saveResource("gradient-messages.yml", false);
plugin.saveResource("messages.yml", false);
}
messages = YamlConfiguration.loadConfiguration(messagesFile);
InputStream defStream = plugin.getResource("gradient-messages.yml");
InputStream defStream = plugin.getResource("messages.yml");
if (defStream != null) {
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(
new InputStreamReader(defStream, StandardCharsets.UTF_8));
@ -49,7 +49,7 @@ public class GradientMessages {
* Получает сообщение как строку с MiniMessage тегами
*/
public String get(String key) {
String message = messages.getString(key, "&#FF0000Сообщение не найдено: " + key);
String message = messages.getString("gradient." + key, "&#FF0000Сообщение не найдено: " + key);
return ChatFormatter.convertAllColors(message);
}
@ -57,7 +57,7 @@ public class GradientMessages {
* Получает сообщение как строку с заменами
*/
public String get(String key, String... replacements) {
String message = messages.getString(key, "&#FF0000Сообщение не найдено: " + key);
String message = messages.getString("gradient." + key, "&#FF0000Сообщение не найдено: " + key);
for (int i = 0; i < replacements.length; i += 2) {
if (i + 1 < replacements.length) {
message = message.replace("{" + replacements[i] + "}", replacements[i + 1]);

View file

@ -54,23 +54,44 @@ public class ChatEventListener implements Listener {
}
String filteredMessage = filterResult.filteredMessage();
event.message(Component.text(filteredMessage));
// Определяем тип чата
boolean isGlobal = message.startsWith("!");
event.message(com.loki.lochat.utils.ChatFormatter.parse(filteredMessage));
event.renderer(new EnhancedChatRenderer(sender, event.message()));
event.renderer(new EnhancedChatRenderer(sender, event.message(), isGlobal));
}
private class EnhancedChatRenderer implements ChatRenderer {
private final Player sender;
private final Component originalMessage;
private final boolean isGlobal;
public EnhancedChatRenderer(Player sender, Component originalMessage) {
public EnhancedChatRenderer(Player sender, Component originalMessage, boolean isGlobal) {
this.sender = sender;
this.originalMessage = originalMessage;
this.isGlobal = isGlobal;
}
@Override
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
String prefix = plugin.getConfig().getString("chat.global.prefix", "[G]");
// Префикс в зависимости от типа чата
Component prefixComponent;
if (isGlobal) {
// [G] с желтой буквой G
prefixComponent = Component.text("[")
.color(net.kyori.adventure.text.format.NamedTextColor.GRAY)
.append(Component.text("G").color(net.kyori.adventure.text.format.NamedTextColor.GOLD))
.append(Component.text("]").color(net.kyori.adventure.text.format.NamedTextColor.GRAY));
} else {
// [L] с голубой буквой L
prefixComponent = Component.text("[")
.color(net.kyori.adventure.text.format.NamedTextColor.GRAY)
.append(Component.text("L").color(net.kyori.adventure.text.format.NamedTextColor.AQUA))
.append(Component.text("]").color(net.kyori.adventure.text.format.NamedTextColor.GRAY));
}
Component playerComponent = createPlayerComponent(source, sourceDisplayName);
Component processedMessage = message;
@ -82,9 +103,10 @@ public class ChatEventListener implements Listener {
processedMessage = mentionHandler.processMentions(processedMessage, source, viewerPlayer);
}
return Component.text(prefix + " ")
return prefixComponent
.append(Component.text(" "))
.append(playerComponent)
.append(Component.text(": "))
.append(Component.text(": ").color(net.kyori.adventure.text.format.NamedTextColor.GRAY))
.append(processedMessage);
}
@ -129,7 +151,8 @@ public class ChatEventListener implements Listener {
private Component processUrls(Component message) {
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!35538117-c5a8-4bfe-b9f2-b0dcf9374222'()*+,;=%]+)";
// Fixed regex: escape the dash in character class to avoid illegal range
String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)";
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(urlPattern);
java.util.regex.Matcher matcher = pattern.matcher(plainText);

View file

@ -31,20 +31,15 @@ public class AutoMessageManager {
}
private void loadMessages() {
File file = new File(plugin.getDataFolder(), "automessages.yml");
if (!file.exists()) {
plugin.saveResource("automessages.yml", false);
}
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
FileConfiguration config = plugin.getConfig();
// Настройки
prefix = config.getString("settings.prefix", "&6[&eINFO&6]&r");
mode = config.getString("settings.mode", "sequential").toLowerCase();
prefix = plugin.getConfigManager().getString("prefix", "&#00D4FF[LoChat]");
mode = "sequential"; // Всегда последовательно
// Загружаем сообщения
// Загружаем сообщения из config.yml
messages = new LinkedHashMap<>();
ConfigurationSection msgSection = config.getConfigurationSection("messages");
ConfigurationSection msgSection = config.getConfigurationSection("automessages-list");
if (msgSection != null) {
for (String key : msgSection.getKeys(false)) {
@ -65,32 +60,7 @@ public class AutoMessageManager {
}
}
// Определяем порядок
messageOrder = new ArrayList<>();
if (mode.equals("custom")) {
String orderStr = config.getString("settings.order", "");
for (String item : orderStr.split(",")) {
String trimmed = item.trim();
if (!trimmed.isEmpty()) {
try {
int index = Integer.parseInt(trimmed) - 1;
List<String> keys = new ArrayList<>(messages.keySet());
if (index >= 0 && index < keys.size()) {
messageOrder.add(keys.get(index));
}
} catch (NumberFormatException e) {
if (messages.containsKey(trimmed)) {
messageOrder.add(trimmed);
}
}
}
}
}
if (messageOrder.isEmpty()) {
messageOrder.addAll(messages.keySet());
}
messageOrder = new ArrayList<>(messages.keySet());
plugin.getLogger().info("Загружено " + messages.size() + " автосообщений");
}

View file

@ -1,51 +0,0 @@
package com.loki.lochat.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.command.CommandSender;
/**
* Утилита для форматирования сообщений
*/
public class ChatFormatter {
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.legacyAmpersand();
public static Object parse(String message) {
try {
return MINI_MESSAGE.deserialize(message);
} catch (Exception e) {
return LEGACY.deserialize(message);
}
}
public static String toPlain(Object message) {
if (message instanceof Component component) {
return LegacyComponentSerializer.legacySection().serialize(component);
}
return message.toString();
}
public static void sendMessage(CommandSender sender, Object message) {
if (message instanceof Component component) {
sender.sendMessage(component);
} else {
sender.sendMessage(message.toString());
}
}
public static void broadcast(Object message) {
if (message instanceof Component component) {
org.bukkit.Bukkit.broadcast(component);
}
}
public static String stripTags(String message) {
return message.replaceAll("<[^>]+>", "").replaceAll("§[0-9a-fk-or]", "");
}
public static String convertAllColors(String message) {
return message.replace("&", "§");
}
}

View file

@ -40,24 +40,24 @@ public final class ChatFormatter {
private static String convertLegacyFormats(String message) {
if (message == null) return "";
// Конвертируем &#RRGGBB в <#RRGGBB>
// Конвертируем &#RRGGBB в <color:#RRGGBB>
java.util.regex.Pattern pattern1 = java.util.regex.Pattern.compile("&#([0-9a-fA-F]{6})");
java.util.regex.Matcher matcher1 = pattern1.matcher(message);
StringBuffer sb1 = new StringBuffer();
while (matcher1.find()) {
String hex = matcher1.group(1).toLowerCase();
matcher1.appendReplacement(sb1, "<#" + hex + ">");
matcher1.appendReplacement(sb1, "<color:#" + hex + ">");
}
matcher1.appendTail(sb1);
message = sb1.toString();
// Конвертируем #RRGGBB в <#RRGGBB> (только если не внутри тегов)
// Конвертируем #RRGGBB в <color:#RRGGBB> (только если не внутри тегов)
java.util.regex.Pattern pattern2 = java.util.regex.Pattern.compile("(?<!<)#([0-9a-fA-F]{6})(?![^<]*>)");
java.util.regex.Matcher matcher2 = pattern2.matcher(message);
StringBuffer sb2 = new StringBuffer();
while (matcher2.find()) {
String hex = matcher2.group(1).toLowerCase();
matcher2.appendReplacement(sb2, "<#" + hex + ">");
matcher2.appendReplacement(sb2, "<color:#" + hex + ">");
}
matcher2.appendTail(sb2);
message = sb2.toString();

View file

@ -17,20 +17,32 @@ public final class PlayerUtil {
/**
* Safely parses a sound name from config.
* Uses deprecated Sound.valueOf() for backward compatibility with string-based config values.
* Uses Registry API for modern sound parsing with fallback to reflection.
*
* @param soundName the sound name to parse
* @param defaultSound the default sound to return if parsing fails
* @return the parsed Sound or the default if invalid
*/
@SuppressWarnings("deprecation")
public static Sound parseSound(String soundName, Sound defaultSound) {
if (soundName == null || soundName.isEmpty()) {
return defaultSound;
}
try {
return Sound.valueOf(soundName.toUpperCase());
} catch (IllegalArgumentException e) {
// Try to get sound by key using Registry (modern way)
Sound sound = org.bukkit.Registry.SOUNDS.match(soundName);
if (sound != null) {
return sound;
}
} catch (Exception e) {
// Registry failed, try fallback
}
// Fallback: try direct field access via reflection
try {
java.lang.reflect.Field field = Sound.class.getField(soundName.toUpperCase().trim());
return (Sound) field.get(null);
} catch (Exception ex) {
return defaultSound;
}
}

View file

@ -6,9 +6,13 @@ public final class TimeFormatter {
}
public static String format(long millis) {
if (millis <= 0) {
if (millis < 0) {
return "0с";
}
if (millis == 0) {
return "навсегда";
}
long seconds = millis / 1000;
long minutes = seconds / 60;
@ -16,18 +20,21 @@ public final class TimeFormatter {
long days = hours / 24;
if (days > 0) {
return days + "д " + (hours % 24) + "ч";
long remainingHours = hours % 24;
return remainingHours > 0 ? days + "д " + remainingHours + "ч" : days + "д";
} else if (hours > 0) {
return hours + "ч " + (minutes % 60) + "м";
long remainingMinutes = minutes % 60;
return remainingMinutes > 0 ? hours + "ч " + remainingMinutes + "м" : hours + "ч";
} else if (minutes > 0) {
return minutes + "м " + (seconds % 60) + "с";
long remainingSeconds = seconds % 60;
return remainingSeconds > 0 ? minutes + "м " + remainingSeconds + "с" : minutes + "м";
} else {
return seconds + "с";
}
}
public static long parse(String timeStr) {
if (timeStr == null || timeStr.isEmpty()) {
if (timeStr == null || timeStr.trim().isEmpty()) {
return 0;
}
@ -38,8 +45,12 @@ public final class TimeFormatter {
if (Character.isDigit(c)) {
number.append(c);
} else if (number.length() > 0) {
long value = Long.parseLong(number.toString());
totalMillis += getMillisForUnit(c, value);
try {
long value = Long.parseLong(number.toString());
totalMillis += getMillisForUnit(c, value);
} catch (NumberFormatException e) {
// Skip invalid numbers
}
number.setLength(0);
}
}