From 1db87e81f07cfff31c1b2e948f5e1d3f9b0be14d Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sat, 24 Jan 2026 17:03:48 +0100 Subject: [PATCH] Update from Git Manager GUI --- src/main/java/de/nexuslobby/NexusLobby.java | 178 +++++++++----- .../commands/LobbyTabCompleter.java | 100 +++++--- .../commands/NexusLobbyCommand.java | 150 ++++++------ .../modules/armorstandtools/ASTListener.java | 61 +++-- .../ArmorStandCmdExecutor.java | 220 ++++++++++++++---- .../armorstandtools/ArmorStandCommand.java | 90 ++++--- .../ArmorStandLookAtModule.java | 113 +++++++++ .../ArmorStandStatusModule.java | 80 +++++++ .../armorstandtools/ArmorStandTool.java | 34 ++- .../armorstandtools/ConversationManager.java | 166 +++++++++++++ .../modules/servers/ServerChecker.java | 119 ++++++++++ src/main/resources/config.yml | 12 +- src/main/resources/plugin.yml | 19 +- 13 files changed, 1077 insertions(+), 265 deletions(-) create mode 100644 src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandLookAtModule.java create mode 100644 src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandStatusModule.java create mode 100644 src/main/java/de/nexuslobby/modules/armorstandtools/ConversationManager.java create mode 100644 src/main/java/de/nexuslobby/modules/servers/ServerChecker.java diff --git a/src/main/java/de/nexuslobby/NexusLobby.java b/src/main/java/de/nexuslobby/NexusLobby.java index 4777b90..0843782 100644 --- a/src/main/java/de/nexuslobby/NexusLobby.java +++ b/src/main/java/de/nexuslobby/NexusLobby.java @@ -13,6 +13,7 @@ import de.nexuslobby.modules.settings.LobbySettingsModule; import de.nexuslobby.modules.portal.PortalManager; import de.nexuslobby.modules.portal.PortalCommand; import de.nexuslobby.modules.servers.ServerSwitcherListener; +import de.nexuslobby.modules.servers.ServerChecker; // Hinzugefügt import de.nexuslobby.modules.armorstandtools.*; import de.nexuslobby.modules.gadgets.GadgetModule; import de.nexuslobby.modules.hologram.HologramModule; @@ -29,18 +30,23 @@ import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.command.PluginCommand; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; import java.io.File; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; public class NexusLobby extends JavaPlugin implements Listener { @@ -56,17 +62,29 @@ public class NexusLobby extends JavaPlugin implements Listener { private MapArtModule mapArtModule; private IntroModule introModule; private BorderModule borderModule; + + private ConversationManager conversationManager; private File visualsFile; private FileConfiguration visualsConfig; private boolean updateAvailable = false; private String latestVersion = ""; + + private final Set silentPlayers = new HashSet<>(); public static NexusLobby getInstance() { return instance; } + public Set getSilentPlayers() { + return silentPlayers; + } + + public ConversationManager getConversationManager() { + return conversationManager; + } + @Override public void onEnable() { instance = this; @@ -76,12 +94,21 @@ public class NexusLobby extends JavaPlugin implements Listener { getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); moduleManager = new ModuleManager(this); + this.conversationManager = new ConversationManager(this); + ArmorStandGUI.init(); registerModules(); moduleManager.enableAll(); registerListeners(); + // --- STATUS CHECKER START --- + ServerChecker.startGlobalChecker(); + + new ArmorStandLookAtModule(); + + startAutoConversationTimer(); + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { new NexusLobbyExpansion().register(); getLogger().info("NexusLobby PAPI Expansion registriert."); @@ -93,24 +120,51 @@ public class NexusLobby extends JavaPlugin implements Listener { getLogger().info("NexusLobby v" + getDescription().getVersion() + " wurde erfolgreich gestartet."); } - private void checkUpdates() { - new UpdateChecker(this).getVersion(version -> { - if (!this.getDescription().getVersion().equalsIgnoreCase(version)) { - this.updateAvailable = true; - this.latestVersion = version; - getLogger().warning("===================================================="); - getLogger().warning("Update gefunden! v" + getDescription().getVersion() + " -> v" + version); - getLogger().warning("Autor: M_Viper"); - getLogger().warning("===================================================="); - } else { - getLogger().info("NexusLobby ist aktuell (v" + version + ")."); + private void startAutoConversationTimer() { + new BukkitRunnable() { + @Override + public void run() { + if (conversationManager == null) return; + + for (World world : Bukkit.getWorlds()) { + for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) { + if (as.getScoreboardTags().stream().anyMatch(tag -> tag.startsWith("conv_id:"))) { + boolean playerNearby = false; + for (Entity nearby : as.getNearbyEntities(30, 30, 30)) { + if (nearby instanceof Player) { + playerNearby = true; + break; + } + } + + if (playerNearby) { + String dialogId = null; + String partnerUUID = null; + + for (String tag : as.getScoreboardTags()) { + if (tag.startsWith("conv_id:")) dialogId = tag.split(":")[1]; + if (tag.startsWith("conv_partner:")) partnerUUID = tag.split(":")[1]; + } + + if (dialogId != null && partnerUUID != null) { + try { + UUID partnerId = UUID.fromString(partnerUUID); + conversationManager.playConversation(as.getUniqueId(), partnerId, dialogId); + } catch (Exception ignored) {} + } + } + } + } + } } - }); + }.runTaskTimer(this, 20L * 30, 20L * 90); } public void reloadPlugin() { getLogger().info("Plugin Reload wird gestartet..."); + Bukkit.getScheduler().cancelTasks(this); + if (moduleManager != null) { moduleManager.disableAll(); } @@ -119,8 +173,11 @@ public class NexusLobby extends JavaPlugin implements Listener { visualsConfig = null; reloadVisualsConfig(); Config.load(); + + if (conversationManager != null) { + conversationManager.setupFile(); + } - // Border-Settings nach Config-Reload synchronisieren if (borderModule != null) { borderModule.reloadConfig(); } @@ -128,15 +185,34 @@ public class NexusLobby extends JavaPlugin implements Listener { if (portalManager != null) { portalManager.loadPortals(); } + ArmorStandGUI.init(); if (moduleManager != null) { moduleManager.enableAll(); } + ServerChecker.startGlobalChecker(); + new ArmorStandLookAtModule(); + startAutoConversationTimer(); + getLogger().info("Plugin Reload abgeschlossen. Änderungen wurden übernommen."); } + private void checkUpdates() { + new UpdateChecker(this).getVersion(version -> { + if (!this.getDescription().getVersion().equalsIgnoreCase(version)) { + this.updateAvailable = true; + this.latestVersion = version; + getLogger().warning("===================================================="); + getLogger().warning("Update gefunden! v" + getDescription().getVersion() + " -> v" + version); + getLogger().warning("===================================================="); + } else { + getLogger().info("NexusLobby ist aktuell (v" + version + ")."); + } + }); + } + private void registerModules() { moduleManager.registerModule(new ProtectionModule()); moduleManager.registerModule(new ScoreboardModule()); @@ -153,6 +229,8 @@ public class NexusLobby extends JavaPlugin implements Listener { this.dynamicArmorStandModule = new DynamicArmorStandModule(); moduleManager.registerModule(this.dynamicArmorStandModule); + moduleManager.registerModule(new ArmorStandStatusModule()); + this.mapArtModule = new MapArtModule(); moduleManager.registerModule(this.mapArtModule); @@ -191,7 +269,6 @@ public class NexusLobby extends JavaPlugin implements Listener { Player player = event.getPlayer(); event.setJoinMessage(null); - // Teleport zum Lobby-Spawn beim Beitreten teleportToSpawn(player); player.getInventory().clear(); @@ -214,7 +291,7 @@ public class NexusLobby extends JavaPlugin implements Listener { TextComponent link = new TextComponent("§8» §6Klicke §e§l[HIER] §6zum Herunterladen."); link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://git.viper.ipv64.net/M_Viper/NexusLobby/releases")); link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, - new ComponentBuilder("§7Öffnet die Gitea Release-Seite").create())); + new ComponentBuilder("§7Öffnet die Release-Seite").create())); player.spigot().sendMessage(link); player.sendMessage(" "); @@ -241,37 +318,26 @@ public class NexusLobby extends JavaPlugin implements Listener { } private void initCustomConfigs() { - if (!getDataFolder().exists()) { - getDataFolder().mkdirs(); - } + if (!getDataFolder().exists()) getDataFolder().mkdirs(); File configFile = new File(getDataFolder(), "config.yml"); - if (!configFile.exists()) { - saveResource("config.yml", false); - } + if (!configFile.exists()) saveResource("config.yml", false); reloadConfig(); File settingsFile = new File(getDataFolder(), "settings.yml"); - if (!settingsFile.exists()) { - saveResource("settings.yml", false); - } + if (!settingsFile.exists()) saveResource("settings.yml", false); visualsFile = new File(getDataFolder(), "visuals.yml"); - if (!visualsFile.exists()) { - saveResource("visuals.yml", false); - } + if (!visualsFile.exists()) saveResource("visuals.yml", false); reloadVisualsConfig(); Config.load(); } public void reloadVisualsConfig() { - if (visualsFile == null) { - visualsFile = new File(getDataFolder(), "visuals.yml"); - } + if (visualsFile == null) visualsFile = new File(getDataFolder(), "visuals.yml"); visualsConfig = YamlConfiguration.loadConfiguration(visualsFile); - getLogger().info("visuals.yml erfolgreich geladen."); } public FileConfiguration getVisualsConfig() { @@ -290,22 +356,19 @@ public class NexusLobby extends JavaPlugin implements Listener { LobbyTabCompleter tabCompleter = new LobbyTabCompleter(portalManager, hologramModule); NexusLobbyCommand nexusCommand = new NexusLobbyCommand(); - PluginCommand portalCmd = this.getCommand("portal"); - if (portalCmd != null) { - portalCmd.setExecutor(new PortalCommand(portalManager)); - portalCmd.setTabCompleter(tabCompleter); + if (getCommand("portal") != null) { + getCommand("portal").setExecutor(new PortalCommand(portalManager)); + getCommand("portal").setTabCompleter(tabCompleter); } - PluginCommand holoCmd = this.getCommand("holo"); - if (holoCmd != null) { - holoCmd.setExecutor(new HoloCommand(hologramModule)); - holoCmd.setTabCompleter(tabCompleter); + if (getCommand("holo") != null) { + getCommand("holo").setExecutor(new HoloCommand(hologramModule)); + getCommand("holo").setTabCompleter(tabCompleter); } - PluginCommand maintenanceCmd = this.getCommand("maintenance"); - if (maintenanceCmd != null) { - maintenanceCmd.setExecutor(new MaintenanceCommand()); - maintenanceCmd.setTabCompleter(tabCompleter); + if (getCommand("maintenance") != null) { + getCommand("maintenance").setExecutor(new MaintenanceCommand()); + getCommand("maintenance").setTabCompleter(tabCompleter); } if (getCommand("giveportalwand") != null) getCommand("giveportalwand").setExecutor(new GivePortalToolCommand(this)); @@ -322,32 +385,28 @@ public class NexusLobby extends JavaPlugin implements Listener { getCommand("nexuscmd").setTabCompleter(tabCompleter); } - // Haupt- und Spawn-Befehl Registrierung (NexusLobbyCommand verarbeitet beides) - PluginCommand nexusCmd = this.getCommand("nexuslobby"); - if (nexusCmd != null) { - nexusCmd.setExecutor(nexusCommand); - nexusCmd.setTabCompleter(tabCompleter); + if (getCommand("nexuslobby") != null) { + getCommand("nexuslobby").setExecutor(nexusCommand); + getCommand("nexuslobby").setTabCompleter(tabCompleter); } - PluginCommand spawnCmd = this.getCommand("spawn"); - if (spawnCmd != null) { - spawnCmd.setExecutor(nexusCommand); - spawnCmd.setTabCompleter(tabCompleter); + if (getCommand("spawn") != null) { + getCommand("spawn").setExecutor(nexusCommand); + getCommand("spawn").setTabCompleter(tabCompleter); } if (getCommand("mapart") != null) getCommand("mapart").setTabCompleter(tabCompleter); if (getCommand("intro") != null) getCommand("intro").setTabCompleter(tabCompleter); - PluginCommand borderCmd = this.getCommand("border"); - if (borderCmd != null) { - borderCmd.setExecutor(new BorderCommand()); - borderCmd.setTabCompleter(tabCompleter); + if (getCommand("border") != null) { + getCommand("border").setExecutor(new BorderCommand()); + getCommand("border").setTabCompleter(tabCompleter); } } public class NexusLobbyExpansion extends PlaceholderExpansion { @Override public @NotNull String getIdentifier() { return "nexuslobby"; } - @Override public @NotNull String getAuthor() { return String.join(", ", NexusLobby.this.getDescription().getAuthors()); } + @Override public @NotNull String getAuthor() { return "M_Viper"; } @Override public @NotNull String getVersion() { return NexusLobby.this.getDescription().getVersion(); } @Override public boolean persist() { return true; } @@ -357,15 +416,18 @@ public class NexusLobby extends JavaPlugin implements Listener { if (params.equalsIgnoreCase("maintenance_status")) return MaintenanceListener.isMaintenance() ? "§cAktiv" : "§aDeaktiviert"; if (params.equalsIgnoreCase("version")) return NexusLobby.this.getDescription().getVersion(); if (params.equalsIgnoreCase("build_mode")) return BuildCommand.isInBuildMode(player) ? "§aAktiv" : "§cInaktiv"; + if (params.equalsIgnoreCase("silent_join")) return silentPlayers.contains(player.getUniqueId()) ? "§aEin" : "§cAus"; return null; } } public ModuleManager getModuleManager() { return moduleManager; } + public PortalManager getPortalManager() { return portalManager; } // Hinzugefügt/Wiederhergestellt public TablistModule getTablistModule() { return tablistModule; } public LobbySettingsModule getLobbySettingsModule() { return lobbySettingsModule; } public ItemsModule getItemsModule() { return itemsModule; } public GadgetModule getGadgetModule() { return gadgetModule; } public HologramModule getHologramModule() { return hologramModule; } public DynamicArmorStandModule getDynamicArmorStandModule() { return dynamicArmorStandModule; } + public MapArtModule getMapArtModule() { return mapArtModule; } // Wiederhergestellt } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java b/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java index 86167a5..e0376c9 100644 --- a/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java +++ b/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java @@ -1,5 +1,6 @@ package de.nexuslobby.commands; +import de.nexuslobby.NexusLobby; import de.nexuslobby.modules.portal.PortalManager; import de.nexuslobby.modules.hologram.HologramModule; import org.bukkit.command.Command; @@ -25,31 +26,31 @@ public class LobbyTabCompleter implements TabCompleter { @Override public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { List suggestions = new ArrayList<>(); + String cmdName = command.getName().toLowerCase(); // --- NexusLobby Hauptbefehl (/nexus) --- - if (command.getName().equalsIgnoreCase("nexuslobby") || command.getName().equalsIgnoreCase("nexus")) { + if (cmdName.equals("nexuslobby") || cmdName.equals("nexus")) { if (args.length == 1) { - // Hier fügen wir 'setspawn' hinzu if (sender.hasPermission("nexuslobby.admin")) { - suggestions.add("reload"); - suggestions.add("setspawn"); + suggestions.addAll(Arrays.asList("reload", "setspawn", "silentjoin")); } suggestions.add("sb"); - } else if (args.length == 2 && args[0].equalsIgnoreCase("sb")) { - suggestions.add("on"); - suggestions.add("off"); - if (sender.hasPermission("nexuslobby.scoreboard.admin")) { - suggestions.add("admin"); - suggestions.add("spieler"); + } else if (args.length == 2) { + if (args[0].equalsIgnoreCase("sb")) { + suggestions.addAll(Arrays.asList("on", "off")); + if (sender.hasPermission("nexuslobby.scoreboard.admin")) { + suggestions.addAll(Arrays.asList("admin", "spieler")); + } + } else if (args[0].equalsIgnoreCase("silentjoin")) { + suggestions.addAll(Arrays.asList("on", "off")); } } } // --- Hologram Befehl --- - else if (command.getName().equalsIgnoreCase("holo")) { + else if (cmdName.equals("holo")) { if (args.length == 1) { - suggestions.add("create"); - suggestions.add("delete"); + suggestions.addAll(Arrays.asList("create", "delete")); } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { if (hologramModule != null) { suggestions.addAll(hologramModule.getHologramIds()); @@ -58,19 +59,16 @@ public class LobbyTabCompleter implements TabCompleter { } // --- Wartungsmodus --- - else if (command.getName().equalsIgnoreCase("maintenance")) { + else if (cmdName.equals("maintenance")) { if (args.length == 1) { - suggestions.add("on"); - suggestions.add("off"); + suggestions.addAll(Arrays.asList("on", "off")); } } // --- Portalsystem --- - else if (command.getName().equalsIgnoreCase("portal")) { + else if (cmdName.equals("portal")) { if (args.length == 1) { - suggestions.add("create"); - suggestions.add("delete"); - suggestions.add("list"); + suggestions.addAll(Arrays.asList("create", "delete", "list")); } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { if (portalManager != null) { suggestions.addAll(portalManager.getPortalNames()); @@ -79,7 +77,7 @@ public class LobbyTabCompleter implements TabCompleter { } // --- MapArt --- - else if (command.getName().equalsIgnoreCase("mapart")) { + else if (cmdName.equals("mapart")) { if (args.length == 1) { suggestions.add("https://"); } else if (args.length == 2) { @@ -88,31 +86,73 @@ public class LobbyTabCompleter implements TabCompleter { } // --- Intro System --- - else if (command.getName().equalsIgnoreCase("intro")) { + else if (cmdName.equals("intro")) { if (args.length == 1) { suggestions.addAll(Arrays.asList("add", "clear", "start")); } } // --- WorldBorder --- - else if (command.getName().equalsIgnoreCase("border")) { + else if (cmdName.equals("border")) { if (args.length == 1) { - suggestions.add("circle"); - suggestions.add("square"); - suggestions.add("disable"); + suggestions.addAll(Arrays.asList("circle", "square", "disable")); } else if (args.length == 2 && args[0].equalsIgnoreCase("circle")) { suggestions.addAll(Arrays.asList("10", "25", "50", "100", "250")); } } - // --- NexusCmd & Tools (Verkürzte Logik) --- - else if (command.getName().equalsIgnoreCase("nexuscmd") || command.getName().equalsIgnoreCase("ncmd")) { + // --- NexusCmd (ArmorStand Commands & Dialoge) --- + else if (cmdName.equals("nexuscmd") || cmdName.equals("ncmd") || cmdName.equals("ascmd")) { if (args.length == 1) { - suggestions.addAll(Arrays.asList("name", "list", "add", "remove")); + suggestions.addAll(Arrays.asList("add", "remove", "list", "name", "lookat", "conv")); + } + else if (args.length == 2) { + if (args[0].equalsIgnoreCase("add")) { + suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")); + } else if (args[0].equalsIgnoreCase("name")) { + suggestions.addAll(Arrays.asList("", "none")); + } else if (args[0].equalsIgnoreCase("conv")) { + // NEU: unlink hinzugefügt + suggestions.addAll(Arrays.asList("select1", "select2", "link", "unlink", "start")); + } else if (args[0].equalsIgnoreCase("remove")) { + suggestions.add("all"); + } + } + else if (args.length == 3) { + if (args[0].equalsIgnoreCase("add")) { + suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")); + } else if (args[0].equalsIgnoreCase("conv")) { + if (args[1].equalsIgnoreCase("start") || args[1].equalsIgnoreCase("link")) { + if (NexusLobby.getInstance().getConversationManager() != null) { + suggestions.addAll(NexusLobby.getInstance().getConversationManager().getConversationIds()); + } + } + } + } + else if (args.length == 4 && args[0].equalsIgnoreCase("add")) { + suggestions.addAll(Arrays.asList("bungee", "console", "player")); + } + else if (args.length == 5 && args[0].equalsIgnoreCase("add") && args[3].equalsIgnoreCase("bungee")) { + if (NexusLobby.getInstance().getConfig().getConfigurationSection("servers") != null) { + suggestions.addAll(NexusLobby.getInstance().getConfig().getConfigurationSection("servers").getKeys(false)); + } } } - // Filtert die Vorschläge basierend auf dem, was der Spieler bereits getippt hat + // --- ArmorStandTools (/astools) --- + else if (cmdName.equals("astools") || cmdName.equals("nt") || cmdName.equals("ntools")) { + if (args.length == 1) { + suggestions.addAll(Arrays.asList("dynamic", "lookat", "addplayer", "addconsole", "remove", "reload")); + } + else if (args.length == 2 && (args[0].equalsIgnoreCase("addplayer") || args[0].equalsIgnoreCase("addconsole"))) { + suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")); + } + else if (args.length == 3 && (args[0].equalsIgnoreCase("addplayer") || args[0].equalsIgnoreCase("addconsole"))) { + suggestions.addAll(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")); + } + } + + // Filtert die Liste basierend auf der bisherigen Eingabe (für Case-Insensitivity und Teilübereinstimmung) return suggestions.stream() .filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase())) .collect(Collectors.toList()); diff --git a/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java b/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java index 7a70ce2..5208fba 100644 --- a/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java +++ b/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java @@ -18,15 +18,12 @@ public class NexusLobbyCommand implements CommandExecutor { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (!(sender instanceof Player)) { + if (!(sender instanceof Player player)) { sender.sendMessage("§cDieser Befehl ist nur für Spieler!"); return true; } - Player player = (Player) sender; - // ========================================== - // LOGIK FÜR /spawn - // ========================================== + // --- SPAWN BEFEHL --- if (command.getName().equalsIgnoreCase("spawn")) { FileConfiguration config = NexusLobby.getInstance().getConfig(); if (config.contains("spawn.world")) { @@ -44,75 +41,94 @@ public class NexusLobbyCommand implements CommandExecutor { return true; } - // ========================================== - // LOGIK FÜR /nexus (Hauptbefehl) - // ========================================== - - // Sub-Befehl: /nexus setspawn - if (args.length == 1 && args[0].equalsIgnoreCase("setspawn")) { - if (!player.hasPermission("nexuslobby.admin")) { - player.sendMessage("§cDu hast keine Berechtigung, den Spawn zu setzen."); - return true; - } - - Location loc = player.getLocation(); - FileConfiguration config = NexusLobby.getInstance().getConfig(); - - config.set("spawn.world", loc.getWorld().getName()); - config.set("spawn.x", loc.getX()); - config.set("spawn.y", loc.getY()); - config.set("spawn.z", loc.getZ()); - config.set("spawn.yaw", (double) loc.getYaw()); - config.set("spawn.pitch", (double) loc.getPitch()); - - NexusLobby.getInstance().saveConfig(); - - player.sendMessage("§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!"); - player.sendMessage("§7X: §f" + String.format("%.2f", loc.getX()) + " §7Y: §f" + String.format("%.2f", loc.getY()) + " §7Z: §f" + String.format("%.2f", loc.getZ())); + // --- HAUPTBEFEHL ARGUMENTE --- + if (args.length == 0) { + sendInfo(player); return true; } - // Sub-Befehl: /nexus sb - if (args.length >= 2 && args[0].equalsIgnoreCase("sb")) { - ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class); - if (sbModule == null) { - player.sendMessage("§cScoreboard-Modul ist deaktiviert."); - return true; - } + switch (args[0].toLowerCase()) { + case "reload": + if (!player.hasPermission("nexuslobby.admin")) { + player.sendMessage("§cKeine Berechtigung."); + return true; + } + + // Aufruf der Reload-Methode in der Hauptklasse + NexusLobby.getInstance().reloadPlugin(); + + player.sendMessage("§8[§6Nexus§8] §aPlugin erfolgreich neu geladen!"); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1.5f); + break; - String sub = args[1].toLowerCase(); - switch (sub) { - case "on": sbModule.setVisibility(player, true); break; - case "off": sbModule.setVisibility(player, false); break; - case "admin": - if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true); - else player.sendMessage("§cKeine Rechte."); - break; - case "spieler": - if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false); - else player.sendMessage("§cKeine Rechte."); - break; - default: player.sendMessage("§cBenutzung: /nexus sb "); break; - } - return true; + case "setspawn": + if (!player.hasPermission("nexuslobby.admin")) { + player.sendMessage("§cKeine Berechtigung."); + return true; + } + Location loc = player.getLocation(); + FileConfiguration config = NexusLobby.getInstance().getConfig(); + config.set("spawn.world", loc.getWorld().getName()); + config.set("spawn.x", loc.getX()); + config.set("spawn.y", loc.getY()); + config.set("spawn.z", loc.getZ()); + config.set("spawn.yaw", (double) loc.getYaw()); + config.set("spawn.pitch", (double) loc.getPitch()); + NexusLobby.getInstance().saveConfig(); + player.sendMessage("§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!"); + break; + + case "silentjoin": + if (!player.hasPermission("nexuslobby.silentjoin")) { + player.sendMessage("§cKeine Berechtigung."); + return true; + } + if (NexusLobby.getInstance().getSilentPlayers().contains(player.getUniqueId())) { + NexusLobby.getInstance().getSilentPlayers().remove(player.getUniqueId()); + player.sendMessage("§8[§6Nexus§8] §7Silent Join: §cDeaktiviert"); + } else { + NexusLobby.getInstance().getSilentPlayers().add(player.getUniqueId()); + player.sendMessage("§8[§6Nexus§8] §7Silent Join: §aAktiviert"); + } + break; + + case "sb": + handleScoreboard(player, args); + break; + + default: + sendInfo(player); + break; } - // Sub-Befehl: /nexus reload - if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { - if (!player.hasPermission("nexuslobby.admin")) { - player.sendMessage("§cKeine Rechte."); - return true; - } - NexusLobby.getInstance().reloadPlugin(); - player.sendMessage("§7[§6NexusLobby§7] §aPlugin erfolgreich neu geladen!"); - return true; - } - - // Info-Screen - sendInfo(player); return true; } + private void handleScoreboard(Player player, String[] args) { + if (args.length < 2) { + player.sendMessage("§cBenutzung: /nexus sb "); + return; + } + ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class); + if (sbModule == null) { + player.sendMessage("§cScoreboard-Modul ist deaktiviert."); + return; + } + String sub = args[1].toLowerCase(); + switch (sub) { + case "on": sbModule.setVisibility(player, true); break; + case "off": sbModule.setVisibility(player, false); break; + case "admin": + if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true); + else player.sendMessage("§cKeine Rechte."); + break; + case "spieler": + if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false); + else player.sendMessage("§cKeine Rechte."); + break; + } + } + private Location getSpawnFromConfig(FileConfiguration config) { World world = Bukkit.getWorld(config.getString("spawn.world", "world")); if (world == null) return null; @@ -122,14 +138,14 @@ public class NexusLobbyCommand implements CommandExecutor { } private void sendInfo(Player player) { - String version = NexusLobby.getInstance().getDescription().getVersion(); player.sendMessage("§8§m--------------------------------------"); player.sendMessage("§6§lNexusLobby §7- Informationen"); player.sendMessage(""); player.sendMessage("§f/spawn §7- Zum Spawn teleportieren"); player.sendMessage("§f/nexus setspawn §7- Spawn setzen"); - player.sendMessage("§f/nexus reload §7- Konfiguration laden"); + player.sendMessage("§f/nexus silentjoin §7- Join-Nachricht umschalten"); player.sendMessage("§f/nexus sb §7- Scoreboard"); + player.sendMessage("§f/nexus reload §7- Konfiguration laden"); player.sendMessage("§8§m--------------------------------------"); } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ASTListener.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ASTListener.java index e41f3de..b70cdcf 100644 --- a/src/main/java/de/nexuslobby/modules/armorstandtools/ASTListener.java +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ASTListener.java @@ -1,5 +1,9 @@ package de.nexuslobby.modules.armorstandtools; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import de.nexuslobby.NexusLobby; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Player; @@ -14,40 +18,65 @@ import org.bukkit.util.EulerAngle; public class ASTListener implements Listener { - /** - * Erkennt den Rechtsklick auf den ArmorStand. - * Öffnet bei Sneak + Rechtsklick die Haupt-GUI. - */ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onInteract(PlayerInteractAtEntityEvent event) { - // Sicherstellen, dass nur die Haupthand zählt (verhindert Doppel-Trigger) if (event.getHand() != EquipmentSlot.HAND) return; if (!(event.getRightClicked() instanceof ArmorStand as)) return; Player p = event.getPlayer(); - // Prüfen ob Spieler schleicht (Shift) + // --- 1. FALL: SNEAKING -> Editor öffnen --- if (p.isSneaking()) { if (p.hasPermission("nexuslobby.armorstand.use")) { event.setCancelled(true); - - // ArmorStand für diesen Spieler zwischenspeichern AST.selectedArmorStand.put(p.getUniqueId(), as); - - // Haupt-GUI öffnen new ArmorStandGUI(as, p); } + return; + } + + // --- 2. FALL: NORMALER KLICK -> Befehle ausführen (Bungee, etc.) --- + for (String tag : as.getScoreboardTags()) { + if (tag.startsWith("ascmd:")) { + String[] parts = tag.split(":"); + if (parts.length < 5) continue; + + String type = parts[3].toLowerCase(); + String command = parts[4]; + + event.setCancelled(true); // Verhindert z.B. dass man Items klaut + + switch (type) { + case "bungee": + connectToServer(p, command); + break; + case "player": + p.performCommand(command); + break; + case "console": + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.replace("%player%", p.getName())); + break; + } + break; // Nur den ersten gefundenen Befehl ausführen + } } } /** - * Verwaltet alle Klicks innerhalb der verschiedenen GUIs. + * Sendet den Spieler per Plugin-Message an BungeeCord */ + private void connectToServer(Player player, String server) { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Connect"); + out.writeUTF(server); + player.sendPluginMessage(NexusLobby.getInstance(), "BungeeCord", out.toByteArray()); + player.sendMessage("§8[§6Nexus§8] §7Du wirst mit §e" + server + " §7verbunden..."); + } + @EventHandler(priority = EventPriority.LOW) public void onInventoryClick(InventoryClickEvent event) { String title = event.getView().getTitle(); - // Prüfen, ob es eine unserer GUIs ist if (!title.equals("Nexus ArmorStand Editor") && !title.equals("Pose: Körperteil wählen") && !title.startsWith("Achsen:")) return; @@ -64,19 +93,15 @@ public class ASTListener implements Listener { ItemStack item = event.getCurrentItem(); if (item == null || item.getType() == Material.AIR) return; - // --- 1. LOGIK: HAUPTMENÜ --- if (title.equals("Nexus ArmorStand Editor")) { ArmorStandTool tool = ArmorStandTool.get(item); if (tool != null) { tool.execute(as, p); - // GUI erneuern (außer sie wurde durch execute geschlossen) if (p.getOpenInventory().getTitle().equals(title)) { new ArmorStandGUI(as, p); } } } - - // --- 2. LOGIK: KÖRPERTEIL-AUSWAHL --- else if (title.equals("Pose: Körperteil wählen")) { if (item.getType() == Material.BARRIER) { new ArmorStandGUI(as, p); @@ -95,8 +120,6 @@ public class ASTListener implements Listener { ArmorStandPoseGUI.openAxisDetailMenu(p, as, targetPart); } } - - // --- 3. LOGIK: ACHSEN-FEINJUSTIERUNG --- else if (title.startsWith("Achsen:")) { if (item.getType() == Material.ARROW) { ArmorStandPoseGUI.openPartSelectionMenu(p, as); @@ -106,7 +129,6 @@ public class ASTListener implements Listener { String partName = title.split(": ")[1]; ArmorStandTool tool = ArmorStandTool.valueOf(partName); - // Berechnung des Winkels (Links/Rechts & Shift) double change = event.isShiftClick() ? Math.toRadians(1) : Math.toRadians(15); if (event.isRightClick()) change *= -1; @@ -122,7 +144,6 @@ public class ASTListener implements Listener { } ArmorStandPoseGUI.setAngleForPart(as, tool, newPose); - // GUI aktualisieren um Werte in der Lore anzuzeigen ArmorStandPoseGUI.openAxisDetailMenu(p, as, partName); } } diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java index 64ffb0a..b2ae0e6 100644 --- a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java @@ -1,17 +1,26 @@ package de.nexuslobby.modules.armorstandtools; +import de.nexuslobby.NexusLobby; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.Particle; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.util.EulerAngle; +import org.bukkit.util.RayTraceResult; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Set; +import java.util.UUID; +/** + * ArmorStandCmdExecutor - Erweiterte Steuerung für ArmorStand-Interaktionen. + * Nutzt Raytracing für präzise Auswahl und permanentes Dialog-Linking sowie Status-Backup. + */ public class ArmorStandCmdExecutor implements CommandExecutor { private final String prefix = "§8[§6Nexus§8] "; @@ -25,74 +34,175 @@ public class ArmorStandCmdExecutor implements CommandExecutor { return true; } - // 1. Priorität: Name setzen (verwendet den in der Map gespeicherten AS aus der GUI) - if (args.length >= 2 && args[0].equalsIgnoreCase("name")) { - ArmorStand target = AST.selectedArmorStand.get(p.getUniqueId()); - if (target == null || !target.isValid()) { - p.sendMessage(prefix + "§cBitte wähle zuerst einen ArmorStand im GUI (Sneak-Rechtsklick) aus!"); - return true; + if (args.length == 0) return sendHelp(p); + + // --- CONVERSATION BEFEHLE --- + if (args[0].equalsIgnoreCase("conv")) { + if (args.length < 2) { + return sendConvHelp(p); } - + + switch (args[1].toLowerCase()) { + case "select1": + case "select2": + ArmorStand target = getTargetArmorStand(p); + if (target == null) { + p.sendMessage(prefix + "§cDu musst einen ArmorStand direkt anschauen (Fadenkreuz)!"); + return true; + } + + boolean isFirst = args[1].equalsIgnoreCase("select1"); + String metaKey = isFirst ? "conv_npc1" : "conv_npc2"; + UUID targetUUID = target.getUniqueId(); + + p.setMetadata(metaKey, new FixedMetadataValue(NexusLobby.getInstance(), targetUUID.toString())); + p.sendMessage(prefix + "§aNPC §e" + (isFirst ? "1" : "2") + " §amarkiert (§7" + targetUUID.toString().substring(0, 8) + "...§a)"); + p.spawnParticle(Particle.WITCH, target.getLocation().add(0, 1.0, 0), 15, 0.2, 0.2, 0.2, 0.05); + break; + + case "link": + if (args.length < 3) { + p.sendMessage(prefix + "§cNutze: /nexuscmd conv link "); + return true; + } + if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) { + p.sendMessage(prefix + "§cBitte markiere erst beide NPCs (select1 & select2)!"); + return true; + } + + UUID id1 = UUID.fromString(p.getMetadata("conv_npc1").get(0).asString()); + UUID id2 = UUID.fromString(p.getMetadata("conv_npc2").get(0).asString()); + String dialogId = args[2]; + + Entity entity1 = Bukkit.getEntity(id1); + if (entity1 instanceof ArmorStand as1) { + as1.getScoreboardTags().removeIf(tag -> tag.startsWith("conv_partner:") || tag.startsWith("conv_id:")); + as1.addScoreboardTag("conv_partner:" + id2.toString()); + as1.addScoreboardTag("conv_id:" + dialogId); + + NexusLobby.getInstance().getConversationManager().saveLink(id1, id2, dialogId); + + p.sendMessage(prefix + "§a§lDauerhafte Verknüpfung erstellt!"); + p.spawnParticle(Particle.HAPPY_VILLAGER, as1.getLocation().add(0, 1.5, 0), 20, 0.4, 0.4, 0.4, 0.1); + } else { + p.sendMessage(prefix + "§cFehler: Sprecher 1 nicht gefunden."); + } + break; + + case "unlink": + ArmorStand targetUnlink = getTargetArmorStand(p); + if (targetUnlink == null) { + p.sendMessage(prefix + "§cSchau den NPC an, dessen Verknüpfung du lösen willst!"); + return true; + } + + // Ingame Tags entfernen + targetUnlink.getScoreboardTags().removeIf(tag -> tag.startsWith("conv_partner:") || tag.startsWith("conv_id:")); + + // Aus Konfiguration löschen + NexusLobby.getInstance().getConversationManager().removeLink(targetUnlink.getUniqueId()); + + p.sendMessage(prefix + "§eNPC-Verknüpfung wurde aufgehoben."); + p.spawnParticle(Particle.SMOKE, targetUnlink.getLocation().add(0, 1.0, 0), 20, 0.2, 0.2, 0.2, 0.02); + break; + + case "start": + if (args.length < 3) { + p.sendMessage(prefix + "§cNutze: /nexuscmd conv start "); + return true; + } + if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) { + p.sendMessage(prefix + "§cBitte markiere erst beide NPCs!"); + return true; + } + UUID s1 = UUID.fromString(p.getMetadata("conv_npc1").get(0).asString()); + UUID s2 = UUID.fromString(p.getMetadata("conv_npc2").get(0).asString()); + NexusLobby.getInstance().getConversationManager().playConversation(s1, s2, args[2]); + break; + + default: + return sendConvHelp(p); + } + return true; + } + + // --- STANDARD TOOLS (LOOKAT / NAME / ADD) --- + ArmorStand target = getTargetArmorStand(p); + + if (args[0].equalsIgnoreCase("lookat")) { + if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } + if (target.getScoreboardTags().contains("as_lookat")) { + target.removeScoreboardTag("as_lookat"); + target.setHeadPose(new EulerAngle(0, 0, 0)); + p.sendMessage(prefix + "§cBlickkontakt aus."); + } else { + target.addScoreboardTag("as_lookat"); + p.sendMessage(prefix + "§aBlickkontakt an."); + } + return true; + } + + if (args[0].equalsIgnoreCase("name") && args.length >= 2) { + if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } String nameInput = buildString(args, 1); + if (nameInput.equalsIgnoreCase("none")) { target.setCustomName(""); target.setCustomNameVisible(false); + target.getScoreboardTags().removeIf(tag -> tag.startsWith("asname:")); p.sendMessage(prefix + "§eName entfernt."); } else { String colored = ChatColor.translateAlternateColorCodes('&', nameInput); target.setCustomName(colored); target.setCustomNameVisible(true); + + target.getScoreboardTags().removeIf(tag -> tag.startsWith("asname:")); + target.addScoreboardTag("asname:" + nameInput); + p.sendMessage(prefix + "§7Name gesetzt: " + colored); + p.sendMessage(prefix + "§8(Status-Backup wurde gespeichert)"); } return true; } - // 2. Priorität: Action-Commands (verwendet Nearby-Suche für /nexuscmd add...) - ArmorStand target = getNearbyArmorStand(p); - if (target == null) { - p.sendMessage(prefix + "§cKein ArmorStand in der Nähe (4 Blöcke) gefunden!"); + if (args[0].equalsIgnoreCase("add") && args.length >= 5) { + if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } + String slot1 = args[1], slot2 = args[2], type = args[3].toLowerCase(); + String cmdStr = buildString(args, 4); + + target.addScoreboardTag("ascmd:" + slot1 + ":" + slot2 + ":" + type + ":" + cmdStr); + p.sendMessage(prefix + "§aBefehl gebunden."); return true; } - if (args.length >= 5 && args[0].equalsIgnoreCase("add")) { - String type = args[3].toLowerCase(); - String cmd = buildString(args, 4); - - if (!type.equals("player") && !type.equals("console") && !type.equals("bungee")) { - p.sendMessage(prefix + "§cTypen: §eplayer, console, bungee"); - return true; - } - - target.addScoreboardTag("ascmd:" + type + ":" + cmd); - p.sendMessage(prefix + "§aBefehl an ArmorStand gebunden!"); + if (args[0].equalsIgnoreCase("list")) { + if (target == null) { p.sendMessage(prefix + "§cKein Ziel!"); return true; } + p.sendMessage("§6§lBefehle & Tags:"); + target.getScoreboardTags().forEach(t -> p.sendMessage(" §8» §e" + t)); return true; } - if (args.length >= 1 && args[0].equalsIgnoreCase("list")) { - p.sendMessage("§6§lBefehle auf diesem ArmorStand:"); - for (String tag : target.getScoreboardTags()) { - if (tag.startsWith("ascmd:")) { - p.sendMessage(" §8» §e" + tag.replace("ascmd:", "")); - } - } - return true; - } - - if (args.length >= 1 && args[0].equalsIgnoreCase("remove")) { - Set tags = target.getScoreboardTags(); - for (String tag : new ArrayList<>(tags)) { - if (tag.startsWith("ascmd:")) target.removeScoreboardTag(tag); - } - p.sendMessage(prefix + "§eAlle Befehle entfernt."); + if (args[0].equalsIgnoreCase("remove")) { + if (target == null) { p.sendMessage(prefix + "§cKein Ziel!"); return true; } + target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:")); + p.sendMessage(prefix + "§eAlle Befehle gelöscht."); return true; } return sendHelp(p); } - private ArmorStand getNearbyArmorStand(Player p) { - for (Entity e : p.getNearbyEntities(4, 4, 4)) { - if (e instanceof ArmorStand as) return as; + private ArmorStand getTargetArmorStand(Player p) { + RayTraceResult result = p.getWorld().rayTraceEntities( + p.getEyeLocation(), + p.getLocation().getDirection(), + 8, + 0.3, + entity -> entity instanceof ArmorStand + ); + + if (result != null && result.getHitEntity() instanceof ArmorStand as) { + return as; } return null; } @@ -105,12 +215,26 @@ public class ArmorStandCmdExecutor implements CommandExecutor { return sb.toString(); } + private boolean sendConvHelp(Player p) { + p.sendMessage(" "); + p.sendMessage("§6§lConversation Setup:"); + p.sendMessage("§e/nexuscmd conv select1 §7- Sprecher 1"); + p.sendMessage("§e/nexuscmd conv select2 §7- Sprecher 2"); + p.sendMessage("§e/nexuscmd conv link §7- Speichern"); + p.sendMessage("§e/nexuscmd conv unlink §7- Verknüpfung lösen"); + p.sendMessage("§e/nexuscmd conv start §7- Testen"); + p.sendMessage(" "); + return true; + } + private boolean sendHelp(Player p) { - p.sendMessage("§6§lNexus Command-Binder Hilfe:"); - p.sendMessage("§e/nexuscmd name §7- Namen setzen (AS vorher anklicken)"); - p.sendMessage("§e/nexuscmd add 0 0 §7- Befehl binden"); - p.sendMessage("§e/nexuscmd list §7- Befehle anzeigen"); - p.sendMessage("§e/nexuscmd remove §7- Befehle löschen"); + p.sendMessage("§6§lNexus Tools Hilfe:"); + p.sendMessage("§e/nexuscmd name §7- Setzt Namen & Status-Backup"); + p.sendMessage("§e/nexuscmd lookat §7- Blickkontakt umschalten"); + p.sendMessage("§e/nexuscmd add bungee §7- Bungee-Bindung"); + p.sendMessage("§e/nexuscmd conv §7- Gesprächs-Menü"); + p.sendMessage("§e/nexuscmd list §7- Zeigt alle Tags"); + p.sendMessage("§e/nexuscmd remove §7- Löscht Befehle"); return true; } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCommand.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCommand.java index a24de19..617a70b 100644 --- a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCommand.java +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCommand.java @@ -9,16 +9,39 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.util.EulerAngle; import org.jetbrains.annotations.NotNull; -import java.util.Set; +import java.util.UUID; /** * ArmorStandCommand - Vollständige Steuerung für ArmorStands. - * Inklusive Dynamic-Modus Erkennung und visueller Rückmeldung. + * Inklusive Dynamic-Modus, Look-At Funktion, Befehls-Slots und Conversation-Sprecher. */ public class ArmorStandCommand implements CommandExecutor { + // Statische Variablen für die aktuell markierten Sprecher + private static UUID speaker1; + private static UUID speaker2; + + // Getter-Methoden für die NexusLobby Hauptklasse + public static UUID getSpeaker1() { + return speaker1; + } + + public static UUID getSpeaker2() { + return speaker2; + } + + // Setter-Methoden (werden vom ASTListener oder der GUI aufgerufen) + public static void setSpeaker1(UUID id) { + speaker1 = id; + } + + public static void setSpeaker2(UUID id) { + speaker2 = id; + } + @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { if (!(sender instanceof Player p)) { @@ -72,36 +95,56 @@ public class ArmorStandCommand implements CommandExecutor { } if (NexusLobby.getInstance().getDynamicArmorStandModule() != null) { - // Toggle Logik if (target.getScoreboardTags().contains("as_dynamic")) { target.removeScoreboardTag("as_dynamic"); p.sendMessage(prefix + "§c§l[-] §7Dynamic-Modus §cdeaktiviert§7."); p.spawnParticle(Particle.SMOKE, target.getLocation().add(0, 1, 0), 15, 0.3, 0.3, 0.3, 0.05); } else { target.addScoreboardTag("as_dynamic"); - p.sendMessage(prefix + "§a§l[+] §7Dynamic-Modus §aaktiviert§7 (Wetter/Zeit)."); - // Visueller Erfolgseffekt (Grüne Sternchen) + p.sendMessage(prefix + "§a§l[+] §7Dynamic-Modus §aaktiviert§7."); p.spawnParticle(Particle.HAPPY_VILLAGER, target.getLocation().add(0, 1, 0), 25, 0.5, 0.5, 0.5, 0.1); } - // Internes Update sofort triggern NexusLobby.getInstance().getDynamicArmorStandModule().toggleDynamicStatus(target); } else { p.sendMessage(prefix + "§cFehler: Dynamic-Modul ist nicht aktiv!"); } break; + case "lookat": + if (!p.hasPermission("nexuslobby.armorstand.lookat")) { + p.sendMessage(prefix + ChatColor.RED + "Keine Rechte für Look-At!"); + return true; + } + + if (target.getScoreboardTags().contains("as_lookat")) { + target.removeScoreboardTag("as_lookat"); + // Kopf-Pose zurücksetzen für sauberen Übergang + target.setHeadPose(new EulerAngle(0, 0, 0)); + p.sendMessage(prefix + "§c§l[-] §7Blickkontakt §cdeaktiviert§7."); + p.spawnParticle(Particle.SMOKE, target.getLocation().add(0, 1, 0), 10, 0.2, 0.2, 0.2, 0.02); + } else { + target.addScoreboardTag("as_lookat"); + p.sendMessage(prefix + "§a§l[+] §7Blickkontakt §aaktiviert§7."); + p.spawnParticle(Particle.HAPPY_VILLAGER, target.getLocation().add(0, 1, 0), 15, 0.4, 0.4, 0.4, 0.05); + } + break; + case "addplayer": - if (args.length < 2) return sendHelp(p, prefix); - String pCmd = buildString(args, 1); - target.addScoreboardTag("ascmd:player:" + pCmd); - p.sendMessage(prefix + "§aBefehl (Player) gespeichert: §e/" + pCmd); + if (args.length < 4) return sendHelp(p, prefix); + String s1P = args[1]; + String s2P = args[2]; + String pCmd = buildString(args, 3); + target.addScoreboardTag("ascmd:" + s1P + ":" + s2P + ":player:" + pCmd); + p.sendMessage(prefix + "§aBefehl (Player) auf Slot " + s1P + "/" + s2P + " gespeichert."); break; case "addconsole": - if (args.length < 2) return sendHelp(p, prefix); - String cCmd = buildString(args, 1); - target.addScoreboardTag("ascmd:console:" + cCmd); - p.sendMessage(prefix + "§aBefehl (Konsole) gespeichert: §e" + cCmd); + if (args.length < 4) return sendHelp(p, prefix); + String s1C = args[1]; + String s2C = args[2]; + String cCmd = buildString(args, 3); + target.addScoreboardTag("ascmd:" + s1C + ":" + s2C + ":console:" + cCmd); + p.sendMessage(prefix + "§aBefehl (Konsole) auf Slot " + s1C + "/" + s2C + " gespeichert."); break; case "remove": @@ -117,18 +160,12 @@ public class ArmorStandCommand implements CommandExecutor { return true; } - /** - * Sucht den am besten passenden ArmorStand. - * Priorität 1: Der ArmorStand, den der Spieler direkt ansieht. - * Priorität 2: Der absolut nächste ArmorStand im Umkreis. - */ private ArmorStand getBestTargetArmorStand(Player p) { ArmorStand best = null; - double bestDot = 0.95; // Schwellenwert für das "Anschauen" + double bestDot = 0.95; for (Entity e : p.getNearbyEntities(8, 8, 8)) { if (e instanceof ArmorStand as) { - // Berechne, ob der Spieler in Richtung des ArmorStands schaut double dot = p.getLocation().getDirection().dot( as.getLocation().toVector().subtract(p.getLocation().toVector()).normalize() ); @@ -140,7 +177,6 @@ public class ArmorStandCommand implements CommandExecutor { } } - // Falls nichts aktiv angeschaut wird, nimm einfach den nächsten im 4-Block-Radius if (best == null) { for (Entity e : p.getNearbyEntities(4, 4, 4)) { if (e instanceof ArmorStand as) { @@ -163,12 +199,12 @@ public class ArmorStandCommand implements CommandExecutor { private boolean sendHelp(Player p, String prefix) { p.sendMessage(" "); p.sendMessage("§8§m-----------§r " + prefix + "§8§m-----------"); - p.sendMessage("§e/astools §7- Editor öffnen"); - p.sendMessage("§b/astools dynamic §7- Wetter/Zeit Logik umschalten"); - p.sendMessage("§e/astools addplayer §7- Befehl als Spieler"); - p.sendMessage("§e/astools addconsole §7- Befehl via Konsole"); + p.sendMessage("§e/astools §7- Editor GUI öffnen"); + p.sendMessage("§b/astools dynamic §7- Näherung/Zeit Logik"); + p.sendMessage("§b/astools lookat §7- Blickkontakt umschalten"); + p.sendMessage("§e/astools addplayer §7- Befehl (Spieler)"); + p.sendMessage("§e/astools addconsole §7- Befehl (Konsole)"); p.sendMessage("§e/astools remove §7- Befehle löschen"); - p.sendMessage("§e/astools reload §7- Konfig neu laden"); p.sendMessage("§8§m---------------------------------------"); return true; } diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandLookAtModule.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandLookAtModule.java new file mode 100644 index 0000000..10d6d22 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandLookAtModule.java @@ -0,0 +1,113 @@ +package de.nexuslobby.modules.armorstandtools; + +import de.nexuslobby.NexusLobby; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.EulerAngle; +import org.bukkit.util.Vector; + +public class ArmorStandLookAtModule { + + public ArmorStandLookAtModule() { + startRotationTask(); + } + + private void startRotationTask() { + new BukkitRunnable() { + @Override + public void run() { + // Wir gehen alle Welten durch, um markierte ArmorStands zu finden + for (World world : Bukkit.getWorlds()) { + for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) { + + // Nur ArmorStands mit dem Tag "as_lookat" + if (as.getScoreboardTags().contains("as_lookat")) { + + // Suche den nächsten Spieler im Umkreis von 7 Blöcken + Player nearest = getNearestPlayer(as, 7.0); + + if (nearest != null) { + // Spieler gefunden -> Kopf zum Spieler drehen + updateLookAt(as, nearest); + } else { + // Kein Spieler da -> Kopf sanft auf 0,0,0 zurücksetzen + resetHeadPose(as); + } + } + } + } + } + }.runTaskTimer(NexusLobby.getInstance(), 0L, 1L); + } + + private void updateLookAt(ArmorStand as, Player player) { + Location asLoc = as.getEyeLocation(); + Location target = player.getEyeLocation(); + + Vector direction = target.toVector().subtract(asLoc.toVector()).normalize(); + + // Ziel-Winkel aus Vektor berechnen + double targetYaw = Math.toDegrees(Math.atan2(-direction.getX(), direction.getZ())); + double targetPitch = Math.toDegrees(Math.asin(direction.getY())); + + // Relativer Yaw zum Körper-Yaw + double relativeYaw = targetYaw - as.getLocation().getYaw(); + relativeYaw = ((relativeYaw + 180) % 360 + 360) % 360 - 180; + + double yawRad = Math.toRadians(relativeYaw); + double pitchRad = Math.toRadians(-targetPitch); + + // Begrenzung (Nacken-Schutz) + if (yawRad > 1.2) yawRad = 1.2; + if (yawRad < -1.2) yawRad = -1.2; + if (pitchRad > 0.8) pitchRad = 0.8; + if (pitchRad < -0.8) pitchRad = -0.8; + + applySmoothPose(as, pitchRad, yawRad); + } + + private void resetHeadPose(ArmorStand as) { + EulerAngle current = as.getHeadPose(); + // Wenn der Kopf schon (fast) gerade ist, nichts tun + if (Math.abs(current.getX()) < 0.01 && Math.abs(current.getY()) < 0.01) { + if (current.getX() != 0 || current.getY() != 0) { + as.setHeadPose(new EulerAngle(0, 0, 0)); + } + return; + } + + // Ziel: 0, 0, 0 (Geradeaus schauen) + applySmoothPose(as, 0, 0); + } + + private void applySmoothPose(ArmorStand as, double pitchRad, double yawRad) { + EulerAngle current = as.getHeadPose(); + double lerp = 0.15; // Geschmeidigkeit + + double finalPitch = current.getX() + (pitchRad - current.getX()) * lerp; + double finalYaw = current.getY() + (yawRad - current.getY()) * lerp; + + as.setHeadPose(new EulerAngle(finalPitch, finalYaw, 0)); + } + + private Player getNearestPlayer(ArmorStand as, double range) { + Player nearest = null; + double dSquared = range * range; + + for (Player p : as.getWorld().getPlayers()) { + // Falls Spieler im Vanish/Spectator ist, ignorieren (optional) + if (p.getGameMode().name().equals("SPECTATOR")) continue; + + double dist = p.getLocation().distanceSquared(as.getLocation()); + if (dist < dSquared) { + dSquared = dist; + nearest = p; + } + } + return nearest; + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandStatusModule.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandStatusModule.java new file mode 100644 index 0000000..1236388 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandStatusModule.java @@ -0,0 +1,80 @@ +package de.nexuslobby.modules.armorstandtools; + +import de.nexuslobby.NexusLobby; +import de.nexuslobby.api.Module; +import de.nexuslobby.modules.servers.ServerChecker; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.World; + +public class ArmorStandStatusModule implements Module { + + @Override + public String getName() { + return "ArmorStandStatus"; + } + + @Override + public void onEnable() { + // Alle 10 Sekunden prüfen + Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), this::updateAllServerArmorStands, 100L, 200L); + } + + @Override + public void onDisable() {} + + private void updateAllServerArmorStands() { + for (World world : Bukkit.getWorlds()) { + for (Entity entity : world.getEntitiesByClass(ArmorStand.class)) { + if (entity instanceof ArmorStand as) { + checkAndRefreshStatus(as); + } + } + } + } + + private void checkAndRefreshStatus(ArmorStand as) { + String bungeeTag = null; + for (String tag : as.getScoreboardTags()) { + if (tag.startsWith("ascmd:bungee:")) { + bungeeTag = tag.replace("ascmd:bungee:", ""); + break; + } + } + + if (bungeeTag == null) return; + + String serverName = bungeeTag.toLowerCase(); + String ip = NexusLobby.getInstance().getConfig().getString("servers." + serverName + ".ip", "127.0.0.1"); + int port = NexusLobby.getInstance().getConfig().getInt("servers." + serverName + ".port", 25565); + + ServerChecker.isOnline(ip, port).thenAccept(isOnline -> { + Bukkit.getScheduler().runTask(NexusLobby.getInstance(), () -> { + String originalDisplayName = getOriginalName(as); + if (originalDisplayName == null) return; + + String translatedName = ChatColor.translateAlternateColorCodes('&', originalDisplayName); + + if (isOnline) { + // Zeigt nur den normalen Namen an, wenn online + as.setCustomName(translatedName); + } else { + // Zeigt den Namen an und darunter (getrennt durch Leerzeichen/Format) den Status + // Da Minecraft Namen meist einzeilig sind, nutzen wir eine klare farbliche Trennung + as.setCustomName(translatedName + " §7- §cOffline"); + } + }); + }); + } + + private String getOriginalName(ArmorStand as) { + for (String tag : as.getScoreboardTags()) { + if (tag.startsWith("as_displayname:")) { + return tag.replace("as_displayname:", "").replace("§§", ":"); + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandTool.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandTool.java index 678342d..95588bf 100644 --- a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandTool.java +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandTool.java @@ -6,11 +6,12 @@ import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import de.nexuslobby.NexusLobby; import java.util.ArrayList; import java.util.List; public enum ArmorStandTool { - // --- HAUPTMENÜ ATTRIBUTE --- INVIS(Material.GLASS_PANE, 10), ARMS(Material.STICK, 11), BASE(Material.STONE_SLAB, 12), @@ -19,10 +20,11 @@ public enum ArmorStandTool { INVUL(Material.BEDROCK, 15), SET_NAME(Material.NAME_TAG, 16), - // Zentrales Item zum Öffnen des Pose-Editors + // Neuer Button für Gespräche + CONV_SETUP(Material.WRITABLE_BOOK, 17), + OPEN_POSE_EDITOR(Material.ARMOR_STAND, 31), - // --- DIESE TEILE WANDERN IN DAS UNTERMENÜ (isForGui = false) --- HEAD_ROT(Material.PLAYER_HEAD, -1), BODY_ROT(Material.IRON_CHESTPLATE, -1), L_ARM_ROT(Material.STICK, -1), @@ -50,16 +52,28 @@ public enum ArmorStandTool { case INVUL -> as.setInvulnerable(!as.isInvulnerable()); case SET_NAME -> { p.closeInventory(); - p.sendMessage("§6§lHologramm-Editor: §7Nutze §e/nexuscmd name "); + p.sendMessage("§8[§6Nexus§8] §7Nutze §e/nexuscmd name "); AST.selectedArmorStand.put(p.getUniqueId(), as); } + case CONV_SETUP -> { + // Automatisches Markieren via GUI + if (!p.hasMetadata("conv_npc1")) { + p.setMetadata("conv_npc1", new FixedMetadataValue(NexusLobby.getInstance(), as.getUniqueId().toString())); + p.sendMessage("§8[§6Nexus§8] §aNPC als Sprecher 1 markiert."); + } else { + p.setMetadata("conv_npc2", new FixedMetadataValue(NexusLobby.getInstance(), as.getUniqueId().toString())); + p.sendMessage("§8[§6Nexus§8] §aNPC als Sprecher 2 markiert."); + p.sendMessage("§8[§6Nexus§8] §7Nutze nun §e/nexuscmd conv start "); + } + p.closeInventory(); + } case REMOVE -> { as.remove(); p.closeInventory(); p.sendMessage("§cNexus ArmorStand entfernt."); } case OPEN_POSE_EDITOR -> ArmorStandPoseGUI.openPartSelectionMenu(p, as); - default -> {} // Pose-Detailklicks werden direkt im PoseGUI/Listener behandelt + default -> {} } } @@ -70,8 +84,12 @@ public enum ArmorStandTool { ItemStack item = new ItemStack(material); ItemMeta meta = item.getItemMeta(); if (meta != null) { - String name = (this == OPEN_POSE_EDITOR) ? "§a§lPose Editor öffnen" : "§6" + this.name().replace("_", " "); - meta.setDisplayName(name); + String displayName = switch (this) { + case OPEN_POSE_EDITOR -> "§a§lPose Editor öffnen"; + case CONV_SETUP -> "§d§lGesprächs-Setup"; + default -> "§6" + this.name().replace("_", " "); + }; + meta.setDisplayName(displayName); List lore = new ArrayList<>(); lore.add("§7Klicken zum Verwalten"); meta.setLore(lore); @@ -83,8 +101,8 @@ public enum ArmorStandTool { public static ArmorStandTool get(ItemStack item) { if (item == null || !item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return null; String name = ChatColor.stripColor(item.getItemMeta().getDisplayName()).replace(" ", "_"); - // Spezielles Mapping für den Pose-Editor-Button im Listener if (name.equals("Pose_Editor_öffnen")) return OPEN_POSE_EDITOR; + if (name.equals("Gesprächs-Setup")) return CONV_SETUP; try { return valueOf(name); } catch (Exception e) { return null; } } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ConversationManager.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ConversationManager.java new file mode 100644 index 0000000..e409df9 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ConversationManager.java @@ -0,0 +1,166 @@ +package de.nexuslobby.modules.armorstandtools; + +import de.nexuslobby.NexusLobby; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class ConversationManager { + + private final NexusLobby plugin; + private File file; + private FileConfiguration config; + + public ConversationManager(NexusLobby plugin) { + this.plugin = plugin; + setupFile(); + } + + public void setupFile() { + this.file = new File(plugin.getDataFolder(), "conversations.yml"); + + if (!file.exists()) { + try { + if (!plugin.getDataFolder().exists()) { + plugin.getDataFolder().mkdirs(); + } + plugin.saveResource("conversations.yml", false); + } catch (Exception e) { + try { + file.createNewFile(); + YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(file); + defaultConfig.set("conversations.test.dialogue", Arrays.asList( + "&eNPC 1: &7Hallo!", + "&aNPC 2: &7Hi, wie geht es dir?", + "&eNPC 1: &7Bestens, danke!" + )); + defaultConfig.save(file); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + this.config = YamlConfiguration.loadConfiguration(file); + } + + public void saveLink(UUID id1, UUID id2, String dialogId) { + config.set("links." + id1.toString() + ".partner", id2.toString()); + config.set("links." + id1.toString() + ".dialog", dialogId); // WICHTIG: ".dialog" + saveConfig(); + } + + public void removeLink(UUID id) { + if (config.contains("links." + id.toString())) { + config.set("links." + id.toString(), null); + saveConfig(); + } + } + + public void startAllAutomatedConversations() { + if (config.getConfigurationSection("links") == null) return; + + for (String npc1String : config.getConfigurationSection("links").getKeys(false)) { + try { + UUID id1 = UUID.fromString(npc1String); + UUID id2 = UUID.fromString(config.getString("links." + npc1String + ".partner")); + String dialogId = config.getString("links." + npc1String + ".dialog"); // WICHTIG: ".dialog" + + Entity e1 = Bukkit.getEntity(id1); + Entity e2 = Bukkit.getEntity(id2); + + if (e1 instanceof ArmorStand as1 && e2 instanceof ArmorStand as2) { + if (as1.getNearbyEntities(15, 15, 15).stream().anyMatch(e -> e instanceof Player)) { + playConversation(id1, id2, dialogId); + } + } + } catch (Exception ignored) {} + } + } + + public List getConversationIds() { + if (config == null || !config.contains("conversations")) { + return new ArrayList<>(); + } + return new ArrayList<>(config.getConfigurationSection("conversations").getKeys(false)); + } + + public void playConversation(UUID id1, UUID id2, String key) { + // Sicherstellen, dass wir auf "conversations.KEY.dialogue" prüfen + if (config == null || !config.contains("conversations." + key)) { + Bukkit.getLogger().warning("[NexusLobby] Dialog-ID '" + key + "' nicht in conversations.yml gefunden!"); + return; + } + + List lines = config.getStringList("conversations." + key + ".dialogue"); + if (lines == null || lines.isEmpty()) return; + + new BukkitRunnable() { + int step = 0; + + @Override + public void run() { + if (step >= lines.size()) { + this.cancel(); + return; + } + + UUID speakerUUID = (step % 2 == 0) ? id1 : id2; + Entity entity = Bukkit.getEntity(speakerUUID); + + if (entity instanceof ArmorStand as) { + as.getWorld().playSound(as.getLocation(), Sound.ENTITY_CHICKEN_EGG, 0.5f, 1.5f); + showBubble(as, lines.get(step)); + } else { + this.cancel(); + return; + } + + step++; + } + }.runTaskTimer(plugin, 0L, 70L); + } + + private void showBubble(ArmorStand as, String text) { + Location loc = as.getEyeLocation().add(0, 0.6, 0); + + ArmorStand bubble = as.getWorld().spawn(loc, ArmorStand.class, s -> { + s.setMarker(true); + s.setVisible(false); + s.setGravity(false); + s.setCustomName(ChatColorTranslate(text)); + s.setCustomNameVisible(true); + s.setInvulnerable(true); + }); + + Bukkit.getScheduler().runTaskLater(plugin, bubble::remove, 60L); + } + + private String ChatColorTranslate(String text) { + return text.replace("&", "§"); + } + + private void saveConfig() { + try { + config.save(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void reload() { + this.config = YamlConfiguration.loadConfiguration(file); + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/servers/ServerChecker.java b/src/main/java/de/nexuslobby/modules/servers/ServerChecker.java new file mode 100644 index 0000000..91d6241 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/servers/ServerChecker.java @@ -0,0 +1,119 @@ +package de.nexuslobby.modules.servers; + +import de.nexuslobby.NexusLobby; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.entity.ArmorStand; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.concurrent.CompletableFuture; + +public class ServerChecker { + + /** + * Prüft asynchron, ob ein Server unter der angegebenen IP und Port erreichbar ist. + */ + public static CompletableFuture isOnline(String ip, int port) { + return CompletableFuture.supplyAsync(() -> { + try (Socket socket = new Socket()) { + // 500ms Timeout für stabilere Erkennung + socket.connect(new InetSocketAddress(ip, port), 500); + return true; + } catch (IOException e) { + return false; + } + }); + } + + /** + * Startet den Scheduler, der alle ArmorStands in allen Welten regelmäßig prüft. + */ + public static void startGlobalChecker() { + // WICHTIG: runTaskTimer (synchron), um den Main-Thread für getEntitiesByClass zu nutzen + Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { + for (World world : Bukkit.getWorlds()) { + // Das Abrufen der Entities muss auf dem Main-Thread passieren + for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) { + checkAndUpdateArmorStand(as); + } + } + }, 100L, 200L); + } + + /** + * Analysiert die Tags eines ArmorStands und aktualisiert den Namen basierend auf dem Serverstatus. + */ + private static void checkAndUpdateArmorStand(ArmorStand as) { + for (String tag : as.getScoreboardTags()) { + // Erwartetes Format: ascmd:slot1:slot2:type:command + if (tag.startsWith("ascmd:")) { + String[] parts = tag.split(":"); + if (parts.length < 5) continue; + + String type = parts[3].toLowerCase(); + if (!type.equals("bungee")) continue; + + String serverName = parts[4]; + + // Suche den passenden Key in der Config + String configKey = null; + if (NexusLobby.getInstance().getConfig().getConfigurationSection("servers") != null) { + for (String key : NexusLobby.getInstance().getConfig().getConfigurationSection("servers").getKeys(false)) { + if (key.equalsIgnoreCase(serverName)) { + configKey = key; + break; + } + } + } + + if (configKey == null) continue; + + String ip = NexusLobby.getInstance().getConfig().getString("servers." + configKey + ".ip"); + int port = NexusLobby.getInstance().getConfig().getInt("servers." + configKey + ".port"); + + if (ip == null) continue; + + // Der Ping selbst läuft asynchron weg vom Main-Thread + isOnline(ip, port).thenAccept(online -> { + // Zurück zum Main-Thread für die Änderung des ArmorStands + Bukkit.getScheduler().runTask(NexusLobby.getInstance(), () -> { + if (!as.isValid()) return; // Sicherheitscheck falls AS gelöscht wurde + + if (online) { + restoreName(as); + } else { + String offlineText = "§c● §lOFFLINE §c●"; + if (!offlineText.equals(as.getCustomName())) { + as.setCustomName(offlineText); + as.setCustomNameVisible(true); + } + } + }); + }); + break; + } + } + } + + /** + * Stellt den ursprünglichen Namen des ArmorStands wieder her. + * Nutzt den Tag "asname:NAME" als Backup. + */ + private static void restoreName(ArmorStand as) { + for (String t : as.getScoreboardTags()) { + if (t.startsWith("asname:")) { + String originalName = t.substring(7); + String translatedName = ChatColor.translateAlternateColorCodes('&', originalName); + + if (!translatedName.equals(as.getCustomName())) { + as.setCustomName(translatedName); + as.setCustomNameVisible(true); + } + return; + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 608f5db..d27a3fd 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -27,6 +27,16 @@ lobby: default-gamemode: Adventure clear-inventory-on-join: true +# Mapping für den Server-Status-Ping der ArmorStands +# Der Name (z.B. survival) muss exakt dem Bungee-Servernamen entsprechen +servers: + survival: + ip: "127.0.0.1" + port: 25566 + skyblock: + ip: "127.0.0.1" + port: 25567 + # --- Tablist Einstellungen --- tablist: enabled: true @@ -54,7 +64,7 @@ items: portals: default-particle: "PORTAL" portal-cooldown: 40 # Ticks, 2 Sekunden - save-file: "portals.yml" # Datei im Plugin-Ordner + save-file: "portals.yml" # ----------------------------------------------------- # COMPASS MENU diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a8a4045..6284aee 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: NexusLobby main: de.nexuslobby.NexusLobby -version: "1.0.4" +version: "1.0.5" api-version: "1.21" author: M_Viper description: Modular Lobby Plugin @@ -40,13 +40,16 @@ commands: nexuslobby: description: Zeigt Informationen über das Plugin an oder lädt es neu usage: /nexuslobby [reload|setspawn] - aliases: [nexus] + aliases: [nexus, lobby] nexustools: - description: Nexus ArmorStand Editor + description: Nexus ArmorStand Editor (LookAt, Dynamic, etc.) aliases: [nt, ntools, astools] nexuscmd: - description: Nexus Command Binder - aliases: [ncmd, ascmd] + description: Nexus Command Binder (Slots 0-9) und Gesprächs-System + usage: /nexuscmd + permission: nexuslobby.armorstand.cmd + permission-message: "§cDu hast keine Rechte!" + aliases: [ncmd, ascmd, conv] holo: description: Verwalte Lobby Hologramme (Text-Displays) usage: /holo [text] @@ -66,6 +69,7 @@ commands: spawn: description: Teleportiert dich zum Lobby-Spawnpunkt usage: /spawn + aliases: [l, hub] permissions: nexuslobby.portal: @@ -90,11 +94,14 @@ permissions: description: Erlaubt die Nutzung der NexusTools GUI default: op nexuslobby.armorstand.cmd: - description: Erlaubt das Binden von Commands via NexusCmd + description: Erlaubt das Binden von Commands und das Verwalten von Conversations default: op nexuslobby.armorstand.dynamic: description: Erlaubt das Markieren von dynamischen NPCs default: op + nexuslobby.armorstand.lookat: + description: Erlaubt das Aktivieren des Blickkontakts bei NPCs + default: op nexuslobby.hologram: description: Erlaubt das Erstellen von Text-Display Hologrammen default: op