package dev.loki.objective; import dev.loki.mdbd; import dev.loki.util.TextUtil; import org.bukkit.Location; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Display; import org.bukkit.entity.EntityType; import org.bukkit.entity.TextDisplay; import org.bukkit.scheduler.BukkitTask; import java.util.HashMap; import java.util.Map; /** * Floating labels above generators (name + repair progress). * Uses TextDisplay (Paper 1.19.4+) with ArmorStand fallback for older versions. */ public class GeneratorHologram { private final mdbd plugin; private final GeneratorManager manager; private final Map displays = new HashMap<>(); // TextDisplay or ArmorStand private BukkitTask task; private boolean useTextDisplay; public GeneratorHologram(mdbd plugin, GeneratorManager manager) { this.plugin = plugin; this.manager = manager; this.useTextDisplay = isTextDisplayAvailable(); } private static boolean isTextDisplayAvailable() { try { EntityType.valueOf("TEXT_DISPLAY"); return true; } catch (IllegalArgumentException e) { return false; } } public void start() { stop(); spawnAll(); task = plugin.getServer().getScheduler().runTaskTimer(plugin, this::refresh, 20L, 20L); plugin.getLogger().info("[GeneratorHologram] Started with " + displays.size() + " holograms (mode=" + (useTextDisplay ? "TextDisplay" : "ArmorStand") + ")"); } public void stop() { if (task != null) { task.cancel(); task = null; } for (Object display : displays.values()) { if (display instanceof TextDisplay td && td.isValid()) { td.remove(); } else if (display instanceof ArmorStand as && as.isValid()) { as.remove(); } } displays.clear(); } private void spawnAll() { double heightOffset = plugin.getConfigManager().getGame().getGeneratorHologramHeightOffset(); for (Generator gen : manager.getAllGenerators()) { Location loc = gen.getLocation(); if (loc.getWorld() == null) continue; Location holoLoc = loc.clone().add(0.5, 2.0 + heightOffset, 0.5); try { if (useTextDisplay) { TextDisplay display = loc.getWorld().spawn(holoLoc, TextDisplay.class, d -> { d.setBillboard(Display.Billboard.VERTICAL); d.setSeeThrough(false); d.setShadowed(true); d.setDefaultBackground(false); d.setPersistent(false); d.setTextOpacity((byte) -1); }); displays.put(gen.getId(), display); } else { ArmorStand stand = loc.getWorld().spawn(holoLoc, ArmorStand.class, as -> { as.setInvisible(true); as.setInvulnerable(true); as.setGravity(false); as.setMarker(true); as.setCustomNameVisible(true); as.setPersistent(false); }); displays.put(gen.getId(), stand); } } catch (Exception e) { plugin.getLogger().warning("[GeneratorHologram] Failed to spawn hologram for generator " + gen.getId() + ": " + e.getMessage()); } } } private void refresh() { double heightOffset = plugin.getConfigManager().getGame().getGeneratorHologramHeightOffset(); int total = manager.getTotalCount(); for (Generator gen : manager.getAllGenerators()) { Object display = displays.get(gen.getId()); if (display == null) continue; boolean valid = (display instanceof TextDisplay td && td.isValid()) || (display instanceof ArmorStand as && as.isValid()); if (!valid) continue; int pct = (int) gen.getProgress(); String bar = buildBar(pct); String text; if (gen.isCompleted()) { text = "§6⚙ Генератор §f#" + (gen.getId() + 1) + "/" + total + "\n§a✔ Починен!"; } else if (gen.isUnderRegression()) { text = "§6⚙ Генератор §f#" + (gen.getId() + 1) + "/" + total + "\n§c↓ " + bar + " §f" + pct + "%"; } else { text = "§6⚙ Генератор §f#" + (gen.getId() + 1) + "/" + total + "\n§e" + bar + " §f" + pct + "%"; } Location base = gen.getLocation().clone().add(0.5, 2.0 + heightOffset, 0.5); if (display instanceof TextDisplay td) { td.text(TextUtil.color(text)); td.teleport(base); } else if (display instanceof ArmorStand as) { as.setCustomName(text); as.teleport(base); } } } private String buildBar(int pct) { int filled = pct / 10; int empty = 10 - filled; return "§e" + "█".repeat(Math.max(0, filled)) + "§7" + "░".repeat(Math.max(0, empty)); } }