diff --git a/src/main/java/de/nexuslobby/NexusLobby.java b/src/main/java/de/nexuslobby/NexusLobby.java index 9f8aac0..d4cead8 100644 --- a/src/main/java/de/nexuslobby/NexusLobby.java +++ b/src/main/java/de/nexuslobby/NexusLobby.java @@ -15,6 +15,7 @@ import de.nexuslobby.modules.portal.PortalCommand; import de.nexuslobby.modules.servers.ServerSwitcherListener; import de.nexuslobby.modules.armorstandtools.*; import de.nexuslobby.modules.gadgets.GadgetModule; +import de.nexuslobby.modules.hologram.HologramModule; import de.nexuslobby.utils.VoidProtection; import de.nexuslobby.utils.DoubleJump; import de.nexuslobby.utils.PlayerHider; @@ -50,6 +51,8 @@ public class NexusLobby extends JavaPlugin implements Listener { private LobbySettingsModule lobbySettingsModule; private ItemsModule itemsModule; private GadgetModule gadgetModule; + private HologramModule hologramModule; + private DynamicArmorStandModule dynamicArmorStandModule; // Neu hinzugefügt private File visualsFile; private FileConfiguration visualsConfig; @@ -65,7 +68,6 @@ public class NexusLobby extends JavaPlugin implements Listener { public void onEnable() { instance = this; - // Erst Config initialisieren initCustomConfigs(); getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); @@ -137,6 +139,13 @@ public class NexusLobby extends JavaPlugin implements Listener { this.gadgetModule = new GadgetModule(); moduleManager.registerModule(this.gadgetModule); + this.hologramModule = new HologramModule(); + moduleManager.registerModule(this.hologramModule); + + // Dynamic ArmorStand Module registrieren + this.dynamicArmorStandModule = new DynamicArmorStandModule(); + moduleManager.registerModule(this.dynamicArmorStandModule); + moduleManager.registerModule(new SecurityModule()); moduleManager.registerModule(new BossBarModule()); moduleManager.registerModule(new ActionBarModule()); @@ -200,14 +209,9 @@ public class NexusLobby extends JavaPlugin implements Listener { File configFile = new File(getDataFolder(), "config.yml"); if (!configFile.exists()) { - // Nur speichern wenn sie fehlt saveResource("config.yml", false); - } else { - // WICHTIG: ConfigUpdater für config.yml deaktiviert, da er Sektionen nicht korrekt erkennt! - // ConfigUpdater.updateConfig("config.yml"); } - // Einfaches Laden reicht völlig aus reloadConfig(); File settingsFile = new File(getDataFolder(), "settings.yml"); @@ -247,7 +251,7 @@ public class NexusLobby extends JavaPlugin implements Listener { } private void registerCommands() { - LobbyTabCompleter tabCompleter = new LobbyTabCompleter(portalManager); + LobbyTabCompleter tabCompleter = new LobbyTabCompleter(portalManager, hologramModule); PluginCommand portalCmd = this.getCommand("portal"); if (portalCmd != null) { @@ -255,6 +259,12 @@ public class NexusLobby extends JavaPlugin implements Listener { portalCmd.setTabCompleter(tabCompleter); } + PluginCommand holoCmd = this.getCommand("holo"); + if (holoCmd != null) { + holoCmd.setExecutor(new HoloCommand(hologramModule)); + holoCmd.setTabCompleter(tabCompleter); + } + PluginCommand maintenanceCmd = this.getCommand("maintenance"); if (maintenanceCmd != null) { maintenanceCmd.setExecutor(new MaintenanceCommand()); @@ -303,4 +313,6 @@ public class NexusLobby extends JavaPlugin implements Listener { 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; } // Getter hinzugefügt } \ 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 05ca7c0..34e3a8e 100644 --- a/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java +++ b/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java @@ -1,6 +1,7 @@ package de.nexuslobby.commands; import de.nexuslobby.modules.portal.PortalManager; +import de.nexuslobby.modules.hologram.HologramModule; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; @@ -13,9 +14,11 @@ import java.util.stream.Collectors; public class LobbyTabCompleter implements TabCompleter { private final PortalManager portalManager; + private final HologramModule hologramModule; - public LobbyTabCompleter(PortalManager portalManager) { + public LobbyTabCompleter(PortalManager portalManager, HologramModule hologramModule) { this.portalManager = portalManager; + this.hologramModule = hologramModule; } @Override @@ -37,6 +40,16 @@ public class LobbyTabCompleter implements TabCompleter { } } + // --- Hologram Befehl --- + else if (command.getName().equalsIgnoreCase("holo")) { + if (args.length == 1) { + suggestions.add("create"); + suggestions.add("delete"); + } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { + suggestions.addAll(hologramModule.getHologramIds()); + } + } + // --- Wartungsmodus --- else if (command.getName().equalsIgnoreCase("maintenance")) { if (args.length == 1) { @@ -60,23 +73,22 @@ public class LobbyTabCompleter implements TabCompleter { else if (command.getName().equalsIgnoreCase("nexustools") || command.getName().equalsIgnoreCase("astools") || command.getName().equalsIgnoreCase("nt")) { if (args.length == 1) { suggestions.add("reload"); + suggestions.add("dynamic"); // Neu: Vorschlag für den Dynamic-Modus } } // --- NexusCmd (ehemals ascmd) --- else if (command.getName().equalsIgnoreCase("nexuscmd") || command.getName().equalsIgnoreCase("ascmd") || command.getName().equalsIgnoreCase("ncmd")) { if (args.length == 1) { - suggestions.add("name"); // NEU + suggestions.add("name"); suggestions.add("list"); suggestions.add("add"); suggestions.add("remove"); } - // Vorschläge für: /nexuscmd name else if (args.length == 2 && args[0].equalsIgnoreCase("name")) { suggestions.add("none"); suggestions.add(""); } - // Vorschläge für: /nexuscmd add else if (args.length == 2 && args[0].equalsIgnoreCase("add")) { suggestions.add("0"); } @@ -93,7 +105,6 @@ public class LobbyTabCompleter implements TabCompleter { } } - // Filtert die Liste basierend auf dem, was der Spieler bereits getippt hat return suggestions.stream() .filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase())) .collect(Collectors.toList()); diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCommand.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCommand.java index c5d3115..a24de19 100644 --- a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCommand.java +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCommand.java @@ -1,6 +1,8 @@ package de.nexuslobby.modules.armorstandtools; +import de.nexuslobby.NexusLobby; import org.bukkit.ChatColor; +import org.bukkit.Particle; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -11,85 +13,143 @@ import org.jetbrains.annotations.NotNull; import java.util.Set; +/** + * ArmorStandCommand - Vollständige Steuerung für ArmorStands. + * Inklusive Dynamic-Modus Erkennung und visueller Rückmeldung. + */ public class ArmorStandCommand implements CommandExecutor { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (!(sender instanceof Player p)) return true; - - if (!p.hasPermission("nexuslobby.armorstand.use")) { - p.sendMessage(Config.prefix() + ChatColor.RED + Config.generalNoPerm); + if (!(sender instanceof Player p)) { + sender.sendMessage("Nur Spieler können diesen Befehl ausführen."); return true; } - // Suche ArmorStand im Umkreis von 5 Blöcken - ArmorStand target = getNearbyArmorStand(p); + // Fester Prefix für maximale Build-Sicherheit + String prefix = "§8[§6Nexus§8] "; - // Fall 1: Nur /astools -> GUI öffnen + if (!p.hasPermission("nexuslobby.armorstand.use")) { + p.sendMessage(prefix + ChatColor.RED + "Dazu hast du keine Rechte!"); + return true; + } + + // 1. Suche nach dem ArmorStand (Präzise & Umkreis) + ArmorStand target = getBestTargetArmorStand(p); + + // Fall: Nur /astools oder /nt (Ohne Argumente) if (args.length == 0) { if (target != null) { AST.selectedArmorStand.put(p.getUniqueId(), target); new ArmorStandGUI(target, p); + p.sendMessage(prefix + "§aEditor für ArmorStand geöffnet."); } else { - p.sendMessage(Config.prefix() + ChatColor.RED + "Kein ArmorStand in der Nähe gefunden!"); + p.sendMessage(prefix + ChatColor.RED + "Kein ArmorStand in der Nähe gefunden!"); } return true; } - // Fall 2: Unterbefehle (add, remove, list) String sub = args[0].toLowerCase(); + // Globaler Reload + if (sub.equals("reload")) { + NexusLobby.getInstance().reloadPlugin(); + p.sendMessage(prefix + "§aKonfiguration und Module wurden neu geladen."); + return true; + } + + // Für alle anderen Aktionen ist ein ArmorStand zwingend erforderlich if (target == null) { - p.sendMessage(Config.prefix() + ChatColor.RED + "Du musst nah an einem ArmorStand stehen!"); + p.sendMessage(prefix + ChatColor.RED + "Du musst einen ArmorStand anschauen oder direkt davor stehen!"); return true; } switch (sub) { - case "addplayer": // /astools addplayer - if (args.length < 2) return sendHelp(p); + case "dynamic": + if (!p.hasPermission("nexuslobby.armorstand.dynamic")) { + p.sendMessage(prefix + ChatColor.RED + "Keine Rechte für Dynamic-NPCs!"); + return true; + } + + 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.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 "addplayer": + if (args.length < 2) return sendHelp(p, prefix); String pCmd = buildString(args, 1); target.addScoreboardTag("ascmd:player:" + pCmd); - p.sendMessage(Config.prefix() + "§aBefehl (Player) hinzugefügt: §e" + pCmd); + p.sendMessage(prefix + "§aBefehl (Player) gespeichert: §e/" + pCmd); break; - case "addconsole": // /astools addconsole - if (args.length < 2) return sendHelp(p); + case "addconsole": + if (args.length < 2) return sendHelp(p, prefix); String cCmd = buildString(args, 1); target.addScoreboardTag("ascmd:console:" + cCmd); - p.sendMessage(Config.prefix() + "§aBefehl (Konsole) hinzugefügt: §e" + cCmd); + p.sendMessage(prefix + "§aBefehl (Konsole) gespeichert: §e" + cCmd); break; - case "addbungee": // /astools addbungee - if (args.length < 2) return sendHelp(p); - String server = args[1]; - target.addScoreboardTag("ascmd:bungee:" + server); - p.sendMessage(Config.prefix() + "§aBefehl (Bungee) hinzugefügt: §eConnect zu " + server); - break; - - case "remove": // /astools remove (Löscht alle ascmd Tags) - Set tags = target.getScoreboardTags(); - tags.removeIf(tag -> tag.startsWith("ascmd:")); - p.sendMessage(Config.prefix() + "§cAlle Befehle von diesem ArmorStand entfernt."); - break; - - case "reload": - Config.load(); - p.sendMessage(Config.prefix() + "§aKonfiguration neu geladen."); + case "remove": + target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:")); + p.sendMessage(prefix + "§cAlle Befehls-Tags wurden entfernt."); break; default: - sendHelp(p); + sendHelp(p, prefix); break; } return true; } - private ArmorStand getNearbyArmorStand(Player p) { - for (Entity e : p.getNearbyEntities(5, 5, 5)) { - if (e instanceof ArmorStand as) return as; + /** + * 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" + + 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() + ); + + if (dot > bestDot) { + bestDot = dot; + best = as; + } + } } - return null; + + // 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) { + return as; + } + } + } + + return best; } private String buildString(String[] args, int start) { @@ -100,14 +160,16 @@ public class ArmorStandCommand implements CommandExecutor { return sb.toString(); } - private boolean sendHelp(Player p) { - p.sendMessage("§6§lArmorStandTools Hilfe:"); - p.sendMessage("§e/astools §7- Öffnet Editor"); - p.sendMessage("§e/astools addplayer §7- Befehl als Spieler ausführen"); - p.sendMessage("§e/astools addconsole §7- Befehl als Konsole ausführen"); - p.sendMessage("§e/astools addbungee §7- Serverwechsel"); - p.sendMessage("§e/astools remove §7- Entfernt alle Befehle"); - p.sendMessage("§e/astools reload §7- Lädt Config neu"); + 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 remove §7- Befehle löschen"); + p.sendMessage("§e/astools reload §7- Konfig neu laden"); + p.sendMessage("§8§m---------------------------------------"); return true; } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/DynamicArmorStandModule.java b/src/main/java/de/nexuslobby/modules/armorstandtools/DynamicArmorStandModule.java new file mode 100644 index 0000000..78b9ef2 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/DynamicArmorStandModule.java @@ -0,0 +1,119 @@ +package de.nexuslobby.modules.armorstandtools; + +import de.nexuslobby.NexusLobby; +import de.nexuslobby.api.Module; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; + +import java.time.LocalTime; + +/** + * DynamicArmorStandModule + * Reagiert auf Uhrzeit und Wetter. + * Nutzt PersistentData, damit die Einstellung auch nach einem Server-Neustart bleibt. + */ +public class DynamicArmorStandModule implements Module { + + // Der Key muss mit dem Command übereinstimmen oder das Modul nutzt Tags. + // Wir nutzen hier PersistentData, da es sicherer ist als Tags. + private final NamespacedKey npcKey = new NamespacedKey(NexusLobby.getInstance(), "dynamic_npc"); + + @Override + public String getName() { + return "DynamicArmorStands"; + } + + @Override + public void onEnable() { + startUpdateTask(); + Bukkit.getLogger().info("[NexusLobby] DynamicArmorStandModule wurde aktiviert."); + } + + /** + * Startet einen Timer, der alle 5 Sekunden (100 Ticks) prüft. + */ + private void startUpdateTask() { + Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { + updateAllArmorStands(); + }, 100L, 100L); // Alle 5 Sekunden statt 2 Minuten + } + + public void updateAllArmorStands() { + int hour = LocalTime.now().getHour(); + + for (World world : Bukkit.getWorlds()) { + boolean isRaining = world.hasStorm(); + + for (Entity entity : world.getEntities()) { + if (entity instanceof ArmorStand armorStand) { + // PRÜFUNG: Entweder PersistentData ODER Scoreboard-Tag "as_dynamic" + if (armorStand.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE) || + armorStand.getScoreboardTags().contains("as_dynamic")) { + + applyDynamicChanges(armorStand, hour, isRaining); + } + } + } + } + } + + private void applyDynamicChanges(ArmorStand as, int hour, boolean isRaining) { + // --- ZEIT-LOGIK (Nacht: 20:00 - 06:00) --- + if (hour >= 20 || hour <= 6) { + if (as.getEquipment().getItemInOffHand().getType() != Material.TORCH) { + as.getEquipment().setItemInOffHand(new ItemStack(Material.TORCH)); + } + } else { + if (as.getEquipment().getItemInOffHand().getType() == Material.TORCH) { + as.getEquipment().setItemInOffHand(null); + } + } + + // --- WETTER-LOGIK (Regen) --- + if (isRaining) { + if (as.getEquipment().getHelmet() == null || as.getEquipment().getHelmet().getType() != Material.LEATHER_HELMET) { + as.getEquipment().setHelmet(new ItemStack(Material.LEATHER_HELMET)); + } + } else { + // Nur ausziehen, wenn es hellwach ist und nicht regnet + if (hour < 20 && hour > 6) { + if (as.getEquipment().getHelmet() != null && as.getEquipment().getHelmet().getType() == Material.LEATHER_HELMET) { + as.getEquipment().setHelmet(null); + } + } + } + } + + /** + * Schaltet den Status um. Wird vom ArmorStandCommand aufgerufen. + */ + public void toggleDynamicStatus(ArmorStand as) { + if (as.getPersistentDataContainer().has(npcKey, PersistentDataType.BYTE)) { + // Deaktivieren + as.getPersistentDataContainer().remove(npcKey); + as.removeScoreboardTag("as_dynamic"); // Zur Sicherheit beides entfernen + as.getEquipment().setItemInOffHand(null); + as.getEquipment().setHelmet(null); + } else { + // Aktivieren + as.getPersistentDataContainer().set(npcKey, PersistentDataType.BYTE, (byte) 1); + as.addScoreboardTag("as_dynamic"); + + // Sofortige visuelle Rückmeldung + int hour = LocalTime.now().getHour(); + boolean isRaining = as.getWorld().hasStorm(); + applyDynamicChanges(as, hour, isRaining); + } + } + + @Override + public void onDisable() { + // Nichts zu tun + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java b/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java index 4a64508..831c5d3 100644 --- a/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java +++ b/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java @@ -4,28 +4,37 @@ import de.nexuslobby.NexusLobby; import de.nexuslobby.api.Module; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerFishEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; public class GadgetModule implements Module, Listener { private final Map activeBalloons = new HashMap<>(); private final Map activeEffects = new HashMap<>(); + private final Set activeShields = new HashSet<>(); - // Titel für die verschiedenen Inventare zur Identifikation private final String MAIN_TITLE = "§b§lGadgets §8- §7Menü"; private final String BALLOON_TITLE = "§b§lGadgets §8- §eBallons"; private final String PARTICLE_TITLE = "§b§lGadgets §8- §dPartikel"; private final String FUN_TITLE = "§b§lGadgets §8- §6Lustiges"; + private final String HAT_TITLE = "§b§lGadgets §8- §aHüte & Köpfe"; + private final String PET_TITLE = "§b§lGadgets §8- §dBegleiter"; @Override public String getName() { return "Gadgets"; } @@ -33,39 +42,104 @@ public class GadgetModule implements Module, Listener { @Override public void onEnable() { Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance()); + Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { + PetManager.updatePets(); activeBalloons.values().forEach(Balloon::update); - activeEffects.forEach((uuid, effect) -> { - Player p = Bukkit.getPlayer(uuid); - if (p != null && p.isOnline()) effect.update(p); - }); + + for (Player p : Bukkit.getOnlinePlayers()) { + UUID uuid = p.getUniqueId(); + handleSpecialHatEffects(p); + if (activeEffects.containsKey(uuid)) activeEffects.get(uuid).update(p); + if (activeShields.contains(uuid)) ShieldTask.handleShield(p); + } }, 1L, 1L); } - // --- GUI ÖFFNER --- + private void handleSpecialHatEffects(Player p) { + ItemStack hat = p.getInventory().getHelmet(); + if (hat == null || hat.getType() == Material.AIR) return; + + switch (hat.getType()) { + case CAMPFIRE: + p.getWorld().spawnParticle(Particle.CAMPFIRE_COSY_SMOKE, p.getLocation().add(0, 2.2, 0), 1, 0.05, 0.05, 0.05, 0.02); + break; + case SPAWNER: + p.getWorld().spawnParticle(Particle.FLAME, p.getLocation().add(0, 2.1, 0), 1, 0.12, 0.12, 0.12, 0.02); + break; + case SEA_LANTERN: + case BEACON: + p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add(0, 2.1, 0), 1, 0.1, 0.1, 0.1, 0.03); + break; + case ENCHANTING_TABLE: + p.getWorld().spawnParticle(Particle.ENCHANT, p.getLocation().add(0, 2.3, 0), 1, 0.2, 0.2, 0.2, 0.5); + break; + } + } public void openGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 27, MAIN_TITLE); fillEdges(gui); gui.setItem(10, createItem(Material.LEAD, "§e§lBallons", "§7Wähle einen fliegenden Begleiter")); - gui.setItem(13, createItem(Material.FIREWORK_ROCKET, "§6§lLustiges", "§7Witzige Effekte für zwischendurch")); - gui.setItem(16, createItem(Material.NETHER_STAR, "§d§lPartikel", "§7Wähle magische Effekte")); + gui.setItem(11, createItem(Material.GOLDEN_HELMET, "§a§lHüte", "§7Setze dir etwas auf den Kopf")); + gui.setItem(13, createItem(Material.BONE, "§d§lBegleiter", "§7Echte Tiere, die dir folgen")); + gui.setItem(15, createItem(Material.FIREWORK_ROCKET, "§6§lLustiges", "§7Witzige Effekte")); + gui.setItem(16, createItem(Material.NETHER_STAR, "§d§lPartikel", "§7Magische Auren & Effekte")); gui.setItem(22, createItem(Material.BARRIER, "§c§lStopp", "§7Alle Gadgets entfernen")); + player.openInventory(gui); + } + private void openHatGUI(Player player) { + Inventory gui = Bukkit.createInventory(null, 45, HAT_TITLE); + fillEdges(gui); + + gui.setItem(10, createItem(Material.JACK_O_LANTERN, "§6Kürbis-Hut", "§7Es ist immer Halloween!")); + gui.setItem(11, createItem(Material.SEA_LANTERN, "§bMeeres-Leuchten", "§7§oEffekt: Glitzern")); + gui.setItem(12, createItem(Material.GLOWSTONE, "§eGlowstone-Kopf", "§7Werde zur Lampe")); + gui.setItem(13, createItem(Material.TNT, "§cExplosiv-Hut", "§7Vorsicht, heiß!")); + gui.setItem(14, createItem(Material.GLASS, "§fAstronaut", "§7Bereit für den Mond?")); + gui.setItem(15, createItem(Material.DRAGON_HEAD, "§5Enderdrache", "§7Der König der Lüfte")); + gui.setItem(16, createItem(Material.CAKE, "§dKuchen-Kopf", "§7Jeder mag Kuchen!")); + + gui.setItem(19, createItem(Material.SLIME_BLOCK, "§aGlibber-Block", "§7Ziemlich klebrig...")); + gui.setItem(20, createItem(Material.MELON, "§aMelonen-Helm", "§7Frisch und saftig")); + gui.setItem(21, createItem(Material.HAY_BLOCK, "§eStrohhut", "§7Sommer auf dem Land")); + gui.setItem(22, createItem(Material.SPAWNER, "§8Monster-Käfig", "§7§oEffekt: Flammen")); + gui.setItem(23, createItem(Material.CRAFTING_TABLE, "§6Werkbank", "§7Immer am Basteln")); + gui.setItem(24, createItem(Material.BOOKSHELF, "§fBücherregal", "§7Ein wahrer Schlaukopf")); + gui.setItem(25, createItem(Material.HONEY_BLOCK, "§6Honig-Hut", "§7Süß und klebrig")); + + gui.setItem(28, createItem(Material.GOLD_BLOCK, "§6Gold-Bonze", "§7Zeig was du hast")); + gui.setItem(29, createItem(Material.DIAMOND_ORE, "§bDiamant-Erz", "§7Bau mich bloß nicht ab!")); + gui.setItem(30, createItem(Material.BEACON, "§fLeuchtfeuer", "§7§oEffekt: Glitzern")); + gui.setItem(31, createItem(Material.CONDUIT, "§3Auge des Meeres", "§7Die Macht von Atlantis")); + gui.setItem(32, createItem(Material.ENCHANTING_TABLE, "§dMagier", "§7§oEffekt: Runen")); + gui.setItem(33, createItem(Material.CAMPFIRE, "§cHeißer Kopf", "§7§oEffekt: Rauch")); + gui.setItem(34, createItem(Material.SKELETON_SKULL, "§7Skelett", "§7Ein wenig gruselig")); + + gui.setItem(40, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); + player.openInventory(gui); + } + + private void openPetGUI(Player player) { + Inventory gui = Bukkit.createInventory(null, 27, PET_TITLE); + fillEdges(gui); + gui.setItem(11, createItem(Material.BONE, "§fWolf", "§7Ein treuer Begleiter")); + gui.setItem(13, createItem(Material.CAT_SPAWN_EGG, "§6Katze", "§7Ein verschmuster Freund")); + gui.setItem(15, createItem(Material.PANDA_SPAWN_EGG, "§aPanda", "§7Ein gemütlicher Zeitgenosse")); + gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); player.openInventory(gui); } private void openBalloonGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 36, BALLOON_TITLE); fillEdges(gui); - Material[] wools = {Material.WHITE_WOOL, Material.ORANGE_WOOL, Material.MAGENTA_WOOL, Material.LIGHT_BLUE_WOOL, Material.YELLOW_WOOL, Material.LIME_WOOL, Material.PINK_WOOL, Material.GRAY_WOOL, Material.CYAN_WOOL, Material.PURPLE_WOOL, Material.BLUE_WOOL, Material.BROWN_WOOL, Material.GREEN_WOOL, Material.RED_WOOL}; - int slot = 10; for (Material m : wools) { if (slot == 17) slot = 19; @@ -78,11 +152,9 @@ public class GadgetModule implements Module, Listener { private void openParticleGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 27, PARTICLE_TITLE); fillEdges(gui); - - gui.setItem(11, createItem(Material.POPPY, "§cHerzchen-Aura", "§7Verbreite Liebe")); - gui.setItem(13, createItem(Material.BLAZE_POWDER, "§6Flammen-Ring", "§7Werde feurig")); - gui.setItem(15, createItem(Material.WATER_BUCKET, "§bRegenwolke", "§7Lass es regnen")); - + gui.setItem(11, createItem(Material.POPPY, "§cHerzchen-Aura", "§7Verbreite Liebe in der Lobby")); + gui.setItem(13, createItem(Material.BLAZE_POWDER, "§6Flammen-Ring", "§7Lass es brennen!")); + gui.setItem(15, createItem(Material.WATER_BUCKET, "§bRegenwolke", "§7Deine persönliche Abkühlung")); gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); player.openInventory(gui); } @@ -90,70 +162,91 @@ public class GadgetModule implements Module, Listener { private void openFunGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 27, FUN_TITLE); fillEdges(gui); - - gui.setItem(13, createItem(Material.EGG, "§f§lChicken-Rain", "§7Lass es Küken regnen!")); - + gui.setItem(11, createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Zieh dich durch die Luft!")); + gui.setItem(13, createItem(Material.SHIELD, "§5§lSchutzzone", "§7Halte andere auf Distanz")); + gui.setItem(15, createItem(Material.EGG, "§f§lChicken-Rain", "§7Gack-Gack! Hühner überall!")); gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); player.openInventory(gui); } - // --- EVENT HANDLER --- - @EventHandler public void onInventoryClick(InventoryClickEvent event) { String title = event.getView().getTitle(); - if (!title.equals(MAIN_TITLE) && !title.equals(BALLOON_TITLE) && !title.equals(PARTICLE_TITLE) && !title.equals(FUN_TITLE)) return; - + if (!title.startsWith("§b§lGadgets")) return; event.setCancelled(true); Player player = (Player) event.getWhoClicked(); ItemStack item = event.getCurrentItem(); if (item == null || item.getType() == Material.AIR) return; - // Zurück-Button Logik - if (item.getType() == Material.ARROW) { - openGUI(player); - return; - } + if (item.getType() == Material.ARROW) { openGUI(player); return; } - // HAUPTMENÜ LOGIK if (title.equals(MAIN_TITLE)) { if (item.getType() == Material.LEAD) openBalloonGUI(player); + else if (item.getType() == Material.GOLDEN_HELMET) openHatGUI(player); + else if (item.getType() == Material.BONE) openPetGUI(player); else if (item.getType() == Material.NETHER_STAR) openParticleGUI(player); else if (item.getType() == Material.FIREWORK_ROCKET) openFunGUI(player); - else if (item.getType() == Material.BARRIER) { - removeGadgets(player); + else if (item.getType() == Material.BARRIER) { removeGadgets(player); player.closeInventory(); } + } + else if (title.equals(HAT_TITLE)) { + if (item.getType() != Material.GRAY_STAINED_GLASS_PANE) { + HatManager.setHat(player, item.getType(), item.getItemMeta().getDisplayName()); + player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_GENERIC, 1, 1); player.closeInventory(); } } - - // BALLON MENÜ LOGIK + else if (title.equals(PET_TITLE)) { + if (item.getType() == Material.BONE) PetManager.spawnEntityPet(player, "WOLF"); + else if (item.getType() == Material.CAT_SPAWN_EGG) PetManager.spawnEntityPet(player, "CAT"); + else if (item.getType() == Material.PANDA_SPAWN_EGG) PetManager.spawnEntityPet(player, "PANDA"); + player.sendMessage("§8[§6Nexus§8] §dDein Pet wurde gerufen!"); + player.closeInventory(); + } else if (title.equals(BALLOON_TITLE)) { if (item.getType().toString().endsWith("_WOOL")) { if (activeBalloons.containsKey(player.getUniqueId())) activeBalloons.get(player.getUniqueId()).remove(); activeBalloons.put(player.getUniqueId(), new Balloon(player, item.getType())); - player.sendMessage("§8[§6Nexus§8] §aBallon ausgerüstet!"); + player.sendMessage("§8[§6Nexus§8] §aBallon aktiviert!"); player.closeInventory(); } } - - // PARTIKEL MENÜ LOGIK else if (title.equals(PARTICLE_TITLE)) { if (item.getType() == Material.POPPY) activeEffects.put(player.getUniqueId(), new ParticleEffect("hearts")); else if (item.getType() == Material.BLAZE_POWDER) activeEffects.put(player.getUniqueId(), new ParticleEffect("flames")); else if (item.getType() == Material.WATER_BUCKET) activeEffects.put(player.getUniqueId(), new ParticleEffect("cloud")); - - if (item.getType() != Material.GRAY_STAINED_GLASS_PANE) { - player.sendMessage("§8[§6Nexus§8] §aEffekt aktiviert!"); - player.closeInventory(); - } + player.sendMessage("§8[§6Nexus§8] §aPartikel aktiviert!"); + player.closeInventory(); } - - // FUN MENÜ LOGIK else if (title.equals(FUN_TITLE)) { if (item.getType() == Material.EGG) { ChickenRain.start(player); - player.sendMessage("§8[§6Nexus§8] §fEs regnet jetzt Hühner!"); + player.sendMessage("§8[§6Nexus§8] §fHühnerregen gestartet!"); player.closeInventory(); + } else if (item.getType() == Material.FISHING_ROD) { + player.getInventory().addItem(createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Rechtsklick zum Katapultieren")); + player.closeInventory(); + } else if (item.getType() == Material.SHIELD) { + if (activeShields.contains(player.getUniqueId())) { + activeShields.remove(player.getUniqueId()); + player.sendMessage("§8[§6Nexus§8] §cSchutzzone deaktiviert."); + } else { + activeShields.add(player.getUniqueId()); + player.sendMessage("§8[§6Nexus§8] §5Schutzzone aktiviert!"); + } + player.closeInventory(); + } + } + } + + @EventHandler + public void onFish(PlayerFishEvent event) { + Player player = event.getPlayer(); + ItemStack item = player.getInventory().getItemInMainHand(); + if (item != null && item.getType() == Material.FISHING_ROD && item.hasItemMeta() && item.getItemMeta().getDisplayName().equals("§b§lEnterhaken")) { + if (event.getState() == PlayerFishEvent.State.IN_GROUND || event.getState() == PlayerFishEvent.State.REEL_IN || event.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) { + if (event.getHook() != null) { + GrapplingHook.pullPlayer(player, event.getHook().getLocation()); + } } } } @@ -164,15 +257,17 @@ public class GadgetModule implements Module, Listener { activeBalloons.remove(player.getUniqueId()); } activeEffects.remove(player.getUniqueId()); - player.sendMessage("§8[§6Nexus§8] §cAlle Gadgets wurden entfernt."); + activeShields.remove(player.getUniqueId()); + PetManager.removePet(player); + HatManager.removeHat(player); + player.getInventory().remove(Material.FISHING_ROD); + player.sendMessage("§8[§6Nexus§8] §cAlle Gadgets abgelegt."); } private void fillEdges(Inventory inv) { ItemStack glass = createItem(Material.GRAY_STAINED_GLASS_PANE, " ", null); for (int i = 0; i < inv.getSize(); i++) { - if (i < 9 || i >= inv.getSize() - 9 || i % 9 == 0 || (i + 1) % 9 == 0) { - inv.setItem(i, glass); - } + if (i < 9 || i >= inv.getSize() - 9 || i % 9 == 0 || (i + 1) % 9 == 0) inv.setItem(i, glass); } } @@ -182,7 +277,7 @@ public class GadgetModule implements Module, Listener { if (meta != null) { meta.setDisplayName(name); if (lore != null) { - java.util.List l = new java.util.ArrayList<>(); + List l = new ArrayList<>(); l.add(lore); meta.setLore(l); } @@ -193,8 +288,10 @@ public class GadgetModule implements Module, Listener { @Override public void onDisable() { + PetManager.clearAll(); activeBalloons.values().forEach(Balloon::remove); activeBalloons.clear(); activeEffects.clear(); + activeShields.clear(); } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/gadgets/GrapplingHook.java b/src/main/java/de/nexuslobby/modules/gadgets/GrapplingHook.java new file mode 100644 index 0000000..19e5ac3 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/gadgets/GrapplingHook.java @@ -0,0 +1,37 @@ +package de.nexuslobby.modules.gadgets; + +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +public class GrapplingHook { + + public static void pullPlayer(Player player, Location target) { + Location playerLoc = player.getLocation(); + + // Vektor vom Spieler zum Ziel berechnen + double distance = target.distance(playerLoc); + + // Wenn das Ziel zu nah oder zu weit weg ist, nichts tun + if (distance < 2 || distance > 50) return; + + // Berechnung des Wurfs (Vektor) + Vector v = target.toVector().subtract(playerLoc.toVector()); + + // Den Vektor normalisieren und skalieren (Stärke des Zugs) + v.multiply(0.3); // Basis-Geschwindigkeit + v.setY(v.getY() * 0.6 + 0.5); // Etwas mehr Höhe für den Bogen-Effekt + + // Geschwindigkeit begrenzen, damit man nicht aus der Map schießt + if (v.length() > 2.5) { + v.normalize().multiply(2.5); + } + + player.setVelocity(v); + + // Sound-Effekt für das "Ziehen" + player.playSound(playerLoc, Sound.ENTITY_WIND_CHARGE_WIND_BURST, 1.0f, 1.2f); + player.playSound(playerLoc, Sound.ITEM_TRIDENT_RIPTIDE_1, 0.5f, 1.5f); + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/gadgets/HatManager.java b/src/main/java/de/nexuslobby/modules/gadgets/HatManager.java new file mode 100644 index 0000000..fdebdcf --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/gadgets/HatManager.java @@ -0,0 +1,26 @@ +package de.nexuslobby.modules.gadgets; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public class HatManager { + + public static void setHat(Player player, Material material, String name) { + ItemStack hat = new ItemStack(material); + ItemMeta meta = hat.getItemMeta(); + if (meta != null) { + meta.setDisplayName("§6Hut: " + name); + hat.setItemMeta(meta); + } + + // Den Gegenstand auf den Kopf setzen + player.getInventory().setHelmet(hat); + player.sendMessage("§8[§6Nexus§8] §aDu trägst nun: " + name); + } + + public static void removeHat(Player player) { + player.getInventory().setHelmet(null); + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/gadgets/PetManager.java b/src/main/java/de/nexuslobby/modules/gadgets/PetManager.java new file mode 100644 index 0000000..b501e5d --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/gadgets/PetManager.java @@ -0,0 +1,138 @@ +package de.nexuslobby.modules.gadgets; + +import de.nexuslobby.NexusLobby; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class PetManager implements Listener { + + private static final Map activePets = new HashMap<>(); + + public PetManager() { + Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance()); + } + + /** + * Spawnt ein echtes Tier-Entity für den Spieler. + */ + public static void spawnEntityPet(Player player, String type) { + removePet(player); + + EntityType entityType; + try { + entityType = EntityType.valueOf(type); + } catch (IllegalArgumentException e) { + player.sendMessage("§cFehler: Tier-Typ nicht gefunden."); + return; + } + + Location loc = player.getLocation(); + Entity pet = player.getWorld().spawnEntity(loc, entityType); + + pet.setCustomName("§d" + player.getName() + "'s " + capitalize(type.toLowerCase())); + pet.setCustomNameVisible(true); + pet.setInvulnerable(true); + pet.setPersistent(false); + + if (pet instanceof LivingEntity) { + LivingEntity le = (LivingEntity) pet; + le.setRemoveWhenFarAway(false); + + // Verhindert, dass das Pet andere angreift + if (le instanceof Tameable) { + ((Tameable) le).setTamed(true); + ((Tameable) le).setOwner(player); + } + } + + activePets.put(player.getUniqueId(), pet); + } + + /** + * Steuert das Folgen der Tiere. Wird vom GadgetModule-Timer aufgerufen. + */ + public static void updatePets() { + for (Map.Entry entry : activePets.entrySet()) { + Player owner = Bukkit.getPlayer(entry.getKey()); + Entity pet = entry.getValue(); + + if (owner == null || !owner.isOnline() || pet.isDead()) { + continue; + } + + // Wenn das Pet in einer anderen Welt ist oder zu weit weg, teleportiere es + if (!pet.getWorld().equals(owner.getWorld()) || pet.getLocation().distance(owner.getLocation()) > 10) { + pet.teleport(owner.getLocation()); + continue; + } + + // Sanftes Folgen: Wenn das Pet weiter als 3 Blöcke weg ist + if (pet.getLocation().distance(owner.getLocation()) > 3) { + Location target = owner.getLocation().clone().add(owner.getLocation().getDirection().multiply(-1.5)); + target.setY(owner.getLocation().getY()); + + // Teleportiert das Pet leicht zum Ziel (simuliert Laufen) + pet.teleport(pet.getLocation().add(target.toVector().subtract(pet.getLocation().toVector()).normalize().multiply(0.2))); + + // Blickrichtung anpassen + Location lookAt = pet.getLocation(); + lookAt.setDirection(owner.getLocation().toVector().subtract(pet.getLocation().toVector())); + pet.teleport(lookAt); + } + } + } + + public static void removePet(Player player) { + if (activePets.containsKey(player.getUniqueId())) { + activePets.get(player.getUniqueId()).remove(); + activePets.remove(player.getUniqueId()); + } + } + + public static void clearAll() { + for (Entity pet : activePets.values()) { + pet.remove(); + } + activePets.clear(); + } + + private static String capitalize(String str) { + if (str == null || str.isEmpty()) return str; + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + + // --- Events um die Pets zu schützen --- + + @EventHandler + public void onPetDamage(EntityDamageEvent event) { + if (activePets.containsValue(event.getEntity())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onPetTarget(EntityTargetEvent event) { + if (activePets.containsValue(event.getEntity())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + removePet(event.getPlayer()); + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/gadgets/ShieldTask.java b/src/main/java/de/nexuslobby/modules/gadgets/ShieldTask.java new file mode 100644 index 0000000..b5bea1d --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/gadgets/ShieldTask.java @@ -0,0 +1,32 @@ +package de.nexuslobby.modules.gadgets; + +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +public class ShieldTask { + + public static void handleShield(Player owner) { + // Erzeuge einen Partikel-Ring um den Spieler + for (double i = 0; i < Math.PI * 2; i += Math.PI / 8) { + double x = Math.cos(i) * 2.2; + double z = Math.sin(i) * 2.2; + owner.getWorld().spawnParticle(Particle.WITCH, owner.getLocation().add(x, 0.5, z), 1, 0, 0, 0, 0); + } + + // Stoße andere Spieler weg + for (Entity entity : owner.getNearbyEntities(2.2, 2.0, 2.2)) { + if (entity instanceof Player && entity != owner) { + Player target = (Player) entity; + + Vector direction = target.getLocation().toVector().subtract(owner.getLocation().toVector()).normalize(); + direction.multiply(0.4).setY(0.2); + + target.setVelocity(direction); + target.playSound(target.getLocation(), Sound.ENTITY_CHICKEN_EGG, 0.5f, 0.5f); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/hologram/HoloCommand.java b/src/main/java/de/nexuslobby/modules/hologram/HoloCommand.java new file mode 100644 index 0000000..4d11c1d --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/hologram/HoloCommand.java @@ -0,0 +1,85 @@ +package de.nexuslobby.commands; + +import de.nexuslobby.modules.hologram.HologramModule; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class HoloCommand implements CommandExecutor { + + private final HologramModule module; + + public HoloCommand(HologramModule module) { + this.module = module; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) return true; + + if (!player.hasPermission("nexuslobby.hologram")) { + player.sendMessage("§8[§6Nexus§8] §cKeine Rechte."); + return true; + } + + if (args.length < 1) { + sendHelp(player); + return true; + } + + if (args[0].equalsIgnoreCase("create")) { + // Wir brauchen id, typ und mindestens ein Wort Text + if (args.length < 4) { + player.sendMessage("§cNutze: /holo create "); + return true; + } + + String id = args[1]; + // Der Typ (args[2]) wird im neuen System ignoriert, da wir Seiten nutzen + + StringBuilder sb = new StringBuilder(); + for (int i = 3; i < args.length; i++) { + sb.append(args[i]).append(i == args.length - 1 ? "" : " "); + } + + String fullText = sb.toString().trim(); + List pages = new ArrayList<>(); + + // Support für mehrere Seiten via ";" + if (fullText.contains(";")) { + pages.addAll(Arrays.asList(fullText.split(";"))); + } else { + pages.add(fullText); + } + + // Aufruf der neuen Methode mit List + module.createHologram(id, player.getLocation(), pages); + player.sendMessage("§8[§6Nexus§8] §aHologramm §e" + id + " §aerstellt (" + pages.size() + " Seiten)."); + } + else if (args[0].equalsIgnoreCase("delete")) { + if (args.length < 2) { + player.sendMessage("§cBitte gib eine ID an: /holo delete "); + return true; + } + module.removeHologram(args[1]); + player.sendMessage("§8[§6Nexus§8] §cHologramm §e" + args[1] + " §ageloescht."); + } else { + sendHelp(player); + } + + return true; + } + + private void sendHelp(Player player) { + player.sendMessage("§8§m-----------§r §6Hologramme §8§m-----------"); + player.sendMessage("§e/holo create "); + player.sendMessage("§e/holo delete "); + player.sendMessage("§7Nutze §b; §7für neue Seiten."); + player.sendMessage("§7Nutze §b\\n §7für Zeilenumbruch."); + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/hologram/HologramModule.java b/src/main/java/de/nexuslobby/modules/hologram/HologramModule.java new file mode 100644 index 0000000..3fed6e5 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/hologram/HologramModule.java @@ -0,0 +1,134 @@ +package de.nexuslobby.modules.hologram; + +import de.nexuslobby.NexusLobby; +import de.nexuslobby.api.Module; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class HologramModule implements Module, Listener { + + private final Map holograms = new ConcurrentHashMap<>(); + private File file; + private FileConfiguration config; + + @Override + public String getName() { return "Holograms"; } + + @Override + public void onEnable() { + loadConfig(); + loadHolograms(); + Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance()); + + Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { + for (Player player : Bukkit.getOnlinePlayers()) { + holograms.values().forEach(h -> h.renderForPlayer(player)); + } + }, 20L, 5L); + } + + private void loadConfig() { + file = new File(NexusLobby.getInstance().getDataFolder(), "holograms.yml"); + config = YamlConfiguration.loadConfiguration(file); + } + + private void loadHolograms() { + holograms.values().forEach(NexusHologram::removeAll); + holograms.clear(); + for (String id : config.getKeys(false)) { + World world = Bukkit.getWorld(config.getString(id + ".world", "world")); + if (world == null) continue; + Location loc = new Location(world, config.getDouble(id + ".x"), config.getDouble(id + ".y"), config.getDouble(id + ".z")); + + List pages; + if (config.isList(id + ".text")) { + pages = config.getStringList(id + ".text"); + } else { + pages = new ArrayList<>(); + pages.add(config.getString(id + ".text", "No Text")); + } + + holograms.put(id, new NexusHologram(id, loc, pages)); + } + } + + @EventHandler + public void onInteract(PlayerInteractEntityEvent event) { + // Wir prüfen, ob auf ein Interaction-Entity geklickt wurde + for (NexusHologram holo : holograms.values()) { + if (holo.isInteractionEntity(event.getRightClicked().getUniqueId())) { + holo.nextPage(event.getPlayer()); + break; + } + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + holograms.values().forEach(h -> h.removeForPlayer(event.getPlayer())); + } + + @EventHandler + public void onWorldChange(PlayerChangedWorldEvent event) { + holograms.values().forEach(h -> h.removeForPlayer(event.getPlayer())); + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + // Cleanup alter Entities für den Joiner + Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> { + event.getPlayer().getWorld().getEntities().forEach(entity -> { + if (entity.getCustomName() != null && entity.getCustomName().startsWith("nexus_h_")) { + if (!entity.getCustomName().endsWith("_" + event.getPlayer().getName())) { + event.getPlayer().hideEntity(NexusLobby.getInstance(), entity); + } + } + }); + }, 5L); + } + + public void createHologram(String id, Location loc, List pages) { + config.set(id + ".world", loc.getWorld().getName()); + config.set(id + ".x", loc.getX()); + config.set(id + ".y", loc.getY()); + config.set(id + ".z", loc.getZ()); + config.set(id + ".text", pages); + try { config.save(file); } catch (IOException e) { e.printStackTrace(); } + + NexusHologram holo = new NexusHologram(id, loc, pages); + holograms.put(id, holo); + } + + public void removeHologram(String id) { + NexusHologram holo = holograms.remove(id); + if (holo != null) holo.removeAll(); + config.set(id, null); + try { config.save(file); } catch (IOException e) { e.printStackTrace(); } + } + + public Set getHologramIds() { return config.getKeys(false); } + + @Override + public void onDisable() { + holograms.values().forEach(NexusHologram::removeAll); + holograms.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/hologram/NexusHologram.java b/src/main/java/de/nexuslobby/modules/hologram/NexusHologram.java new file mode 100644 index 0000000..00392aa --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/hologram/NexusHologram.java @@ -0,0 +1,115 @@ +package de.nexuslobby.modules.hologram; + +import de.nexuslobby.NexusLobby; +import me.clip.placeholderapi.PlaceholderAPI; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.entity.Display; +import org.bukkit.entity.Interaction; +import org.bukkit.entity.Player; +import org.bukkit.entity.TextDisplay; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class NexusHologram { + + private final String id; + private final Location location; + private final List pages; + + private final Map playerEntities = new ConcurrentHashMap<>(); + private final Map playerInteractions = new ConcurrentHashMap<>(); + private final Map currentPage = new ConcurrentHashMap<>(); + + public NexusHologram(String id, Location location, List pages) { + this.id = id; + this.location = location; + this.pages = pages; + } + + public void nextPage(Player player) { + if (pages.size() <= 1) return; + int next = (currentPage.getOrDefault(player.getUniqueId(), 0) + 1) % pages.size(); + currentPage.put(player.getUniqueId(), next); + renderForPlayer(player); + } + + public void renderForPlayer(Player player) { + if (!player.getWorld().equals(location.getWorld()) || player.getLocation().distanceSquared(location) > 2304) { + removeForPlayer(player); + return; + } + + int pageIdx = currentPage.getOrDefault(player.getUniqueId(), 0); + String rawText = pages.get(pageIdx); + + if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { + rawText = PlaceholderAPI.setPlaceholders(player, rawText); + } + final String finalText = rawText.replace("&", "§").replace("\\n", "\n"); + + TextDisplay display = playerEntities.get(player.getUniqueId()); + + if (display == null || !display.isValid()) { + // Text erstellen + display = location.getWorld().spawn(location, TextDisplay.class, entity -> { + entity.setCustomName("nexus_h_" + id + "_" + player.getName()); + entity.setCustomNameVisible(false); + entity.setPersistent(false); + entity.setBillboard(Display.Billboard.CENTER); + entity.setBackgroundColor(Color.fromARGB(0, 0, 0, 0)); + entity.setText(finalText); + entity.setInvulnerable(true); + }); + + // Interaction Entity für Klick-Erkennung (Hitbox) + Interaction interact = location.getWorld().spawn(location, Interaction.class, entity -> { + entity.setInteractionWidth(2.0f); + entity.setInteractionHeight(2.0f); + entity.setPersistent(false); + }); + + final TextDisplay finalDisplay = display; + final Interaction finalInteract = interact; + + for (Player other : Bukkit.getOnlinePlayers()) { + if (!other.equals(player)) { + other.hideEntity(NexusLobby.getInstance(), finalDisplay); + other.hideEntity(NexusLobby.getInstance(), finalInteract); + } + } + + playerEntities.put(player.getUniqueId(), display); + playerInteractions.put(player.getUniqueId(), interact); + } else { + if (!display.getText().equals(finalText)) { + display.setText(finalText); + } + } + } + + public void removeForPlayer(Player player) { + TextDisplay display = playerEntities.remove(player.getUniqueId()); + if (display != null) display.remove(); + + Interaction interact = playerInteractions.remove(player.getUniqueId()); + if (interact != null) interact.remove(); + } + + public void removeAll() { + playerEntities.values().forEach(TextDisplay::remove); + playerInteractions.values().forEach(Interaction::remove); + playerEntities.clear(); + playerInteractions.clear(); + } + + public boolean isInteractionEntity(UUID entityId) { + return playerInteractions.values().stream().anyMatch(i -> i.getUniqueId().equals(entityId)); + } + + public String getId() { return id; } +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index efb5f3a..5dddf1e 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.2" +version: "1.0.3" api-version: "1.21" author: M_Viper description: Modular Lobby Plugin @@ -12,49 +12,45 @@ commands: usage: /portal permission: nexuslobby.portal permission-message: "§cKeine Rechte!" - giveportalwand: description: Gibt das Portal-Werkzeug usage: /giveportalwand permission: nexuslobby.portal.give permission-message: "§cDu hast keine Berechtigung dafür." - maintenance: description: Aktiviert oder deaktiviert den Wartungsmodus usage: /maintenance permission: nexuslobby.maintenance permission-message: "§cDu hast keine Rechte!" - serverswitcher: description: Öffnet die Server Switcher GUI usage: /serverswitcher permission: nexuslobby.serverswitcher permission-message: "§cDu hast keine Rechte!" - settings: description: Öffnet das Lobby-Einstellungsmenü (Gamerules) usage: /settings permission: nexuslobby.admin permission-message: "§cDu hast keine Rechte für die Admin-Einstellungen!" - build: description: Aktiviert oder deaktiviert den Baumodus usage: /build permission: nexuslobby.build permission-message: "§cDu hast keine Rechte!" - nexuslobby: description: Zeigt Informationen über das Plugin an oder lädt es neu usage: /nexuslobby [reload] aliases: [nexus] - - # --- ArmorStandTools Sektion --- nexustools: description: Nexus ArmorStand Editor aliases: [nt, ntools, astools] nexuscmd: description: Nexus Command Binder aliases: [ncmd, ascmd] + holo: + description: Verwalte Lobby Hologramme (Text-Displays) + usage: /holo [text] + permission: nexuslobby.hologram permissions: nexuslobby.portal: @@ -80,4 +76,10 @@ permissions: default: op nexuslobby.armorstand.cmd: description: Erlaubt das Binden von Commands via NexusCmd + default: op + nexuslobby.armorstand.dynamic: + description: Erlaubt das Markieren von dynamischen NPCs + default: op + nexuslobby.hologram: + description: Erlaubt das Erstellen von Text-Display Hologrammen default: op \ No newline at end of file