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.Entity; import org.bukkit.entity.Interaction; import org.bukkit.entity.Player; import org.bukkit.entity.TextDisplay; 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()); // Render-Task: Prüft alle 5 Ticks Sichtbarkeit und Placeholder-Updates 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() { // Vorherige Instanzen säubern holograms.values().forEach(NexusHologram::removeAll); holograms.clear(); for (String id : config.getKeys(false)) { String worldName = config.getString(id + ".world"); if (worldName == null) continue; World world = Bukkit.getWorld(worldName); 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) { // Nur auf Interaction-Entities reagieren (Hologramm-Hitboxen) if (!(event.getRightClicked() instanceof Interaction)) return; 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 Entity-Reste, die eventuell noch in der Welt schweben Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> { Player p = event.getPlayer(); for (Entity entity : p.getWorld().getEntities()) { if (entity.getCustomName() != null && entity.getCustomName().startsWith("nexus_h_")) { // Wenn das Hologramm nicht exakt für diesen Spieler benannt ist -> verstecken if (!entity.getCustomName().endsWith("_" + p.getName())) { p.hideEntity(NexusLobby.getInstance(), entity); } } } }, 10L); } public void createHologram(String id, Location loc, List pages) { // Falls ID bereits existiert, altes Hologramm sauber entfernen if (holograms.containsKey(id)) { removeHologram(id); } 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); saveHoloConfig(); NexusHologram holo = new NexusHologram(id, loc, pages); holograms.put(id, holo); } public void removeHologram(String id) { NexusHologram holo = holograms.remove(id); if (holo != null) { // Erst für alle Spieler visuell entfernen for (Player player : Bukkit.getOnlinePlayers()) { holo.removeForPlayer(player); } // Dann Entities serverseitig löschen holo.removeAll(); } config.set(id, null); saveHoloConfig(); } /** * Lädt die holograms.yml neu und rendert alle Hologramme für Online-Spieler neu. * Wird von HoloCommand (/holo reload) aufgerufen. */ public void reloadHolograms() { // Alle aktiven Hologramme für alle Spieler entfernen for (Player player : Bukkit.getOnlinePlayers()) { holograms.values().forEach(h -> h.removeForPlayer(player)); } // Config neu einlesen und Hologramme neu laden loadConfig(); loadHolograms(); } private void saveHoloConfig() { try { // Sicherstellen, dass Placeholder-Strings (z.B. %bungeetotal%) korrekt // gequotet gespeichert werden, damit SnakeYAML sie nicht als YAML-Direktive // missinterpretiert. Dazu speichern wir die text-Listen explizit als // String-Werte mit einfachen Anführungszeichen via eigener Serialisierung. StringBuilder yaml = new StringBuilder(); for (String id : config.getKeys(false)) { yaml.append(id).append(":\n"); yaml.append(" world: ").append(quoteIfNeeded(config.getString(id + ".world", ""))).append("\n"); yaml.append(" x: ").append(config.getDouble(id + ".x")).append("\n"); yaml.append(" y: ").append(config.getDouble(id + ".y")).append("\n"); yaml.append(" z: ").append(config.getDouble(id + ".z")).append("\n"); yaml.append(" text:\n"); for (String line : config.getStringList(id + ".text")) { yaml.append(" - ").append(quoteIfNeeded(line)).append("\n"); } } java.nio.file.Files.writeString(file.toPath(), yaml.toString()); } catch (IOException e) { NexusLobby.getInstance().getLogger().severe("Konnte holograms.yml nicht speichern!"); NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der Hologramme: " + e.getMessage()); } } /** * Umschließt Strings mit einfachen Anführungszeichen, wenn sie YAML-Sonderzeichen * wie '%', ':', '#' enthalten, um Parsing-Fehler zu vermeiden. */ private String quoteIfNeeded(String value) { if (value == null) return "''"; if (value.contains("%") || value.contains(":") || value.contains("#") || value.contains("'")) { return "'" + value.replace("'", "''") + "'"; } return value; } /** * WICHTIG: Diese Methode wird vom LobbyTabCompleter benötigt! * @return Set aller registrierten Hologramm-IDs */ public Set getHologramIds() { return holograms.keySet(); } @Override public void onDisable() { org.bukkit.event.HandlerList.unregisterAll(this); holograms.values().forEach(NexusHologram::removeAll); holograms.clear(); } }