diff --git a/src/main/java/com/loki/lochat/LoChat.java b/src/main/java/com/loki/lochat/LoChat.java index 016cdb4..90115f2 100644 --- a/src/main/java/com/loki/lochat/LoChat.java +++ b/src/main/java/com/loki/lochat/LoChat.java @@ -47,7 +47,8 @@ public final class LoChat extends JavaPlugin { @Override public void onEnable() { instance = this; - getLogger().info("Загрузка LoChat v" + getPluginMeta().getVersion() + " для Paper/Folia " + Bukkit.getMinecraftVersion() + "..."); + com.loki.lochat.compat.Adventure.init(this); + getLogger().info("Загрузка LoChat v" + getDescription().getVersion() + " для Spigot " + Bukkit.getBukkitVersion() + "..."); // Инициализация initializer = new PluginInitializer(this); @@ -95,6 +96,7 @@ public final class LoChat extends JavaPlugin { @Override public void onDisable() { shutdown.shutdown(serviceRegistry, autoMessageManager, discordIntegration, gradientModule); + com.loki.lochat.compat.Adventure.close(); } public void reload() { diff --git a/src/main/java/com/loki/lochat/compat/Adventure.java b/src/main/java/com/loki/lochat/compat/Adventure.java new file mode 100644 index 0000000..a5cc300 --- /dev/null +++ b/src/main/java/com/loki/lochat/compat/Adventure.java @@ -0,0 +1,50 @@ +package com.loki.lochat.compat; + +import net.kyori.adventure.platform.bukkit.BukkitAudiences; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Nullable; + +/** + * Holds BukkitAudiences for Spigot. Falls back to legacy text if creation fails. + */ +public final class Adventure { + + private static BukkitAudiences audiences; + private static boolean fallback; + + private Adventure() { + } + + public static void init(Plugin plugin) { + try { + audiences = BukkitAudiences.create(plugin); + fallback = false; + } catch (Throwable t) { + audiences = null; + fallback = true; + plugin.getLogger().warning( + "[LoChat] BukkitAudiences init failed, falling back to legacy text: " + t.getMessage()); + } + } + + public static void close() { + if (audiences != null) { + try { + audiences.close(); + } catch (Exception ignored) { + } + audiences = null; + } + fallback = false; + } + + @Nullable + public static BukkitAudiences audiences() { + return audiences; + } + + public static boolean isFallback() { + return fallback || audiences == null; + } +} diff --git a/src/main/java/com/loki/lochat/compat/Msg.java b/src/main/java/com/loki/lochat/compat/Msg.java new file mode 100644 index 0000000..155688b --- /dev/null +++ b/src/main/java/com/loki/lochat/compat/Msg.java @@ -0,0 +1,174 @@ +package com.loki.lochat.compat; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.kyori.adventure.title.Title; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.meta.ItemMeta; + +import java.time.Duration; +import java.util.Collection; +import java.util.List; + +/** + * Static helpers for sending Adventure components on Spigot via BukkitAudiences. + * Falls back to legacy String when audiences not available. + */ +public final class Msg { + + private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.builder() + .character('§') + .hexColors() + .useUnusualXRepeatedCharacterHexFormat() + .build(); + + private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText(); + + private Msg() { + } + + public static void send(CommandSender sender, Component component) { + if (sender == null || component == null) { + return; + } + if (!Adventure.isFallback() && Adventure.audiences() != null) { + try { + Adventure.audiences().sender(sender).sendMessage(component); + return; + } catch (Throwable ignored) { + } + } + sender.sendMessage(legacy(component)); + } + + public static void send(Collection senders, Component component) { + if (senders == null || component == null) { + return; + } + for (CommandSender s : senders) { + send(s, component); + } + } + + public static void broadcast(Component component) { + if (component == null) { + return; + } + if (!Adventure.isFallback() && Adventure.audiences() != null) { + try { + Adventure.audiences().all().sendMessage(component); + return; + } catch (Throwable ignored) { + } + } + String legacy = legacy(component); + Bukkit.getOnlinePlayers().forEach(p -> p.sendMessage(legacy)); + Bukkit.getConsoleSender().sendMessage(legacy); + } + + public static void actionBar(Player player, Component component) { + if (player == null || component == null) { + return; + } + if (!Adventure.isFallback() && Adventure.audiences() != null) { + try { + Adventure.audiences().player(player).sendActionBar(component); + return; + } catch (Throwable ignored) { + } + } + // Fallback: send as chat + player.sendMessage(legacy(component)); + } + + public static void title(Player player, Component title, Component subtitle, Title.Times times) { + if (player == null) { + return; + } + Component t = title != null ? title : Component.empty(); + Component s = subtitle != null ? subtitle : Component.empty(); + Title.Times tm = times != null ? times : Title.Times.times( + Duration.ofMillis(500), Duration.ofMillis(3000), Duration.ofMillis(500)); + if (!Adventure.isFallback() && Adventure.audiences() != null) { + try { + Adventure.audiences().player(player).showTitle(Title.title(t, s, tm)); + return; + } catch (Throwable ignored) { + } + } + // Fallback via Bukkit API (legacy strings, 1.20.1 supports titles) + player.sendTitle(legacy(t), legacy(s), + (int) (tm.fadeIn().toMillis() / 50), + (int) (tm.stay().toMillis() / 50), + (int) (tm.fadeOut().toMillis() / 50)); + } + + public static String legacy(Component component) { + if (component == null) { + return ""; + } + return LEGACY.serialize(component); + } + + public static String plain(Component component) { + if (component == null) { + return ""; + } + return PLAIN.serialize(component); + } + + public static void setDisplayName(Player player, Component component) { + if (player == null) { + return; + } + player.setDisplayName(component == null ? "" : legacy(component)); + } + + public static void setPlayerListName(Player player, Component component) { + if (player == null) { + return; + } + player.setPlayerListName(component == null ? "" : legacy(component)); + } + + public static void kick(Player player, Component reason) { + if (player == null) { + return; + } + String msg = reason == null ? "" : legacy(reason); + // Must be on main thread; caller should ensure via scheduler if needed + if (Bukkit.isPrimaryThread()) { + player.kickPlayer(msg); + } else { + Bukkit.getScheduler().runTask( + Bukkit.getPluginManager().getPlugin("LoChat"), () -> player.kickPlayer(msg)); + } + } + + public static void itemName(ItemMeta meta, Component name) { + if (meta == null) { + return; + } + meta.setDisplayName(name == null ? null : legacy(name)); + } + + public static void itemLore(ItemMeta meta, List lore) { + if (meta == null) { + return; + } + if (lore == null) { + meta.setLore(null); + return; + } + List lines = lore.stream().map(Msg::legacy).toList(); + meta.setLore(lines); + } + + public static String inventoryTitle(Component title) { + return legacy(title); + } +} diff --git a/src/main/java/com/loki/lochat/utils/platform/AdventureManager.java b/src/main/java/com/loki/lochat/utils/platform/AdventureManager.java deleted file mode 100644 index c860ebe..0000000 --- a/src/main/java/com/loki/lochat/utils/platform/AdventureManager.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.loki.lochat.utils.platform; - -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - -/** - * Менеджер для работы с Adventure API на Arclight/Forge серверах - */ -public class AdventureManager { - - private AdventureManager() { - } - - private static boolean adventureAvailable = false; - private static Object audiences; - - public static void init(Plugin plugin) { - try { - // Проверяем наличие Adventure API - Class.forName("net.kyori.adventure.text.Component"); - Class audiencesClass = Class.forName("net.kyori.adventure.platform.bukkit.BukkitAudiences"); - - // Создаем BukkitAudiences через рефлексию - Object audiencesInstance = audiencesClass.getMethod("create", Plugin.class).invoke(null, plugin); - audiences = audiencesInstance; - adventureAvailable = true; - - plugin.getLogger().info("Adventure API обнаружен - используем нативную поддержку"); - } catch (Exception e) { - adventureAvailable = false; - plugin.getLogger().info("Adventure API не найден - используем legacy режим"); - } - } - - public static void shutdown() { - if (audiences != null && adventureAvailable) { - try { - audiences.getClass().getMethod("close").invoke(audiences); - } catch (Exception e) { - // Игнорируем ошибки при закрытии - } - audiences = null; - } - adventureAvailable = false; - } - - public static void sendMessage(CommandSender sender, Object message) { - if (adventureAvailable && audiences != null) { - try { - // Используем Adventure API - Object senderAudience = audiences.getClass().getMethod("sender", CommandSender.class).invoke(audiences, sender); - senderAudience.getClass() - .getMethod("sendMessage", Class.forName("net.kyori.adventure.text.Component")) - .invoke(senderAudience, message); - return; - } catch (Exception e) { - // Fallback на legacy - } - } - - // Legacy fallback - if (message instanceof String) { - sender.sendMessage((String) message); - } else { - sender.sendMessage(message.toString()); - } - } - - public static void sendMessage(Player player, Object message) { - if (adventureAvailable && audiences != null) { - try { - // Используем Adventure API - Object playerAudience = audiences.getClass().getMethod("player", Player.class).invoke(audiences, player); - playerAudience.getClass() - .getMethod("sendMessage", Class.forName("net.kyori.adventure.text.Component")) - .invoke(playerAudience, message); - return; - } catch (Exception e) { - // Fallback на legacy - } - } - - // Legacy fallback - if (message instanceof String) { - player.sendMessage((String) message); - } else { - player.sendMessage(message.toString()); - } - } - - public static boolean isAdventureAvailable() { - return adventureAvailable; - } -}