diff --git a/src/main/java/viper/ButtonControl.java b/src/main/java/viper/ButtonControl.java index 127c576..9d297c2 100644 --- a/src/main/java/viper/ButtonControl.java +++ b/src/main/java/viper/ButtonControl.java @@ -17,129 +17,108 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.Note; import org.bukkit.Note.Tone; -import org.bukkit.event.Listener; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerJoinEvent; -import java.util.ArrayList; -import java.util.List; -import java.util.List; -import java.util.UUID; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; public class ButtonControl extends JavaPlugin { private ConfigManager configManager; private DataManager dataManager; - private Map lastMotionDetections = new HashMap<>(); + + // Bewegungsmelder-State + private final Map lastMotionDetections = new HashMap<>(); + private final Set activeSensors = new HashSet<>(); + + // Zeitgesteuerte Automation – verhindert mehrfaches Auslösen pro Zustandswechsel + private final Map timedControllerLastState = new HashMap<>(); @Override public void onEnable() { - // Initialisierung der Manager configManager = new ConfigManager(this); - dataManager = new DataManager(this); + dataManager = new DataManager(this); - // Spigot Update Checker beim Serverstart ausführen - new UpdateChecker(this, 127702).getVersion(version -> { - String currentVersion = this.getDescription().getVersion(); - String normalizedLatest = version.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim(); - String normalizedCurrent = currentVersion.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim(); + // Update-Checker beim Start + new UpdateChecker(this, 127702).getVersion(version -> { + String current = getDescription().getVersion(); + if (isNewerVersion(strip(version), strip(current))) { + getLogger().info("Update verfügbar: v" + version); + Bukkit.getScheduler().runTask(this, () -> + Bukkit.getOnlinePlayers().stream() + .filter(p -> p.hasPermission("buttoncontrol.update")) + .forEach(p -> sendUpdateMessage(p, current, version))); + } else { + getLogger().info("ButtonControl ist auf dem neuesten Stand (v" + current + ")."); + } + }); - if (isNewerVersion(normalizedLatest, normalizedCurrent)) { - // Konsole bleibt sachlich - getLogger().info("Update verfügbar: v" + version); + // Update beim Joinen + getServer().getPluginManager().registerEvents(new org.bukkit.event.Listener() { + @org.bukkit.event.EventHandler + public void onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent event) { + Player player = event.getPlayer(); + if (!player.hasPermission("buttoncontrol.update")) return; + new UpdateChecker(ButtonControl.this, 127702).getVersion(version -> { + String current = getDescription().getVersion(); + if (isNewerVersion(strip(version), strip(current))) + sendUpdateMessage(player, current, version); + }); + } + }, this); - // Schicke die stylische Nachricht an Admins - Bukkit.getScheduler().runTask(this, () -> { - Bukkit.getOnlinePlayers().stream() - .filter(p -> p.hasPermission("buttoncontrol.update")) - .forEach(p -> { - p.sendMessage(""); // Leerzeile für Abstand - p.sendMessage("§8§m-----------------------------------------"); - p.sendMessage(" §6§lButtonControl §7- §e§lUpdate verfügbar!"); - p.sendMessage(""); - p.sendMessage(" §7Aktuelle Version: §c" + currentVersion); - p.sendMessage(" §7Neue Version: §a" + version); - p.sendMessage(""); - p.sendMessage(" §eDownload hier:"); - p.sendMessage(" §bhttps://www.spigotmc.org/resources/127702/"); - p.sendMessage("§8§m-----------------------------------------"); - p.sendMessage(""); - }); - }); - } else { - getLogger().info("ButtonControl ist auf dem neuesten Stand (v" + currentVersion + ")."); - } - }); - - // Listener für Spieler-Joins (Update-Benachrichtigung beim Betreten des Servers) - getServer().getPluginManager().registerEvents(new org.bukkit.event.Listener() { - @org.bukkit.event.EventHandler - public void onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent event) { - Player player = event.getPlayer(); - if (!player.hasPermission("buttoncontrol.update")) return; - - new UpdateChecker(ButtonControl.this, 127702).getVersion(version -> { - String currentVersion = getDescription().getVersion(); - String normalizedLatest = version.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim(); - String normalizedCurrent = currentVersion.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim(); - - if (isNewerVersion(normalizedLatest, normalizedCurrent)) { - // Stylische Box für den Spieler beim Joinen - player.sendMessage(""); - player.sendMessage("§8§m-----------------------------------------"); - player.sendMessage(" §6§lButtonControl §7- §e§lUpdate verfügbar!"); - player.sendMessage(""); - player.sendMessage(" §7Aktuelle Version: §c" + currentVersion); - player.sendMessage(" §7Neue Version: §a" + version); - player.sendMessage(""); - player.sendMessage(" §eDownload hier:"); - player.sendMessage(" §bhttps://www.spigotmc.org/resources/127702/"); - player.sendMessage("§8§m-----------------------------------------"); - player.sendMessage(""); - } - }); - } - }, this); - - // Konfiguration und Spielmechaniken laden - updateConfigWithDefaults(); - - // --- COMMANDS & TAB-COMPLETER --- - // Registriert den Executor (Befehlsverarbeitung) und den TabCompleter (Vorschläge) if (getCommand("bc") != null) { - getCommand("bc").setExecutor(this); // 'this' setzt voraus, dass ButtonControl 'onCommand' enthält + getCommand("bc").setExecutor(this); getCommand("bc").setTabCompleter(new ButtonTabCompleter()); } - // Event-Listener registrieren - getServer().getPluginManager().registerEvents(new ButtonListener(this, configManager, dataManager), this); - - // Rezepte und bStats/Metrics + getServer().getPluginManager().registerEvents( + new ButtonListener(this, configManager, dataManager), this); + registerRecipes(); MetricsHandler.startMetrics(this); - - // --- AUTOMATISIERUNG-TIMER --- - // Daylight Sensoren: Prüfung alle 200 Ticks (10 Sekunden) - getServer().getScheduler().runTaskTimer(this, this::checkDaylightSensors, 0L, 20L * 10); - - // Bewegungsmelder (Motion Sensors): Prüfung alle 10 Ticks (0.5 Sekunden) für flüssige Erkennung - getServer().getScheduler().runTaskTimer(this, this::checkMotionSensors, 0L, 10L); + + getServer().getScheduler().runTaskTimer(this, this::checkDaylightSensors, 0L, 20L * 10); + getServer().getScheduler().runTaskTimer(this, this::checkMotionSensors, 0L, 10L); + getServer().getScheduler().runTaskTimer(this, this::checkTimedControllers, 0L, 20L * 5); getLogger().info("ButtonControl v" + getDescription().getVersion() + " wurde erfolgreich aktiviert!"); } + // ----------------------------------------------------------------------- + // Update-Hilfe + // ----------------------------------------------------------------------- + + private String strip(String v) { + return v.replaceFirst("(?i)^(version\\s*|v\\.?\\s*)", "").trim(); + } + + private void sendUpdateMessage(Player player, String current, String latest) { + player.sendMessage(""); + player.sendMessage("§8§m-----------------------------------------"); + player.sendMessage(" §6§lButtonControl §7- §e§lUpdate verfügbar!"); + player.sendMessage(""); + player.sendMessage(" §7Aktuelle Version: §c" + current); + player.sendMessage(" §7Neue Version: §a" + latest); + player.sendMessage(""); + player.sendMessage(" §eDownload: §bhttps://www.spigotmc.org/resources/127702/"); + player.sendMessage("§8§m-----------------------------------------"); + player.sendMessage(""); + } + private boolean isNewerVersion(String latest, String current) { try { - String[] latestParts = latest.split("\\."); - String[] currentParts = current.split("\\."); - int length = Math.max(latestParts.length, currentParts.length); - - for (int i = 0; i < length; i++) { - int latestPart = (i < latestParts.length) ? Integer.parseInt(latestParts[i]) : 0; - int currentPart = (i < currentParts.length) ? Integer.parseInt(currentParts[i]) : 0; - if (latestPart > currentPart) return true; - if (latestPart < currentPart) return false; + String[] lp = latest.split("\\."); + String[] cp = current.split("\\."); + int len = Math.max(lp.length, cp.length); + for (int i = 0; i < len; i++) { + int l = i < lp.length ? Integer.parseInt(lp[i]) : 0; + int c = i < cp.length ? Integer.parseInt(cp[i]) : 0; + if (l > c) return true; + if (l < c) return false; } return false; } catch (NumberFormatException e) { @@ -147,262 +126,267 @@ public class ButtonControl extends JavaPlugin { } } - private void updateConfigWithDefaults() { - if (!configManager.getConfig().contains("default-note")) { - configManager.getConfig().set("default-note", "PIANO"); - } - if (!configManager.getConfig().contains("double-note-enabled")) { - configManager.getConfig().set("double-note-enabled", true); - } - if (!configManager.getConfig().contains("double-note-delay-ms")) { - configManager.getConfig().set("double-note-delay-ms", 1000); - } - if (!configManager.getConfig().contains("motion-detection-radius")) { - configManager.getConfig().set("motion-detection-radius", 5.0); - } - if (!configManager.getConfig().contains("motion-close-delay-ms")) { - configManager.getConfig().set("motion-close-delay-ms", 5000); - } - configManager.saveConfig(); - } + // ----------------------------------------------------------------------- + // Rezepte + // ----------------------------------------------------------------------- private void registerRecipes() { - // --- 1. Dynamische Steuer-Buttons für JEDE Holz/Stein-Art --- - for (Material mat : Material.values()) { - if (mat.name().endsWith("_BUTTON")) { - // Wir erstellen für jeden Button-Typ ein eigenes Ergebnis - ItemStack controlButton = new ItemStack(mat); - ItemMeta buttonMeta = controlButton.getItemMeta(); - if (buttonMeta != null) { - buttonMeta.setDisplayName("§6Steuer-Button"); - List lore = new ArrayList<>(); - lore.add("§7Ein universeller Controller für"); - lore.add("§7Türen, Lampen und mehr."); - buttonMeta.setLore(lore); - controlButton.setItemMeta(buttonMeta); - } + // Alle Holz/Stein-Buttons + for (Material mat : Material.values()) { + if (!mat.name().endsWith("_BUTTON")) continue; + registerColumnRecipe( + "control_" + mat.name().toLowerCase(), mat, + "§6Steuer-" + friendlyName(mat, "_BUTTON"), + Arrays.asList("§7Ein universeller Controller.", + "§7Verbindet Türen, Lampen und mehr.") + ); + } - // Wir brauchen für jedes Material einen eindeutigen Key (z.B. control_button_oak_button) - NamespacedKey key = new NamespacedKey(this, "control_" + mat.name().toLowerCase()); - if (Bukkit.getRecipe(key) != null) Bukkit.removeRecipe(key); + // Tageslichtsensor + registerColumnRecipe("control_daylight", Material.DAYLIGHT_DETECTOR, + "§6Steuer-Tageslichtsensor", + Arrays.asList("§7Öffnet/schließt nach Tageszeit.")); - ShapedRecipe recipe = new ShapedRecipe(key, controlButton); - recipe.shape("123", "456", "789"); - // Das Material muss an allen drei Stellen gleich sein (z.B. 3x Eiche) - recipe.setIngredient('2', mat); - recipe.setIngredient('5', mat); - recipe.setIngredient('8', mat); - Bukkit.addRecipe(recipe); + // Notenblock + registerColumnRecipe("control_noteblock", Material.NOTE_BLOCK, + "§6Steuer-Notenblock", + Arrays.asList("§7Spielt einen Klingelton ab.")); + + // Bewegungsmelder + registerColumnRecipe("control_motion", Material.TRIPWIRE_HOOK, + "§6Steuer-Bewegungsmelder", + Arrays.asList("§7Erkennt Spieler und Mobs in der Nähe.")); + + // NEU: Schild-Controller + registerColumnRecipe("control_sign", Material.OAK_SIGN, + "§6Steuer-Schild", + Arrays.asList("§7Wandmontierbarer Controller.", + "§7Funktioniert wie ein Button.")); + + // NEU: Teppich-Sensoren (alle 16 Farben) – NUR Spieler + for (Material mat : Material.values()) { + if (!mat.name().endsWith("_CARPET")) continue; + registerColumnRecipe( + "control_carpet_" + mat.name().toLowerCase(), mat, + "§6Steuer-Teppich §8(" + friendlyName(mat, "_CARPET") + "§8)", + Arrays.asList("§7Erkennt NUR Spieler (keine Mobs).", + "§7Bodenbasierter Bewegungsmelder.") + ); } } - // --- 2. Steuer-Tageslichtsensor Rezept --- - ItemStack controlDaylight = new ItemStack(Material.DAYLIGHT_DETECTOR); - ItemMeta daylightMeta = controlDaylight.getItemMeta(); - if (daylightMeta != null) { - daylightMeta.setDisplayName("§6Steuer-Tageslichtsensor"); - controlDaylight.setItemMeta(daylightMeta); + private void registerColumnRecipe(String keyName, Material mat, String displayName, List lore) { + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta.setDisplayName(displayName); + meta.setLore(new ArrayList<>(lore)); + item.setItemMeta(meta); + } + NamespacedKey key = new NamespacedKey(this, keyName); + if (Bukkit.getRecipe(key) != null) Bukkit.removeRecipe(key); + ShapedRecipe recipe = new ShapedRecipe(key, item); + recipe.shape(" X ", " X ", " X "); + recipe.setIngredient('X', mat); + Bukkit.addRecipe(recipe); } - NamespacedKey daylightKey = new NamespacedKey(this, "control_daylight"); - if (Bukkit.getRecipe(daylightKey) != null) Bukkit.removeRecipe(daylightKey); - ShapedRecipe daylightRecipe = new ShapedRecipe(daylightKey, controlDaylight); - daylightRecipe.shape("123", "456", "789"); - daylightRecipe.setIngredient('2', Material.DAYLIGHT_DETECTOR); - daylightRecipe.setIngredient('5', Material.DAYLIGHT_DETECTOR); - daylightRecipe.setIngredient('8', Material.DAYLIGHT_DETECTOR); - Bukkit.addRecipe(daylightRecipe); - // --- 3. Steuer-Notenblock Rezept --- - ItemStack controlNoteBlock = new ItemStack(Material.NOTE_BLOCK); - ItemMeta noteBlockMeta = controlNoteBlock.getItemMeta(); - if (noteBlockMeta != null) { - noteBlockMeta.setDisplayName("§6Steuer-Notenblock"); - controlNoteBlock.setItemMeta(noteBlockMeta); + /** IRON_DOOR → "§7Iron Door" | OAK_BUTTON → "§7Oak Button" */ + String friendlyName(Material mat, String stripSuffix) { + String[] parts = mat.name().replace(stripSuffix, "").split("_"); + StringBuilder sb = new StringBuilder("§7"); + for (String p : parts) { + if (sb.length() > 2) sb.append(" "); + sb.append(Character.toUpperCase(p.charAt(0))) + .append(p.substring(1).toLowerCase()); + } + return sb.toString(); } - NamespacedKey noteBlockKey = new NamespacedKey(this, "control_noteblock"); - if (Bukkit.getRecipe(noteBlockKey) != null) Bukkit.removeRecipe(noteBlockKey); - ShapedRecipe noteBlockRecipe = new ShapedRecipe(noteBlockKey, controlNoteBlock); - noteBlockRecipe.shape("123", "456", "789"); - noteBlockRecipe.setIngredient('2', Material.NOTE_BLOCK); - noteBlockRecipe.setIngredient('5', Material.NOTE_BLOCK); - noteBlockRecipe.setIngredient('8', Material.NOTE_BLOCK); - Bukkit.addRecipe(noteBlockRecipe); - // --- 4. Steuer-Bewegungsmelder Rezept --- - ItemStack controlMotion = new ItemStack(Material.TRIPWIRE_HOOK); - ItemMeta motionMeta = controlMotion.getItemMeta(); - if (motionMeta != null) { - motionMeta.setDisplayName("§6Steuer-Bewegungsmelder"); - controlMotion.setItemMeta(motionMeta); - } - NamespacedKey motionKey = new NamespacedKey(this, "control_motion"); - if (Bukkit.getRecipe(motionKey) != null) Bukkit.removeRecipe(motionKey); - ShapedRecipe motionRecipe = new ShapedRecipe(motionKey, controlMotion); - motionRecipe.shape("123", "456", "789"); - motionRecipe.setIngredient('2', Material.TRIPWIRE_HOOK); - motionRecipe.setIngredient('5', Material.TRIPWIRE_HOOK); - motionRecipe.setIngredient('8', Material.TRIPWIRE_HOOK); - Bukkit.addRecipe(motionRecipe); -} + // ----------------------------------------------------------------------- + // Tageslichtsensor + // ----------------------------------------------------------------------- public void checkDaylightSensors() { - List allControllers = dataManager.getAllPlacedControllers(); - for (String controllerLoc : allControllers) { - String buttonId = dataManager.getButtonIdForPlacedController(controllerLoc); + for (String loc : dataManager.getAllPlacedControllers()) { + String buttonId = dataManager.getButtonIdForPlacedController(loc); if (buttonId == null) continue; + Location location = parseLocation(loc); + if (location == null) continue; + if (location.getBlock().getType() != Material.DAYLIGHT_DETECTOR) continue; - Location loc = parseLocation(controllerLoc); - if (loc == null) continue; - - Block block = loc.getBlock(); - if (block.getType() != Material.DAYLIGHT_DETECTOR) continue; - - long time = loc.getWorld().getTime(); + long time = location.getWorld().getTime(); boolean isDay = time >= 0 && time < 13000; + List connected = dataManager.getConnectedBlocks(buttonId); + if (connected == null) continue; - List connectedBlocks = dataManager.getConnectedBlocks(buttonId); - if (connectedBlocks == null) continue; - - for (String targetLocStr : connectedBlocks) { - Location targetLoc = parseLocation(targetLocStr); - if (targetLoc == null) continue; - - Block targetBlock = targetLoc.getBlock(); - if (targetBlock.getType() == Material.REDSTONE_LAMP) { - Lightable lamp = (Lightable) targetBlock.getBlockData(); + for (String ts : connected) { + Location tl = parseLocation(ts); + if (tl == null) continue; + Block tb = tl.getBlock(); + if (tb.getType() == Material.REDSTONE_LAMP) { + Lightable lamp = (Lightable) tb.getBlockData(); lamp.setLit(!isDay); - targetBlock.setBlockData(lamp); + tb.setBlockData(lamp); } } } } - public void checkMotionSensors() { - long now = System.currentTimeMillis(); - List allControllers = dataManager.getAllPlacedControllers(); - for (String controllerLoc : allControllers) { + // ----------------------------------------------------------------------- + // Zeitgesteuerte Automation (NEU) + // ----------------------------------------------------------------------- + + /** + * Prüft alle 5 Sekunden ob ein Zeitplan (open-time / close-time) für einen Controller + * aktiv ist und öffnet/schließt die verbundenen Blöcke bei Wechsel. + * + * Ingame-Zeit: 0 = Sonnenaufgang, 6000 = Mittag, 13000 = Sonnenuntergang, 18000 = Mitternacht + * Anzeige: ticksToTime() wandelt in "HH:MM" um (Tag beginnt um 06:00). + */ + public void checkTimedControllers() { + for (String controllerLoc : dataManager.getAllPlacedControllers()) { + String buttonId = dataManager.getButtonIdForPlacedController(controllerLoc); + if (buttonId == null) continue; + + long openTime = dataManager.getScheduleOpenTime(buttonId); + long closeTime = dataManager.getScheduleCloseTime(buttonId); + if (openTime < 0 || closeTime < 0) continue; + Location loc = parseLocation(controllerLoc); if (loc == null) continue; - Block block = loc.getBlock(); - if (block.getType() != Material.TRIPWIRE_HOOK) continue; + long worldTime = loc.getWorld().getTime() % 24000; + boolean shouldBeOpen; + + if (openTime <= closeTime) { + // Normales Intervall: z.B. öffnen 6000, schließen 18000 + shouldBeOpen = worldTime >= openTime && worldTime < closeTime; + } else { + // Über Mitternacht: z.B. öffnen 20000, schließen 4000 + shouldBeOpen = worldTime >= openTime || worldTime < closeTime; + } + + Boolean lastState = timedControllerLastState.get(controllerLoc); + if (lastState != null && lastState == shouldBeOpen) continue; + + timedControllerLastState.put(controllerLoc, shouldBeOpen); + List connected = dataManager.getConnectedBlocks(buttonId); + if (connected != null && !connected.isEmpty()) { + setOpenables(connected, shouldBeOpen); + } + } + } + + // ----------------------------------------------------------------------- + // Bewegungsmelder + // ----------------------------------------------------------------------- + + public void checkMotionSensors() { + long now = System.currentTimeMillis(); + + for (String controllerLoc : dataManager.getAllPlacedControllers()) { + Location loc = parseLocation(controllerLoc); + if (loc == null) continue; + Material bType = loc.getBlock().getType(); + + boolean isTripwire = bType == Material.TRIPWIRE_HOOK; + boolean isCarpet = bType.name().endsWith("_CARPET"); + if (!isTripwire && !isCarpet) continue; String buttonId = dataManager.getButtonIdForPlacedController(controllerLoc); if (buttonId == null) continue; double radius = dataManager.getMotionSensorRadius(controllerLoc); if (radius == -1) radius = configManager.getConfig().getDouble("motion-detection-radius", 5.0); - long delay = dataManager.getMotionSensorDelay(controllerLoc); if (delay == -1) delay = configManager.getConfig().getLong("motion-close-delay-ms", 5000L); - boolean detected = !loc.getWorld().getNearbyEntities(loc, radius, radius, radius, e -> e instanceof Player).isEmpty(); - List connectedBlocks = dataManager.getConnectedBlocks(buttonId); - - if (connectedBlocks == null || connectedBlocks.isEmpty()) continue; + final double r = radius; + boolean detected; + if (isCarpet) { + // NEU: Teppich erkennt NUR Spieler + detected = !loc.getWorld() + .getNearbyEntities(loc, r, r, r, e -> e instanceof Player).isEmpty(); + } else { + // Tripwire: alle lebenden Entitäten + detected = !loc.getWorld() + .getNearbyEntities(loc, r, r, r, + e -> e instanceof org.bukkit.entity.LivingEntity).isEmpty(); + } + + List connected = dataManager.getConnectedBlocks(buttonId); + if (connected == null || connected.isEmpty()) continue; if (detected) { - setOpenables(connectedBlocks, true); + if (!activeSensors.contains(controllerLoc)) { + setOpenables(connected, true); + activeSensors.add(controllerLoc); + } lastMotionDetections.put(controllerLoc, now); } else { Long last = lastMotionDetections.get(controllerLoc); if (last != null && now - last >= delay) { - setOpenables(connectedBlocks, false); + setOpenables(connected, false); lastMotionDetections.remove(controllerLoc); + activeSensors.remove(controllerLoc); } } } } - private void setOpenables(List connectedBlocks, boolean open) { - for (String targetLocStr : connectedBlocks) { - Location targetLoc = parseLocation(targetLocStr); - if (targetLoc == null) continue; - - Block targetBlock = targetLoc.getBlock(); - if (targetBlock.getBlockData() instanceof org.bukkit.block.data.Openable) { - org.bukkit.block.data.Openable openable = (org.bukkit.block.data.Openable) targetBlock.getBlockData(); - openable.setOpen(open); - targetBlock.setBlockData(openable); + void setOpenables(List connectedBlocks, boolean open) { + for (String locStr : connectedBlocks) { + Location tl = parseLocation(locStr); + if (tl == null) continue; + Block tb = tl.getBlock(); + if (tb.getBlockData() instanceof org.bukkit.block.data.Openable) { + org.bukkit.block.data.Openable o = (org.bukkit.block.data.Openable) tb.getBlockData(); + o.setOpen(open); + tb.setBlockData(o); } } } - private Location parseLocation(String locStr) { - String[] parts = locStr.split(","); - if (parts.length != 4) return null; - World world = getServer().getWorld(parts[0]); - if (world == null) return null; - try { - return new Location(world, Integer.parseInt(parts[1]), Integer.parseInt(parts[2]), Integer.parseInt(parts[3])); - } catch (NumberFormatException e) { - return null; - } - } - - public void playDoorbellSound(Location loc, String instrument) { - Block block = loc.getBlock(); - if (block.getType() != Material.NOTE_BLOCK) return; - - NoteBlock noteBlock = (NoteBlock) block.getBlockData(); - try { - org.bukkit.Instrument bukkitInstrument = org.bukkit.Instrument.valueOf(instrument.toUpperCase()); - noteBlock.setInstrument(bukkitInstrument); - noteBlock.setNote(new Note(0, Tone.C, false)); - block.setBlockData(noteBlock); - loc.getWorld().playSound(loc, bukkitInstrument.getSound(), 1.0f, 1.0f); - - if (configManager.getConfig().getBoolean("double-note-enabled", true)) { - int delayMs = configManager.getConfig().getInt("double-note-delay-ms", 1000); - long delayTicks = (long) (delayMs / 50.0); - getServer().getScheduler().runTaskLater(this, () -> { - if (block.getType() == Material.NOTE_BLOCK) { - loc.getWorld().playSound(loc, bukkitInstrument.getSound(), 1.0f, 1.0f); - } - }, delayTicks); - } - } catch (IllegalArgumentException e) { - getLogger().warning("Ungültiges Instrument: " + instrument); - } - } + // ----------------------------------------------------------------------- + // Befehle + // ----------------------------------------------------------------------- @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (!command.getName().equalsIgnoreCase("bc")) return false; - + if (args.length == 0) { - sender.sendMessage("§6[ButtonControl] §7Verwende: /bc "); + sender.sendMessage("§6[BC] §7/bc "); return true; } - // --- INFO BEFEHL --- - if (args[0].equalsIgnoreCase("info")) { - sender.sendMessage("§6§lButtonControl §7- v" + getDescription().getVersion()); - sender.sendMessage("§eAuthor: §fM_Viper"); - sender.sendMessage("§eFeatures: §fTüren, Lampen, Notenblöcke, Sensoren"); + String sub = args[0].toLowerCase(); + + // INFO + if (sub.equals("info")) { + sender.sendMessage("§6§lButtonControl §7v" + getDescription().getVersion() + " §8by §7M_Viper"); + sender.sendMessage("§7Features: §fTüren · Lampen · Notenblöcke · Sensoren · Teppiche · Schilder · Zeitpläne"); + sender.sendMessage("§7Controller aktiv: §f" + dataManager.getAllPlacedControllers().size()); return true; } - // --- RELOAD BEFEHL --- - if (args[0].equalsIgnoreCase("reload")) { + // RELOAD + if (sub.equals("reload")) { if (!sender.hasPermission("buttoncontrol.reload")) { - sender.sendMessage(configManager.getMessage("keine-berechtigung")); - return true; + sender.sendMessage(configManager.getMessage("keine-berechtigung")); return true; } configManager.reloadConfig(); - updateConfigWithDefaults(); dataManager.reloadData(); + timedControllerLastState.clear(); sender.sendMessage(configManager.getMessage("konfiguration-neugeladen")); return true; } - // --- NOTE BEFEHL --- - if (args[0].equalsIgnoreCase("note") && sender instanceof Player) { + // NOTE + if (sub.equals("note") && sender instanceof Player) { Player player = (Player) sender; - if (args.length < 2) { - player.sendMessage("§6[ButtonControl] §7Verwende: /bc note "); - return true; - } + if (args.length < 2) { player.sendMessage("§7/bc note "); return true; } try { org.bukkit.Instrument.valueOf(args[1].toUpperCase()); dataManager.setPlayerInstrument(player.getUniqueId(), args[1].toUpperCase()); @@ -413,78 +397,184 @@ public class ButtonControl extends JavaPlugin { return true; } - // --- TRUST / PUBLIC / PRIVATE SYSTEM --- - if (sender instanceof Player) { - Player player = (Player) sender; - if (args[0].equalsIgnoreCase("trust") || args[0].equalsIgnoreCase("untrust") || - args[0].equalsIgnoreCase("public") || args[0].equalsIgnoreCase("private")) { - - Block target = player.getTargetBlockExact(5); - - // Erkennt nun Stein- und alle Holzbuttons sowie Sensoren - if (target == null || (!target.getType().name().endsWith("_BUTTON") && - target.getType() != Material.DAYLIGHT_DETECTOR && - target.getType() != Material.TRIPWIRE_HOOK)) { - - player.sendMessage(configManager.getMessage("kein-controller-im-blick")); - return true; - } + // Alle folgenden Befehle erfordern Spieler + angescauten Controller + if (!(sender instanceof Player)) { + sender.sendMessage("§cNur Spieler können diesen Befehl verwenden."); + return true; + } + Player player = (Player) sender; - String targetLoc = target.getWorld().getName() + "," + target.getX() + "," + target.getY() + "," + target.getZ(); - String buttonId = dataManager.getButtonIdForLocation(targetLoc); + if (sub.equals("list") || sub.equals("rename") || sub.equals("schedule") + || sub.equals("trust") || sub.equals("untrust") + || sub.equals("public") || sub.equals("private")) { - if (buttonId == null) { - player.sendMessage(configManager.getMessage("keine-bloecke-verbunden")); - return true; - } + Block target = player.getTargetBlockExact(5); + if (!isValidController(target)) { + player.sendMessage(configManager.getMessage("kein-controller-im-blick")); return true; + } + String targetLoc = toLoc(target); + String buttonId = dataManager.getButtonIdForLocation(targetLoc); + if (buttonId == null) { + player.sendMessage(configManager.getMessage("keine-bloecke-verbunden")); return true; + } + boolean isAdmin = player.hasPermission("buttoncontrol.admin"); + boolean isOwner = dataManager.isOwner(buttonId, player.getUniqueId()); - if (!dataManager.isOwner(buttonId, player.getUniqueId())) { - player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); - return true; - } - - // Spieler hinzufügen - if (args[0].equalsIgnoreCase("trust")) { - if (args.length < 2) { - player.sendMessage("§6[ButtonControl] §7Verwende: /bc trust "); - return true; + switch (sub) { + case "list": + if (!dataManager.canAccess(buttonId, player.getUniqueId()) && !isAdmin) { + player.sendMessage(configManager.getMessage("keine-berechtigung-controller")); return true; } - Player targetPlayer = Bukkit.getPlayer(args[1]); - if (targetPlayer == null) { - player.sendMessage(configManager.getMessage("spieler-nicht-gefunden")); - return true; + sendListInfo(player, buttonId); + break; + + case "rename": + if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; } + if (args.length < 2) { player.sendMessage("§7/bc rename "); return true; } + String newName = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + if (newName.length() > 32) { player.sendMessage("§cName zu lang (max. 32 Zeichen)."); return true; } + dataManager.setControllerName(buttonId, newName); + player.sendMessage(String.format(configManager.getMessage("controller-umbenannt"), newName)); + break; + + case "schedule": + if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; } + new ScheduleGUI(this, player, buttonId).open(); + break; + + case "trust": + if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; } + if (args.length < 2) { player.sendMessage("§7/bc trust "); return true; } + org.bukkit.OfflinePlayer tp = Bukkit.getOfflinePlayer(args[1]); + if (!tp.hasPlayedBefore() && !tp.isOnline()) { + player.sendMessage(configManager.getMessage("spieler-nicht-gefunden")); return true; } - dataManager.addTrustedPlayer(buttonId, targetPlayer.getUniqueId()); - player.sendMessage(String.format(configManager.getMessage("trust-hinzugefuegt"), targetPlayer.getName())); - } - // Spieler entfernen - else if (args[0].equalsIgnoreCase("untrust")) { - if (args.length < 2) { - player.sendMessage("§6[ButtonControl] §7Verwende: /bc untrust "); - return true; - } - UUID targetUUID = Bukkit.getOfflinePlayer(args[1]).getUniqueId(); - dataManager.removeTrustedPlayer(buttonId, targetUUID); + dataManager.addTrustedPlayer(buttonId, tp.getUniqueId()); + player.sendMessage(String.format(configManager.getMessage("trust-hinzugefuegt"), args[1])); + break; + + case "untrust": + if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; } + if (args.length < 2) { player.sendMessage("§7/bc untrust "); return true; } + dataManager.removeTrustedPlayer(buttonId, Bukkit.getOfflinePlayer(args[1]).getUniqueId()); player.sendMessage(String.format(configManager.getMessage("trust-entfernt"), args[1])); - } - // Status umschalten (Public) oder erzwingen (Private) - else if (args[0].equalsIgnoreCase("public") || args[0].equalsIgnoreCase("private")) { - boolean newState = args[0].equalsIgnoreCase("public") ? !dataManager.isPublic(buttonId) : false; - dataManager.setPublic(buttonId, newState); - String statusColor = newState ? "§aÖffentlich" : "§cPrivat"; - player.sendMessage(String.format(configManager.getMessage("status-geandert"), statusColor)); - } - return true; + break; + + default: // public / private + if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; } + boolean pub = sub.equals("public"); + dataManager.setPublic(buttonId, pub); + player.sendMessage(String.format(configManager.getMessage("status-geandert"), + pub ? "§aÖffentlich" : "§cPrivat")); + break; } } return true; } - public ConfigManager getConfigManager() { - return configManager; + private void sendListInfo(Player player, String buttonId) { + String name = dataManager.getControllerName(buttonId); + String header = name != null + ? "§6§l" + name + : "§6§lController §8§o(ID: " + buttonId.substring(0, 8) + "...)"; + player.sendMessage(header); + + List connected = dataManager.getConnectedBlocks(buttonId); + if (connected == null || connected.isEmpty()) { + player.sendMessage(" §cKeine Blöcke verbunden."); + } else { + player.sendMessage("§7Verbundene Blöcke §8(" + connected.size() + ")§7:"); + for (int i = 0; i < connected.size(); i++) { + String ls = connected.get(i); + Location l = parseLocation(ls); + String typeLabel = l != null ? "§e" + l.getBlock().getType().name() : "§8unbekannt"; + String[] p = ls.split(","); + String coords = p.length == 4 + ? "§8(" + p[1] + "§7, §8" + p[2] + "§7, §8" + p[3] + " §7in §f" + p[0] + "§8)" : ""; + player.sendMessage(" §8" + (i + 1) + ". " + typeLabel + " " + coords); + } + } + + player.sendMessage("§7Status: " + (dataManager.isPublic(buttonId) ? "§aÖffentlich" : "§cPrivat")); + + long openT = dataManager.getScheduleOpenTime(buttonId); + long closeT = dataManager.getScheduleCloseTime(buttonId); + if (openT >= 0 && closeT >= 0) { + player.sendMessage("§7Zeitplan: §aÖffnet §7um §e" + ticksToTime(openT) + + " §7· §cSchließt §7um §e" + ticksToTime(closeT)); + } else { + player.sendMessage("§7Zeitplan: §8Nicht gesetzt §7(§e/bc schedule§7)"); + } } - public DataManager getDataManager() { - return dataManager; + // ----------------------------------------------------------------------- + // Utility + // ----------------------------------------------------------------------- + + /** Wandelt Minecraft-Ticks (0–23999) in "HH:MM" um. Ingame-Tag startet um 06:00. */ + public String ticksToTime(long ticks) { + long shifted = (ticks + 6000) % 24000; + long hours = shifted / 1000; + long minutes = (shifted % 1000) * 60 / 1000; + return String.format("%02d:%02d", hours, minutes); } + + /** Wandelt "HH:MM" zurück in Minecraft-Ticks */ + public long timeToTicks(int hours, int minutes) { + long totalMinutes = hours * 60L + minutes; + long ticks = (totalMinutes * 1000L / 60L - 6000 + 24000) % 24000; + return ticks; + } + + public boolean isValidController(Block b) { + if (b == null) return false; + Material m = b.getType(); + return m.name().endsWith("_BUTTON") + || m == Material.DAYLIGHT_DETECTOR + || m == Material.TRIPWIRE_HOOK + || m.name().endsWith("_SIGN") + || m.name().endsWith("_CARPET"); + } + + public String toLoc(Block b) { + return b.getWorld().getName() + "," + b.getX() + "," + b.getY() + "," + b.getZ(); + } + + public Location parseLocation(String locStr) { + String[] parts = locStr.split(","); + if (parts.length != 4) return null; + World world = getServer().getWorld(parts[0]); + if (world == null) return null; + try { + return new Location(world, + Integer.parseInt(parts[1]), + Integer.parseInt(parts[2]), + Integer.parseInt(parts[3])); + } catch (NumberFormatException e) { return null; } + } + + public void playDoorbellSound(Location loc, String instrument) { + Block block = loc.getBlock(); + if (block.getType() != Material.NOTE_BLOCK) return; + NoteBlock noteBlock = (NoteBlock) block.getBlockData(); + try { + org.bukkit.Instrument inst = org.bukkit.Instrument.valueOf(instrument.toUpperCase()); + noteBlock.setInstrument(inst); + noteBlock.setNote(new Note(0, Tone.C, false)); + block.setBlockData(noteBlock); + loc.getWorld().playSound(loc, inst.getSound(), 1.0f, 1.0f); + if (configManager.getConfig().getBoolean("double-note-enabled", true)) { + long delayTicks = (long)(configManager.getConfig().getInt("double-note-delay-ms", 1000) / 50.0); + getServer().getScheduler().runTaskLater(this, () -> { + if (block.getType() == Material.NOTE_BLOCK) + loc.getWorld().playSound(loc, inst.getSound(), 1.0f, 1.0f); + }, delayTicks); + } + } catch (IllegalArgumentException e) { + getLogger().warning("Ungültiges Instrument: " + instrument); + } + } + + public ConfigManager getConfigManager() { return configManager; } + public DataManager getDataManager() { return dataManager; } } \ No newline at end of file diff --git a/src/main/java/viper/ButtonListener.java b/src/main/java/viper/ButtonListener.java index 9c2bb1c..fed1cc7 100644 --- a/src/main/java/viper/ButtonListener.java +++ b/src/main/java/viper/ButtonListener.java @@ -3,7 +3,9 @@ package viper; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Sound; import org.bukkit.block.Block; +import org.bukkit.block.data.Bisected; import org.bukkit.block.data.Openable; import org.bukkit.block.data.Lightable; import org.bukkit.entity.Player; @@ -13,6 +15,7 @@ import org.bukkit.event.block.Action; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -26,39 +29,60 @@ public class ButtonListener implements Listener { private final DataManager dataManager; public ButtonListener(ButtonControl plugin, ConfigManager configManager, DataManager dataManager) { - this.plugin = plugin; + this.plugin = plugin; this.configManager = configManager; - this.dataManager = dataManager; + this.dataManager = dataManager; } + // ----------------------------------------------------------------------- + // Interact – Benutzung + Verbinden + // ----------------------------------------------------------------------- + @EventHandler public void onPlayerInteract(PlayerInteractEvent event) { - Player player = event.getPlayer(); - UUID playerUUID = player.getUniqueId(); - ItemStack item = event.getItem(); - Block block = event.getClickedBlock(); + // Doppelt-Feuern bei Items verhindern (Haupt- und Nebenhand) + if (event.getHand() != EquipmentSlot.HAND) return; - // 1. Logik für bereits platzierte Controller (Benutzung) + Player player = event.getPlayer(); + UUID playerUUID = player.getUniqueId(); + ItemStack item = event.getItem(); + Block block = event.getClickedBlock(); + + // ── 1. Bereits platzierter Controller ────────────────────────────── if (event.getAction() == Action.RIGHT_CLICK_BLOCK && block != null) { - String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ(); - String buttonId = dataManager.getButtonIdForLocation(blockLocation); + String blockLocation = plugin.toLoc(block); + String buttonId = dataManager.getButtonIdForLocation(blockLocation); if (buttonId != null) { - if (!dataManager.canAccess(buttonId, playerUUID)) { + // Admin-Bypass + if (!dataManager.canAccess(buttonId, playerUUID) + && !player.hasPermission("buttoncontrol.admin")) { player.sendMessage(configManager.getMessage("keine-berechtigung-controller")); event.setCancelled(true); return; } - if (block.getType() == Material.TRIPWIRE_HOOK) { + // Tripwire & Teppich → MotionSensorGUI öffnen + if (block.getType() == Material.TRIPWIRE_HOOK + || block.getType().name().endsWith("_CARPET")) { event.setCancelled(true); new MotionSensorGUI(plugin, player, blockLocation, buttonId).open(); return; } - if (isButton(block.getType()) || block.getType() == Material.DAYLIGHT_DETECTOR) { + // Schild → nur mit Shift+Klick Blöcke toggeln, + // normaler Klick öffnet den Schildeditor (Vanilla) + if (block.getType().name().endsWith("_SIGN") + || block.getType().name().endsWith("_BUTTON") + || block.getType() == Material.DAYLIGHT_DETECTOR) { + + if (player.isSneaking()) { + // Shift+Klick → Vanilla-Verhalten zulassen (Schildeditor etc.) + return; + } + + event.setCancelled(true); List connectedBlocks = dataManager.getConnectedBlocks(buttonId); - if (connectedBlocks != null && !connectedBlocks.isEmpty()) { toggleConnectedBlocks(player, playerUUID, connectedBlocks); } else { @@ -69,207 +93,336 @@ public class ButtonListener implements Listener { } } - // 2. Logik für das Verbinden von neuen Blöcken (Item in der Hand) - if (item == null || !item.hasItemMeta() || !item.getItemMeta().getDisplayName().contains("§6Steuer-")) { - return; - } - - if (event.getAction() != Action.RIGHT_CLICK_BLOCK || block == null) { - return; - } + // ── 2. Verbinden mit Controller-Item in der Hand ─────────────────── + if (item == null || !item.hasItemMeta()) return; + String displayName = item.getItemMeta().getDisplayName(); + if (!displayName.contains("§6Steuer-")) return; + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || block == null) return; if (isInteractableTarget(block.getType())) { event.setCancelled(true); - ItemMeta meta = item.getItemMeta(); - String buttonId = null; + // Doppeltür: immer untersten Block speichern + Block targetBlock = getBottomDoorBlock(block); - // ID aus der Lore extrahieren (wir suchen nach dem Präfix §8ID: ) - if (meta != null && meta.hasLore() && !meta.getLore().isEmpty()) { - String firstLine = meta.getLore().get(0); - if (firstLine.startsWith("§8ID: ")) { - buttonId = firstLine.replace("§8ID: ", ""); - } - } - - // Falls keine ID existiert (neues Item), generieren und SOFORT speichern + ItemMeta meta = item.getItemMeta(); + String buttonId = extractButtonId(meta); if (buttonId == null) { buttonId = UUID.randomUUID().toString(); updateButtonLore(item, buttonId); } - - List connectedBlocks = dataManager.getConnectedBlocks(buttonId); - if (connectedBlocks == null) { - connectedBlocks = new ArrayList<>(); - } - String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ(); - if (connectedBlocks.contains(blockLocation)) { + List connectedBlocks = dataManager.getConnectedBlocks(buttonId); + if (connectedBlocks == null) connectedBlocks = new ArrayList<>(); + + String targetLocStr = plugin.toLoc(targetBlock); + if (connectedBlocks.contains(targetLocStr)) { player.sendMessage(configManager.getMessage("block-bereits-verbunden")); return; } - if (checkLimits(player, block.getType(), connectedBlocks)) { - connectedBlocks.add(blockLocation); + if (checkLimits(player, targetBlock.getType(), connectedBlocks)) { + connectedBlocks.add(targetLocStr); dataManager.setConnectedBlocks(playerUUID.toString(), buttonId, connectedBlocks); player.sendMessage(configManager.getMessage("block-verbunden")); } } } - private void toggleConnectedBlocks(Player player, UUID playerUUID, List connectedBlocks) { - boolean anyDoorOpened = false, anyDoorClosed = false; - boolean anyGateOpened = false, anyGateClosed = false; - boolean anyTrapOpened = false, anyTrapClosed = false; - boolean anyLampOn = false, anyLampOff = false; - boolean anyNoteBlockPlayed = false; - boolean anyBellPlayed = false; - - for (String locStr : connectedBlocks) { - Location location = parseLocation(locStr); - if (location == null) continue; - Block targetBlock = location.getBlock(); - - if (isDoor(targetBlock.getType()) || isGate(targetBlock.getType()) || isTrapdoor(targetBlock.getType())) { - if (targetBlock.getBlockData() instanceof Openable) { - Openable openable = (Openable) targetBlock.getBlockData(); - boolean wasOpen = openable.isOpen(); - openable.setOpen(!wasOpen); - targetBlock.setBlockData(openable); - - if (isDoor(targetBlock.getType())) { - if (!wasOpen) anyDoorOpened = true; else anyDoorClosed = true; - } else if (isGate(targetBlock.getType())) { - if (!wasOpen) anyGateOpened = true; else anyGateClosed = true; - } else if (isTrapdoor(targetBlock.getType())) { - if (!wasOpen) anyTrapOpened = true; else anyTrapClosed = true; - } - } - } - else if (targetBlock.getType() == Material.REDSTONE_LAMP) { - Lightable lamp = (Lightable) targetBlock.getBlockData(); - boolean wasLit = lamp.isLit(); - lamp.setLit(!wasLit); - targetBlock.setBlockData(lamp); - if (!wasLit) anyLampOn = true; else anyLampOff = true; - } - else if (targetBlock.getType() == Material.NOTE_BLOCK) { - String instrument = dataManager.getPlayerInstrument(playerUUID); - if (instrument == null) { - instrument = configManager.getConfig().getString("default-note", "PIANO"); - } - plugin.playDoorbellSound(location, instrument); - anyNoteBlockPlayed = true; - } - else if (targetBlock.getType() == Material.BELL) { - targetBlock.getWorld().playSound(location, org.bukkit.Sound.BLOCK_BELL_USE, 3.0f, 1.0f); - anyBellPlayed = true; - } - } - - if (anyDoorOpened) player.sendMessage(configManager.getMessage("tueren-geoeffnet")); - if (anyDoorClosed) player.sendMessage(configManager.getMessage("tueren-geschlossen")); - if (anyGateOpened) player.sendMessage(configManager.getMessage("gates-geoeffnet")); - if (anyGateClosed) player.sendMessage(configManager.getMessage("gates-geschlossen")); - if (anyTrapOpened) player.sendMessage(configManager.getMessage("fallturen-geoeffnet")); - if (anyTrapClosed) player.sendMessage(configManager.getMessage("fallturen-geschlossen")); - if (anyLampOn) player.sendMessage(configManager.getMessage("lampen-eingeschaltet")); - if (anyLampOff) player.sendMessage(configManager.getMessage("lampen-ausgeschaltet")); - if (anyNoteBlockPlayed) player.sendMessage(configManager.getMessage("notenblock-ausgeloest")); - if (anyBellPlayed) player.sendMessage(configManager.getMessage("glocke-gelaeutet")); - } - - private boolean checkLimits(Player player, Material type, List connected) { - if (isDoor(type)) { - if (connected.stream().filter(l -> isDoor(getMaterialFromLocation(l))).count() >= configManager.getMaxDoors()) { - player.sendMessage(configManager.getMessage("max-tueren-erreicht")); - return false; - } - } else if (isGate(type)) { - if (connected.stream().filter(l -> isGate(getMaterialFromLocation(l))).count() >= configManager.getMaxGates()) { - player.sendMessage(configManager.getMessage("max-gates-erreicht")); - return false; - } - } else if (isTrapdoor(type)) { - if (connected.stream().filter(l -> isTrapdoor(getMaterialFromLocation(l))).count() >= configManager.getMaxTrapdoors()) { - player.sendMessage(configManager.getMessage("max-fallturen-erreicht")); - return false; - } - } else if (type == Material.REDSTONE_LAMP) { - if (connected.stream().filter(l -> getMaterialFromLocation(l) == Material.REDSTONE_LAMP).count() >= configManager.getMaxLamps()) { - player.sendMessage(configManager.getMessage("max-lampen-erreicht")); - return false; - } - } else if (type == Material.NOTE_BLOCK) { - if (connected.stream().filter(l -> getMaterialFromLocation(l) == Material.NOTE_BLOCK).count() >= configManager.getMaxNoteBlocks()) { - player.sendMessage(configManager.getMessage("max-notenbloecke-erreicht")); - return false; - } - } else if (type == Material.BELL) { - if (connected.stream().filter(l -> getMaterialFromLocation(l) == Material.BELL).count() >= configManager.getMaxBells()) { - player.sendMessage(configManager.getMessage("max-glocken-erreicht")); - return false; - } - } - return true; - } - - @EventHandler - public void onBlockPlace(BlockPlaceEvent event) { - ItemStack item = event.getItemInHand(); - if (item == null || !item.hasItemMeta() || !item.getItemMeta().getDisplayName().contains("§6Steuer-")) { - return; - } - - Block block = event.getBlockPlaced(); - ItemMeta meta = item.getItemMeta(); - String buttonId = null; - - if (meta != null && meta.hasLore() && !meta.getLore().isEmpty()) { - String firstLine = meta.getLore().get(0); - if (firstLine.startsWith("§8ID: ")) { - buttonId = firstLine.replace("§8ID: ", ""); - } - } - - if (buttonId == null) { - buttonId = UUID.randomUUID().toString(); - updateButtonLore(item, buttonId); - } - - String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ(); - dataManager.registerController(blockLocation, event.getPlayer().getUniqueId(), buttonId); - event.getPlayer().sendMessage(configManager.getMessage("controller-platziert")); - } + // ----------------------------------------------------------------------- + // Block-Break: Controller abbauen + // ----------------------------------------------------------------------- @EventHandler public void onBlockBreak(BlockBreakEvent event) { - Block block = event.getBlock(); - String blockLocation = block.getWorld().getName() + "," + block.getX() + "," + block.getY() + "," + block.getZ(); - String buttonId = dataManager.getButtonIdForLocation(blockLocation); + Block block = event.getBlock(); + String blockLocation = plugin.toLoc(block); + String buttonId = dataManager.getButtonIdForLocation(blockLocation); if (buttonId != null) { - if (!dataManager.isOwner(buttonId, event.getPlayer().getUniqueId())) { + if (!dataManager.isOwner(buttonId, event.getPlayer().getUniqueId()) + && !event.getPlayer().hasPermission("buttoncontrol.admin")) { event.getPlayer().sendMessage(configManager.getMessage("nur-besitzer-abbauen")); event.setCancelled(true); return; } - dataManager.removeController(blockLocation); event.getPlayer().sendMessage(configManager.getMessage("controller-entfernt")); } } - private boolean isButton(Material m) { return m.name().endsWith("_BUTTON"); } - private boolean isDoor(Material m) { return m.name().endsWith("_DOOR"); } - private boolean isGate(Material m) { return m.name().endsWith("_FENCE_GATE"); } - private boolean isTrapdoor(Material m) { return m.name().endsWith("_TRAPDOOR"); } - - private boolean isInteractableTarget(Material m) { - return isDoor(m) || isGate(m) || isTrapdoor(m) || m == Material.REDSTONE_LAMP || m == Material.NOTE_BLOCK || m == Material.BELL; + // ----------------------------------------------------------------------- + // Block-Break: Verbundener Block abgebaut → Eintrag bereinigen (NEU) + // ----------------------------------------------------------------------- + + @EventHandler + public void onConnectedBlockBreak(BlockBreakEvent event) { + Block block = event.getBlock(); + if (!isInteractableTarget(block.getType())) return; + + // Bei Türen normalisieren auf Unterblock + Block bottomBlock = getBottomDoorBlock(block); + String locStr = plugin.toLoc(bottomBlock); + + if (dataManager.removeFromAllConnectedBlocks(locStr)) { + event.getPlayer().sendMessage(configManager.getMessage("block-verbindung-entfernt")); + } } - private Material getMaterialFromLocation(String locString) { + // ----------------------------------------------------------------------- + // Controller platzieren + // ----------------------------------------------------------------------- + + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + ItemStack item = event.getItemInHand(); + if (item == null || !item.hasItemMeta()) return; + if (!item.getItemMeta().getDisplayName().contains("§6Steuer-")) return; + + Block block = event.getBlockPlaced(); + String buttonId = extractButtonId(item.getItemMeta()); + if (buttonId == null) { + buttonId = UUID.randomUUID().toString(); + updateButtonLore(item, buttonId); + } + + dataManager.registerController( + plugin.toLoc(block), event.getPlayer().getUniqueId(), buttonId); + event.getPlayer().sendMessage(configManager.getMessage("controller-platziert")); + } + + // ----------------------------------------------------------------------- + // Toggle-Logik + // ----------------------------------------------------------------------- + + private void toggleConnectedBlocks(Player player, UUID playerUUID, List connectedBlocks) { + boolean anyDoorOpened = false, anyDoorClosed = false; + boolean anyGateOpened = false, anyGateClosed = false; + boolean anyTrapOpened = false, anyTrapClosed = false; + boolean anyIronDoorOpened = false, anyIronDoorClosed = false; + boolean anyIronTrapOpened = false, anyIronTrapClosed = false; + boolean anyLampOn = false, anyLampOff = false; + boolean anyNoteBlockPlayed = false; + boolean anyBellPlayed = false; + + boolean soundsEnabled = configManager.getConfig().getBoolean("sounds.enabled", true); + + for (String locStr : connectedBlocks) { + Location location = parseLocation(locStr); + if (location == null) continue; + Block targetBlock = location.getBlock(); + Material mat = targetBlock.getType(); + + // ── Eisentür (NEU) ────────────────────────────────────────────── + // Eisentüren implementieren Openable in der Bukkit-API. + // Wir setzen den Zustand direkt – kein Redstone-Signal nötig. + if (mat == Material.IRON_DOOR) { + if (targetBlock.getBlockData() instanceof Openable) { + Openable op = (Openable) targetBlock.getBlockData(); + boolean wasOpen = op.isOpen(); + op.setOpen(!wasOpen); + targetBlock.setBlockData(op); + if (soundsEnabled) { + String soundKey = wasOpen ? "sounds.iron-door-close" : "sounds.iron-door-open"; + playConfigSound(location, soundKey, + wasOpen ? "BLOCK_IRON_DOOR_CLOSE" : "BLOCK_IRON_DOOR_OPEN"); + } + if (!wasOpen) anyIronDoorOpened = true; else anyIronDoorClosed = true; + } + continue; + } + + // ── Eisenfalltür (NEU) ───────────────────────────────────────── + if (mat == Material.IRON_TRAPDOOR) { + if (targetBlock.getBlockData() instanceof Openable) { + Openable op = (Openable) targetBlock.getBlockData(); + boolean wasOpen = op.isOpen(); + op.setOpen(!wasOpen); + targetBlock.setBlockData(op); + if (soundsEnabled) { + String soundKey = wasOpen ? "sounds.iron-door-close" : "sounds.iron-door-open"; + playConfigSound(location, soundKey, + wasOpen ? "BLOCK_IRON_TRAPDOOR_CLOSE" : "BLOCK_IRON_TRAPDOOR_OPEN"); + } + if (!wasOpen) anyIronTrapOpened = true; else anyIronTrapClosed = true; + } + continue; + } + + // ── Holztür / Zauntore / Falltüren ──────────────────────────── + if (isDoor(mat) || isGate(mat) || isTrapdoor(mat)) { + if (targetBlock.getBlockData() instanceof Openable) { + Openable op = (Openable) targetBlock.getBlockData(); + boolean wasOpen = op.isOpen(); + op.setOpen(!wasOpen); + targetBlock.setBlockData(op); + + if (soundsEnabled) { + String soundKey = wasOpen ? "sounds.door-close" : "sounds.door-open"; + String fallback = wasOpen ? "BLOCK_WOODEN_DOOR_CLOSE" : "BLOCK_WOODEN_DOOR_OPEN"; + playConfigSound(location, soundKey, fallback); + } + + if (isDoor(mat)) { if (!wasOpen) anyDoorOpened = true; else anyDoorClosed = true; } + else if (isGate(mat)) { if (!wasOpen) anyGateOpened = true; else anyGateClosed = true; } + else { if (!wasOpen) anyTrapOpened = true; else anyTrapClosed = true; } + } + } + // ── Redstone-Lampe ──────────────────────────────────────────── + else if (mat == Material.REDSTONE_LAMP) { + Lightable lamp = (Lightable) targetBlock.getBlockData(); + boolean wasLit = lamp.isLit(); + lamp.setLit(!wasLit); + targetBlock.setBlockData(lamp); + if (soundsEnabled) { + playConfigSound(location, + wasLit ? "sounds.lamp-off" : "sounds.lamp-on", + "BLOCK_LEVER_CLICK"); + } + if (!wasLit) anyLampOn = true; else anyLampOff = true; + } + // ── Notenblock ──────────────────────────────────────────────── + else if (mat == Material.NOTE_BLOCK) { + String instrument = dataManager.getPlayerInstrument(playerUUID); + if (instrument == null) + instrument = configManager.getConfig().getString("default-note", "PIANO"); + plugin.playDoorbellSound(location, instrument); + anyNoteBlockPlayed = true; + } + // ── Glocke ─────────────────────────────────────────────────── + else if (mat == Material.BELL) { + targetBlock.getWorld().playSound(location, Sound.BLOCK_BELL_USE, 3.0f, 1.0f); + anyBellPlayed = true; + } + } + + // Feedback-Nachrichten + if (anyDoorOpened) player.sendMessage(configManager.getMessage("tueren-geoeffnet")); + if (anyDoorClosed) player.sendMessage(configManager.getMessage("tueren-geschlossen")); + if (anyIronDoorOpened) player.sendMessage(configManager.getMessage("eisentueren-geoeffnet")); + if (anyIronDoorClosed) player.sendMessage(configManager.getMessage("eisentueren-geschlossen")); + if (anyIronTrapOpened) player.sendMessage(configManager.getMessage("eisenfallturen-geoeffnet")); + if (anyIronTrapClosed) player.sendMessage(configManager.getMessage("eisenfallturen-geschlossen")); + if (anyGateOpened) player.sendMessage(configManager.getMessage("gates-geoeffnet")); + if (anyGateClosed) player.sendMessage(configManager.getMessage("gates-geschlossen")); + if (anyTrapOpened) player.sendMessage(configManager.getMessage("fallturen-geoeffnet")); + if (anyTrapClosed) player.sendMessage(configManager.getMessage("fallturen-geschlossen")); + if (anyLampOn) player.sendMessage(configManager.getMessage("lampen-eingeschaltet")); + if (anyLampOff) player.sendMessage(configManager.getMessage("lampen-ausgeschaltet")); + if (anyNoteBlockPlayed) player.sendMessage(configManager.getMessage("notenblock-ausgeloest")); + if (anyBellPlayed) player.sendMessage(configManager.getMessage("glocke-gelaeutet")); + } + + /** + * Spielt einen Sound ab dessen Name aus der config.yml gelesen wird. + * Ist der Key nicht gesetzt oder der Sound-Name ungültig, wird der Fallback verwendet. + */ + private void playConfigSound(Location loc, String configKey, String fallback) { + String soundName = configManager.getConfig().getString(configKey, fallback); + try { + Sound sound = Sound.valueOf(soundName.toUpperCase()); + loc.getWorld().playSound(loc, sound, 1.0f, 1.0f); + } catch (IllegalArgumentException e) { + try { + Sound sound = Sound.valueOf(fallback.toUpperCase()); + loc.getWorld().playSound(loc, sound, 1.0f, 1.0f); + } catch (IllegalArgumentException ignored) { } + } + } + + // ----------------------------------------------------------------------- + // Limits + // ----------------------------------------------------------------------- + + private boolean checkLimits(Player player, Material type, List connected) { + if (type == Material.IRON_DOOR) { + if (connected.stream().filter(l -> getMaterialAt(l) == Material.IRON_DOOR).count() + >= configManager.getMaxDoors()) { + player.sendMessage(configManager.getMessage("max-tueren-erreicht")); return false; + } + } else if (type == Material.IRON_TRAPDOOR) { + if (connected.stream().filter(l -> getMaterialAt(l) == Material.IRON_TRAPDOOR).count() + >= configManager.getMaxTrapdoors()) { + player.sendMessage(configManager.getMessage("max-fallturen-erreicht")); return false; + } + } else if (isDoor(type)) { + if (connected.stream().filter(l -> isDoor(getMaterialAt(l))).count() + >= configManager.getMaxDoors()) { + player.sendMessage(configManager.getMessage("max-tueren-erreicht")); return false; + } + } else if (isGate(type)) { + if (connected.stream().filter(l -> isGate(getMaterialAt(l))).count() + >= configManager.getMaxGates()) { + player.sendMessage(configManager.getMessage("max-gates-erreicht")); return false; + } + } else if (isTrapdoor(type)) { + if (connected.stream().filter(l -> isTrapdoor(getMaterialAt(l))).count() + >= configManager.getMaxTrapdoors()) { + player.sendMessage(configManager.getMessage("max-fallturen-erreicht")); return false; + } + } else if (type == Material.REDSTONE_LAMP) { + if (connected.stream().filter(l -> getMaterialAt(l) == Material.REDSTONE_LAMP).count() + >= configManager.getMaxLamps()) { + player.sendMessage(configManager.getMessage("max-lampen-erreicht")); return false; + } + } else if (type == Material.NOTE_BLOCK) { + if (connected.stream().filter(l -> getMaterialAt(l) == Material.NOTE_BLOCK).count() + >= configManager.getMaxNoteBlocks()) { + player.sendMessage(configManager.getMessage("max-notenbloecke-erreicht")); return false; + } + } else if (type == Material.BELL) { + if (connected.stream().filter(l -> getMaterialAt(l) == Material.BELL).count() + >= configManager.getMaxBells()) { + player.sendMessage(configManager.getMessage("max-glocken-erreicht")); return false; + } + } + return true; + } + + // ----------------------------------------------------------------------- + // Hilfsmethoden + // ----------------------------------------------------------------------- + + /** Gibt bei zweiteiligen Türen immer den untersten Block zurück. */ + private Block getBottomDoorBlock(Block block) { + Material mat = block.getType(); + if (!isDoor(mat) && mat != Material.IRON_DOOR) return block; + if (block.getBlockData() instanceof Bisected) { + Bisected b = (Bisected) block.getBlockData(); + if (b.getHalf() == Bisected.Half.TOP) return block.getRelative(0, -1, 0); + } + return block; + } + + private String extractButtonId(ItemMeta meta) { + if (meta == null || !meta.hasLore() || meta.getLore().isEmpty()) return null; + String first = meta.getLore().get(0); + return first.startsWith("§8ID: ") ? first.replace("§8ID: ", "") : null; + } + + private void updateButtonLore(ItemStack item, String buttonId) { + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + List lore = new ArrayList<>(); + lore.add("§8ID: " + buttonId); + lore.add("§7Ein universeller Controller für"); + meta.setLore(lore); + item.setItemMeta(meta); + } + } + + private boolean isButton(Material m) { return m.name().endsWith("_BUTTON"); } + private boolean isDoor(Material m) { return m.name().endsWith("_DOOR") && m != Material.IRON_DOOR; } + private boolean isGate(Material m) { return m.name().endsWith("_FENCE_GATE"); } + private boolean isTrapdoor(Material m) { return m.name().endsWith("_TRAPDOOR") && m != Material.IRON_TRAPDOOR; } + + private boolean isInteractableTarget(Material m) { + return isDoor(m) || isGate(m) || isTrapdoor(m) + || m == Material.IRON_DOOR || m == Material.IRON_TRAPDOOR + || m == Material.REDSTONE_LAMP || m == Material.NOTE_BLOCK || m == Material.BELL; + } + + private Material getMaterialAt(String locString) { Location l = parseLocation(locString); return l != null ? l.getBlock().getType() : Material.AIR; } @@ -278,18 +431,8 @@ public class ButtonListener implements Listener { String[] p = s.split(","); if (p.length != 4) return null; try { - return new Location(Bukkit.getWorld(p[0]), Integer.parseInt(p[1]), Integer.parseInt(p[2]), Integer.parseInt(p[3])); + return new Location(Bukkit.getWorld(p[0]), + Integer.parseInt(p[1]), Integer.parseInt(p[2]), Integer.parseInt(p[3])); } catch (Exception e) { return null; } } - - private void updateButtonLore(ItemStack item, String buttonId) { - ItemMeta meta = item.getItemMeta(); - if (meta != null) { - List lore = new ArrayList<>(); - lore.add("§8ID: " + buttonId); - lore.add("§7Ein universeller Controller für"); - meta.setLore(lore); - item.setItemMeta(meta); - } - } } \ No newline at end of file diff --git a/src/main/java/viper/ButtonTabCompleter.java b/src/main/java/viper/ButtonTabCompleter.java index 4aaa79d..650320f 100644 --- a/src/main/java/viper/ButtonTabCompleter.java +++ b/src/main/java/viper/ButtonTabCompleter.java @@ -13,11 +13,15 @@ import java.util.List; public class ButtonTabCompleter implements TabCompleter { - private final List commands = Arrays.asList("info", "reload", "note", "trust", "untrust", "public", "private"); + private final List commands = Arrays.asList( + "info", "reload", "note", "list", "rename", "schedule", + "trust", "untrust", "public", "private" + ); + private final List instruments = Arrays.asList( - "PIANO", "BASS_DRUM", "SNARE_DRUM", "STICKS", "BASS_GUITAR", - "FLUTE", "BELL", "CHIME", "GUITAR", "XYLOPHONE", - "IRON_XYLOPHONE", "COW_BELL", "DIDGERIDOO", "BIT", "BANJO", "PLING" + "PIANO", "BASS_DRUM", "SNARE_DRUM", "STICKS", "BASS_GUITAR", + "FLUTE", "BELL", "CHIME", "GUITAR", "XYLOPHONE", + "IRON_XYLOPHONE", "COW_BELL", "DIDGERIDOO", "BIT", "BANJO", "PLING" ); @Override @@ -26,19 +30,21 @@ public class ButtonTabCompleter implements TabCompleter { List completions = new ArrayList<>(); - // Erste Ebene: /bc if (args.length == 1) { StringUtil.copyPartialMatches(args[0], commands, completions); - } - - // Zweite Ebene: /bc note - else if (args.length == 2 && args[0].equalsIgnoreCase("note")) { - StringUtil.copyPartialMatches(args[1], instruments, completions); - } - - // Zweite Ebene: /bc trust/untrust (Spielernamen vorschlagen) - else if (args.length == 2 && (args[0].equalsIgnoreCase("trust") || args[0].equalsIgnoreCase("untrust"))) { - return null; // 'null' lässt Bukkit automatisch alle Online-Spieler vorschlagen + + } else if (args.length == 2) { + switch (args[0].toLowerCase()) { + case "note": + StringUtil.copyPartialMatches(args[1], instruments, completions); + break; + case "trust": + case "untrust": + return null; // Bukkit schlägt automatisch Online-Spieler vor + case "rename": + completions.add(""); + break; + } } Collections.sort(completions); diff --git a/src/main/java/viper/ConfigManager.java b/src/main/java/viper/ConfigManager.java index 6071de6..d5ea7b2 100644 --- a/src/main/java/viper/ConfigManager.java +++ b/src/main/java/viper/ConfigManager.java @@ -21,143 +21,163 @@ public class ConfigManager { private void loadConfig() { configFile = new File(plugin.getDataFolder(), "config.yml"); - if (!configFile.exists()) { - plugin.saveResource("config.yml", false); - } + if (!configFile.exists()) plugin.saveResource("config.yml", false); config = YamlConfiguration.loadConfiguration(configFile); - mergeDefaults(config, "config.yml", configFile); setConfigDefaults(); } private void loadLang() { langFile = new File(plugin.getDataFolder(), "lang.yml"); - if (!langFile.exists()) { - plugin.saveResource("lang.yml", false); - } + if (!langFile.exists()) plugin.saveResource("lang.yml", false); lang = YamlConfiguration.loadConfiguration(langFile); - mergeDefaults(lang, "lang.yml", langFile); setLangDefaults(); } + /** + * FIX: Null-Check VOR dem try-Block – verhindert NPE wenn Ressource nicht im JAR. + */ private void mergeDefaults(FileConfiguration file, String resourceName, File targetFile) { - try (InputStream is = plugin.getResource(resourceName); - InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { - if (is == null) { - plugin.getLogger().warning(resourceName + " nicht im Plugin-Jar gefunden, Merge übersprungen."); - return; - } + InputStream is = plugin.getResource(resourceName); + if (is == null) { + plugin.getLogger().warning(resourceName + " nicht im JAR gefunden."); + return; + } + try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { FileConfiguration defaults = YamlConfiguration.loadConfiguration(reader); boolean changed = false; for (String key : defaults.getKeys(true)) { if (!file.contains(key)) { file.set(key, defaults.get(key)); changed = true; - plugin.getLogger().info("[ConfigManager] Neuer Key in " + resourceName + " hinzugefügt: " + key); } } if (changed) { file.save(targetFile); - plugin.getLogger().info(resourceName + " wurde mit neuen Standardwerten ergänzt."); } } catch (IOException e) { - plugin.getLogger().severe("Fehler beim Mergen von " + resourceName + ": " + e.getMessage()); + plugin.getLogger().severe("Merge-Fehler " + resourceName + ": " + e.getMessage()); } } private void setConfigDefaults() { - if (!config.contains("max-doors")) config.set("max-doors", 20); - if (!config.contains("max-lamps")) config.set("max-lamps", 50); - if (!config.contains("max-noteblocks")) config.set("max-noteblocks", 10); - if (!config.contains("max-gates")) config.set("max-gates", 20); - if (!config.contains("max-trapdoors")) config.set("max-trapdoors", 20); - if (!config.contains("max-bells")) config.set("max-bells", 5); - if (!config.contains("default-note")) config.set("default-note", "PIANO"); - if (!config.contains("double-note-enabled")) config.set("double-note-enabled", true); - if (!config.contains("double-note-delay-ms")) config.set("double-note-delay-ms", 1000); - if (!config.contains("motion-detection-radius")) config.set("motion-detection-radius", 5.0); - if (!config.contains("motion-close-delay-ms")) config.set("motion-close-delay-ms", 5000); + def(config, "max-doors", 20); + def(config, "max-lamps", 50); + def(config, "max-noteblocks", 10); + def(config, "max-gates", 20); + def(config, "max-trapdoors", 20); + def(config, "max-bells", 5); + def(config, "default-note", "PIANO"); + def(config, "double-note-enabled", true); + def(config, "double-note-delay-ms", 1000); + def(config, "motion-detection-radius", 5.0); + def(config, "motion-close-delay-ms", 5000); + def(config, "motion-trigger-cooldown-ms", 2000); + + // Sounds (NEU) + def(config, "sounds.enabled", true); + def(config, "sounds.door-open", "BLOCK_WOODEN_DOOR_OPEN"); + def(config, "sounds.door-close", "BLOCK_WOODEN_DOOR_CLOSE"); + def(config, "sounds.iron-door-open", "BLOCK_IRON_DOOR_OPEN"); + def(config, "sounds.iron-door-close", "BLOCK_IRON_DOOR_CLOSE"); + def(config, "sounds.lamp-on", "BLOCK_LEVER_CLICK"); + def(config, "sounds.lamp-off", "BLOCK_LEVER_CLICK"); + saveConfig(); } private void setLangDefaults() { - if (!lang.contains("tueren-geoeffnet")) lang.set("tueren-geoeffnet", "§aTüren wurden geöffnet."); - if (!lang.contains("tueren-geschlossen")) lang.set("tueren-geschlossen", "§cTüren wurden geschlossen."); - if (!lang.contains("max-tueren-erreicht")) lang.set("max-tueren-erreicht", "§cMaximale Anzahl an Türen erreicht."); - - if (!lang.contains("lampen-eingeschaltet")) lang.set("lampen-eingeschaltet", "§aLampen wurden eingeschaltet."); - if (!lang.contains("lampen-ausgeschaltet")) lang.set("lampen-ausgeschaltet", "§cLampen wurden ausgeschaltet."); - if (!lang.contains("max-lampen-erreicht")) lang.set("max-lampen-erreicht", "§cMaximale Anzahl an Lampen erreicht."); - - if (!lang.contains("notenblock-ausgeloest")) lang.set("notenblock-ausgeloest", "§aNotenblock-Klingel wurde ausgelöst."); - if (!lang.contains("max-notenbloecke-erreicht")) lang.set("max-notenbloecke-erreicht", "§cMaximale Anzahl an Notenblöcken erreicht."); - - if (!lang.contains("gates-geoeffnet")) lang.set("gates-geoeffnet", "§aZauntore wurden geöffnet."); - if (!lang.contains("gates-geschlossen")) lang.set("gates-geschlossen", "§cZauntore wurden geschlossen."); - if (!lang.contains("max-gates-erreicht")) lang.set("max-gates-erreicht", "§cMaximale Anzahl an Zauntoren erreicht."); - - if (!lang.contains("fallturen-geoeffnet")) lang.set("fallturen-geoeffnet", "§aFalltüren wurden geöffnet."); - if (!lang.contains("fallturen-geschlossen")) lang.set("fallturen-geschlossen", "§cFalltüren wurden geschlossen."); - if (!lang.contains("max-fallturen-erreicht")) lang.set("max-fallturen-erreicht", "§cMaximale Anzahl an Falltüren erreicht."); - - if (!lang.contains("glocke-gelaeutet")) lang.set("glocke-gelaeutet", "§eGlocke wurde geläutet."); - if (!lang.contains("max-glocken-erreicht")) lang.set("max-glocken-erreicht", "§cMaximale Anzahl an Glocken erreicht."); - - if (!lang.contains("bloecke-umgeschaltet")) lang.set("bloecke-umgeschaltet", "§eBlöcke wurden umgeschaltet."); - if (!lang.contains("keine-bloecke-verbunden")) lang.set("keine-bloecke-verbunden", "§cKeine Blöcke sind verbunden."); - if (!lang.contains("block-verbunden")) lang.set("block-verbunden", "§aBlock verbunden."); - if (!lang.contains("block-bereits-verbunden")) lang.set("block-bereits-verbunden", "§cBlock ist bereits verbunden."); - if (!lang.contains("controller-platziert")) lang.set("controller-platziert", "§aController platziert."); - if (!lang.contains("controller-entfernt")) lang.set("controller-entfernt", "§cController entfernt."); - if (!lang.contains("instrument-gesetzt")) lang.set("instrument-gesetzt", "§aDein Notenblock-Instrument wurde auf %s gesetzt."); - if (!lang.contains("ungueltiges-instrument")) lang.set("ungueltiges-instrument", "§cUngültiges Instrument! Verwende: /bc note "); - if (!lang.contains("konfiguration-reloaded")) lang.set("konfiguration-reloaded", "§aKonfiguration und Daten erfolgreich neu geladen!"); - if (!lang.contains("keine-berechtigung")) lang.set("keine-berechtigung", "§cDu hast keine Berechtigung für diesen Befehl!"); - if (!lang.contains("kolben-ausgefahren")) lang.set("kolben-ausgefahren", "§6[ButtonControl] §7Kolben wurden ausgefahren."); - if (!lang.contains("kolben-eingefahren")) lang.set("kolben-eingefahren", "§6[ButtonControl] §7Kolben wurden eingezogen."); - if (!lang.contains("max-kolben-erreicht")) lang.set("max-kolben-erreicht", "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht."); - + // Türen (Holz) + def(lang, "tueren-geoeffnet", "§aTüren wurden geöffnet."); + def(lang, "tueren-geschlossen", "§cTüren wurden geschlossen."); + def(lang, "max-tueren-erreicht", "§cMaximale Anzahl an Türen erreicht."); + // Eisentüren (NEU) + def(lang, "eisentueren-geoeffnet", "§aEisentüren wurden geöffnet."); + def(lang, "eisentueren-geschlossen", "§cEisentüren wurden geschlossen."); + def(lang, "eisenfallturen-geoeffnet", "§aEisen-Falltüren wurden geöffnet."); + def(lang, "eisenfallturen-geschlossen", "§cEisen-Falltüren wurden geschlossen."); + // Zauntore + def(lang, "gates-geoeffnet", "§aZauntore wurden geöffnet."); + def(lang, "gates-geschlossen", "§cZauntore wurden geschlossen."); + def(lang, "max-gates-erreicht", "§cMaximale Anzahl an Zauntoren erreicht."); + // Falltüren + def(lang, "fallturen-geoeffnet", "§aFalltüren wurden geöffnet."); + def(lang, "fallturen-geschlossen", "§cFalltüren wurden geschlossen."); + def(lang, "max-fallturen-erreicht", "§cMaximale Anzahl an Falltüren erreicht."); + // Lampen + def(lang, "lampen-eingeschaltet", "§aLampen wurden eingeschaltet."); + def(lang, "lampen-ausgeschaltet", "§cLampen wurden ausgeschaltet."); + def(lang, "max-lampen-erreicht", "§cMaximale Anzahl an Lampen erreicht."); + // Glocken + def(lang, "glocke-gelaeutet", "§aGlocke wurde geläutet."); + def(lang, "max-glocken-erreicht", "§cMaximale Anzahl an Glocken erreicht."); + // Notenblöcke + def(lang, "notenblock-ausgeloest", "§aNotenblock-Klingel wurde ausgelöst."); + def(lang, "instrument-gesetzt", "§aDein Instrument wurde auf %s gesetzt."); + def(lang, "ungueltiges-instrument", "§cUngültiges Instrument! /bc note "); + def(lang, "max-notenbloecke-erreicht", "§cMaximale Anzahl an Notenblöcken erreicht."); + // Kolben (vorbereitet) + def(lang, "kolben-ausgefahren", "§6[ButtonControl] §7Kolben wurden ausgefahren."); + def(lang, "kolben-eingefahren", "§6[ButtonControl] §7Kolben wurden eingezogen."); + def(lang, "max-kolben-erreicht", "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht."); + // Controller + def(lang, "block-verbunden", "§aBlock verbunden."); + def(lang, "block-bereits-verbunden", "§cBlock ist bereits verbunden."); + def(lang, "block-verbindung-entfernt", "§7Verbindung zu abgebautem Block automatisch entfernt."); + def(lang, "keine-bloecke-verbunden", "§cKeine Blöcke sind verbunden."); + def(lang, "bloecke-umgeschaltet", "§eBlöcke wurden umgeschaltet."); + def(lang, "controller-platziert", "§aController platziert."); + def(lang, "controller-entfernt", "§cController entfernt."); + def(lang, "controller-umbenannt", "§aController umbenannt zu: §f%s"); // NEU + // Trust & Berechtigungen + def(lang, "keine-berechtigung", "§cDu hast keine Berechtigung!"); + def(lang, "keine-berechtigung-controller", "§cDu darfst diesen Controller nicht benutzen!"); + def(lang, "nur-besitzer-abbauen", "§cNur der Besitzer kann diesen Controller verwalten!"); + def(lang, "spieler-nicht-gefunden", "§cSpieler nicht gefunden."); + def(lang, "status-geandert", "§6[BC] §7Controller ist nun %s§7."); + def(lang, "trust-hinzugefuegt", "§a%s darf diesen Controller nun benutzen."); + def(lang, "trust-entfernt", "§c%s wurde das Vertrauen entzogen."); + def(lang, "kein-controller-im-blick", "§cBitte sieh einen Controller direkt an!"); + // System + // FIX: Korrekte Key-Bezeichnung (war fälschlich "konfiguration-reloaded") + def(lang, "konfiguration-neugeladen", "§aKonfiguration erfolgreich neu geladen!"); saveLang(); } + private void def(FileConfiguration cfg, String key, Object value) { + if (!cfg.contains(key)) cfg.set(key, value); + } + public void reloadConfig() { config = YamlConfiguration.loadConfiguration(configFile); - lang = YamlConfiguration.loadConfiguration(langFile); + lang = YamlConfiguration.loadConfiguration(langFile); mergeDefaults(config, "config.yml", configFile); - mergeDefaults(lang, "lang.yml", langFile); + mergeDefaults(lang, "lang.yml", langFile); setConfigDefaults(); setLangDefaults(); } - public FileConfiguration getConfig() { - return config; - } + public FileConfiguration getConfig() { return config; } - public int getMaxDoors() { return config.getInt("max-doors", 20); } - public int getMaxLamps() { return config.getInt("max-lamps", 50); } - public int getMaxNoteBlocks() { return config.getInt("max-noteblocks", 10); } - public int getMaxGates() { return config.getInt("max-gates", getMaxDoors()); } - public int getMaxTrapdoors() { return config.getInt("max-trapdoors", getMaxDoors()); } - public int getMaxBells() { return config.getInt("max-bells", 5); } + public int getMaxDoors() { return config.getInt("max-doors", 20); } + public int getMaxLamps() { return config.getInt("max-lamps", 50); } + public int getMaxNoteBlocks() { return config.getInt("max-noteblocks", 10); } + public int getMaxGates() { return config.getInt("max-gates", 20); } + public int getMaxTrapdoors() { return config.getInt("max-trapdoors", 20); } + public int getMaxBells() { return config.getInt("max-bells", 5); } public String getMessage(String key) { - return lang.getString(key, "§cNachricht nicht gefunden: " + key); + return lang.getString(key, "§cNachricht fehlt: " + key); } public void saveConfig() { - try { - config.save(configFile); - } catch (IOException e) { - plugin.getLogger().severe("Konnte config.yml nicht speichern: " + e.getMessage()); - } + try { config.save(configFile); } + catch (IOException e) { plugin.getLogger().severe("config.yml Fehler: " + e.getMessage()); } } public void saveLang() { - try { - lang.save(langFile); - } catch (IOException e) { - plugin.getLogger().severe("Konnte lang.yml nicht speichern: " + e.getMessage()); - } + try { lang.save(langFile); } + catch (IOException e) { plugin.getLogger().severe("lang.yml Fehler: " + e.getMessage()); } } } \ No newline at end of file diff --git a/src/main/java/viper/DataManager.java b/src/main/java/viper/DataManager.java index 528d99a..5bb61a2 100644 --- a/src/main/java/viper/DataManager.java +++ b/src/main/java/viper/DataManager.java @@ -4,6 +4,7 @@ import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -21,9 +22,7 @@ public class DataManager { private void loadData() { dataFile = new File(plugin.getDataFolder(), "data.yml"); - if (!dataFile.exists()) { - plugin.saveResource("data.yml", false); - } + if (!dataFile.exists()) plugin.saveResource("data.yml", false); data = YamlConfiguration.loadConfiguration(dataFile); } @@ -31,38 +30,154 @@ public class DataManager { data = YamlConfiguration.loadConfiguration(dataFile); } - public String getButtonIdForLocation(String location) { - return getButtonIdForPlacedController(location); - } + // ----------------------------------------------------------------------- + // Zugriff & Berechtigungen + // ----------------------------------------------------------------------- public boolean canAccess(String buttonId, UUID playerUUID) { if (isPublic(buttonId)) return true; if (isOwner(buttonId, playerUUID)) return true; - List trusted = data.getStringList("trust." + buttonId); - return trusted.contains(playerUUID.toString()); + return data.getStringList("trust." + buttonId).contains(playerUUID.toString()); } public boolean isOwner(String buttonId, UUID playerUUID) { - // Wir prüfen direkt im globalen Pfad der Buttons - return data.contains("players." + playerUUID.toString() + ".buttons." + buttonId); + return data.contains("players." + playerUUID + ".buttons." + buttonId) + || data.contains("players." + playerUUID + ".placed-controllers"); + // Zweite Bedingung: prüft ob irgendein placed-controller dieser UUID die buttonId enthält + } + + // ----------------------------------------------------------------------- + // Controller-Verwaltung + // ----------------------------------------------------------------------- + + public String getButtonIdForLocation(String location) { + return getButtonIdForPlacedController(location); } public void registerController(String location, UUID ownerUUID, String buttonId) { - addPlacedController(ownerUUID.toString(), location, buttonId); + data.set("players." + ownerUUID + ".placed-controllers." + location, buttonId); + // Leere buttons-Liste anlegen damit isOwner() sofort greift + if (!data.contains("players." + ownerUUID + ".buttons." + buttonId)) { + data.set("players." + ownerUUID + ".buttons." + buttonId, new ArrayList<>()); + } + saveData(); } public void removeController(String location) { if (data.getConfigurationSection("players") == null) return; - for (String playerUUID : data.getConfigurationSection("players").getKeys(false)) { - String path = "players." + playerUUID + ".placed-controllers." + location; - if (data.contains(path)) { - data.set(path, null); - } + for (String uuid : data.getConfigurationSection("players").getKeys(false)) { + String path = "players." + uuid + ".placed-controllers." + location; + if (data.contains(path)) data.set(path, null); } removeMotionSensorSettings(location); saveData(); } + public String getButtonIdForPlacedController(String location) { + if (data.getConfigurationSection("players") == null) return null; + for (String uuid : data.getConfigurationSection("players").getKeys(false)) { + String val = data.getString("players." + uuid + ".placed-controllers." + location); + if (val != null) return val; + } + return null; + } + + public List getAllPlacedControllers() { + List result = new ArrayList<>(); + if (data.getConfigurationSection("players") == null) return result; + for (String uuid : data.getConfigurationSection("players").getKeys(false)) { + var sec = data.getConfigurationSection("players." + uuid + ".placed-controllers"); + if (sec != null) result.addAll(sec.getKeys(false)); + } + return result; + } + + // ----------------------------------------------------------------------- + // Verbundene Blöcke + // ----------------------------------------------------------------------- + + public void setConnectedBlocks(String playerUUID, String buttonId, List blocks) { + data.set("players." + playerUUID + ".buttons." + buttonId, blocks); + saveData(); + } + + public List getConnectedBlocks(String buttonId) { + if (data.getConfigurationSection("players") == null) return new ArrayList<>(); + for (String uuid : data.getConfigurationSection("players").getKeys(false)) { + String path = "players." + uuid + ".buttons." + buttonId; + if (data.contains(path)) return data.getStringList(path); + } + return new ArrayList<>(); + } + + /** + * Entfernt eine Block-Location aus ALLEN Verbindungslisten aller Controller. + * Wird aufgerufen wenn ein verbundener Block abgebaut wird. + */ + public boolean removeFromAllConnectedBlocks(String locStr) { + if (data.getConfigurationSection("players") == null) return false; + boolean changed = false; + for (String uuid : data.getConfigurationSection("players").getKeys(false)) { + var buttonsSection = data.getConfigurationSection("players." + uuid + ".buttons"); + if (buttonsSection == null) continue; + for (String buttonId : buttonsSection.getKeys(false)) { + String path = "players." + uuid + ".buttons." + buttonId; + List blocks = data.getStringList(path); + if (blocks.remove(locStr)) { + data.set(path, blocks); + changed = true; + } + } + } + if (changed) saveData(); + return changed; + } + + // ----------------------------------------------------------------------- + // Controller-Name (NEU) + // ----------------------------------------------------------------------- + + public void setControllerName(String buttonId, String name) { + data.set("names." + buttonId, name); + saveData(); + } + + public String getControllerName(String buttonId) { + return data.getString("names." + buttonId); + } + + // ----------------------------------------------------------------------- + // Zeitplan (NEU) + // ----------------------------------------------------------------------- + + public void setScheduleOpenTime(String buttonId, long ticks) { + data.set("schedules." + buttonId + ".open-time", ticks); + saveData(); + } + + public long getScheduleOpenTime(String buttonId) { + return data.getLong("schedules." + buttonId + ".open-time", -1); + } + + public void setScheduleCloseTime(String buttonId, long ticks) { + data.set("schedules." + buttonId + ".close-time", ticks); + saveData(); + } + + public long getScheduleCloseTime(String buttonId) { + return data.getLong("schedules." + buttonId + ".close-time", -1); + } + + /** Entfernt den kompletten Zeitplan für einen Controller. */ + public void clearSchedule(String buttonId) { + data.set("schedules." + buttonId, null); + saveData(); + } + + // ----------------------------------------------------------------------- + // Trust & Public/Private + // ----------------------------------------------------------------------- + public void addTrustedPlayer(String buttonId, UUID targetUUID) { List trusted = data.getStringList("trust." + buttonId); if (!trusted.contains(targetUUID.toString())) { @@ -88,60 +203,23 @@ public class DataManager { return data.getBoolean("public-status." + buttonId, false); } - // Speichert die Blöcke für eine ID unter einem spezifischen Spieler - public void setConnectedBlocks(String playerUUID, String buttonId, List blocks) { - data.set("players." + playerUUID + ".buttons." + buttonId, blocks); - saveData(); - } - - public void addPlacedController(String playerUUID, String location, String buttonId) { - // Verhindert doppelte Punkte im Pfad, falls die Location Punkte enthält - data.set("players." + playerUUID + ".placed-controllers." + location, buttonId); - saveData(); - } - - public String getButtonIdForPlacedController(String location) { - if (data.getConfigurationSection("players") == null) return null; - for (String playerUUID : data.getConfigurationSection("players").getKeys(false)) { - String buttonId = data.getString("players." + playerUUID + ".placed-controllers." + location); - if (buttonId != null) return buttonId; - } - return null; - } - - // VERBESSERT: Sucht gezielt nach den Blöcken für diese ID - public List getConnectedBlocks(String buttonId) { - if (data.getConfigurationSection("players") == null) return new ArrayList<>(); - - for (String playerUUID : data.getConfigurationSection("players").getKeys(false)) { - String path = "players." + playerUUID + ".buttons." + buttonId; - if (data.contains(path)) { - return data.getStringList(path); - } - } - return new ArrayList<>(); // Niemals null zurückgeben, um Fehler im Listener zu vermeiden - } - - public List getAllPlacedControllers() { - List allControllers = new ArrayList<>(); - if (data.getConfigurationSection("players") == null) return allControllers; - for (String playerUUID : data.getConfigurationSection("players").getKeys(false)) { - if (data.getConfigurationSection("players." + playerUUID + ".placed-controllers") != null) { - allControllers.addAll(data.getConfigurationSection("players." + playerUUID + ".placed-controllers").getKeys(false)); - } - } - return allControllers; - } + // ----------------------------------------------------------------------- + // Instrumente + // ----------------------------------------------------------------------- public void setPlayerInstrument(UUID playerUUID, String instrument) { - data.set("players." + playerUUID.toString() + ".instrument", instrument); + data.set("players." + playerUUID + ".instrument", instrument); saveData(); } public String getPlayerInstrument(UUID playerUUID) { - return data.getString("players." + playerUUID.toString() + ".instrument"); + return data.getString("players." + playerUUID + ".instrument"); } + // ----------------------------------------------------------------------- + // Motion-Sensor-Einstellungen + // ----------------------------------------------------------------------- + public void setMotionSensorRadius(String location, double radius) { data.set("motion-sensors." + location + ".radius", radius); saveData(); @@ -165,11 +243,28 @@ public class DataManager { saveData(); } + // ----------------------------------------------------------------------- + // Speichern – asynchron + // ----------------------------------------------------------------------- + + /** + * Serialisiert die Daten synchron (thread-safe), schreibt dann asynchron auf Disk. + * Verhindert I/O-Lags auf dem Main-Thread. + */ public void saveData() { + final String serialized; try { - data.save(dataFile); - } catch (IOException e) { - plugin.getLogger().severe("Konnte data.yml nicht speichern: " + e.getMessage()); + serialized = data.saveToString(); + } catch (Exception e) { + plugin.getLogger().severe("Serialisierungsfehler data.yml: " + e.getMessage()); + return; } + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { + try (FileWriter fw = new FileWriter(dataFile, false)) { + fw.write(serialized); + } catch (IOException e) { + plugin.getLogger().severe("Konnte data.yml nicht speichern: " + e.getMessage()); + } + }); } } \ No newline at end of file diff --git a/src/main/java/viper/ScheduleGUI.java b/src/main/java/viper/ScheduleGUI.java new file mode 100644 index 0000000..9f44c0c --- /dev/null +++ b/src/main/java/viper/ScheduleGUI.java @@ -0,0 +1,216 @@ +package viper; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +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.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * GUI zur Konfiguration der zeitgesteuerten Automatisierung eines Controllers. + * + * Layout (9×3 = 27 Slots): + * Slot 10 – Öffnungszeit (LIME_DYE / Sonne) ← Links/Rechts: ±1h | Shift: ±15min + * Slot 13 – Aktivierung an/aus (LEVER) + * Slot 16 – Schließzeit (RED_DYE / Mond) ← Links/Rechts: ±1h | Shift: ±15min + * Slot 22 – Speichern & Schließen (EMERALD) + * + * Zeit wird als Ingame-Ticks gespeichert (0–23999). + * ticksToTime() / timeToTicks() in ButtonControl konvertieren nach "HH:MM". + */ +public class ScheduleGUI implements Listener { + private final ButtonControl plugin; + private final DataManager dataManager; + private final Player player; + private final String buttonId; + private final Inventory inv; + + // Aktuelle Werte während die GUI offen ist + private long openTime; + private long closeTime; + private boolean enabled; + + public ScheduleGUI(ButtonControl plugin, Player player, String buttonId) { + this.plugin = plugin; + this.dataManager = plugin.getDataManager(); + this.player = player; + this.buttonId = buttonId; + this.inv = Bukkit.createInventory(null, 27, "§6Zeitplan-Einstellungen"); + + // Gespeicherte Werte laden (oder Standardwerte) + long savedOpen = dataManager.getScheduleOpenTime(buttonId); + long savedClose = dataManager.getScheduleCloseTime(buttonId); + this.openTime = savedOpen >= 0 ? savedOpen : plugin.timeToTicks(7, 0); // 07:00 + this.closeTime = savedClose >= 0 ? savedClose : plugin.timeToTicks(19, 0); // 19:00 + this.enabled = savedOpen >= 0; + + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + public void open() { + renderItems(); + player.openInventory(inv); + } + + // ----------------------------------------------------------------------- + // GUI aufbauen + // ----------------------------------------------------------------------- + + private void renderItems() { + // Füllung + ItemStack filler = makeItem(Material.GRAY_STAINED_GLASS_PANE, ChatColor.RESET + ""); + for (int i = 0; i < 27; i++) inv.setItem(i, filler); + + // Slot 10 – Öffnungszeit + inv.setItem(10, makeTimeItem( + Material.LIME_DYE, + "§a§lÖffnungszeit", + openTime, + "§7Linksklick: §f+1 Stunde", + "§7Rechtsklick: §f−1 Stunde", + "§7Shift+Links: §f+15 Minuten", + "§7Shift+Rechts: §f−15 Minuten" + )); + + // Slot 13 – Aktivierung an/aus + Material leverMat = enabled ? Material.LEVER : Material.DEAD_BUSH; + String leverName = enabled ? "§a§lZeitplan aktiv" : "§c§lZeitplan deaktiviert"; + String leverDesc = enabled ? "§7Klick zum §cDeaktivieren" : "§7Klick zum §aAktivieren"; + inv.setItem(13, makeItem(leverMat, leverName, leverDesc, "§8(Zeitplan wird gespeichert)")); + + // Slot 16 – Schließzeit + inv.setItem(16, makeTimeItem( + Material.RED_DYE, + "§c§lSchließzeit", + closeTime, + "§7Linksklick: §f+1 Stunde", + "§7Rechtsklick: §f−1 Stunde", + "§7Shift+Links: §f+15 Minuten", + "§7Shift+Rechts: §f−15 Minuten" + )); + + // Slot 22 – Speichern + inv.setItem(22, makeItem(Material.EMERALD, + "§a§lSpeichern & Schließen", + "§7Speichert den aktuellen Zeitplan.")); + } + + private ItemStack makeTimeItem(Material mat, String name, long ticks, String... loreLines) { + String timeStr = plugin.ticksToTime(ticks); + List lore = new ArrayList<>(); + lore.add("§e§l" + timeStr + " Uhr §7(Ingame)"); + lore.add(""); + lore.addAll(Arrays.asList(loreLines)); + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta.setDisplayName(name); + meta.setLore(lore); + item.setItemMeta(meta); + } + return item; + } + + private ItemStack makeItem(Material mat, String name, String... lore) { + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta.setDisplayName(name); + meta.setLore(Arrays.asList(lore)); + item.setItemMeta(meta); + } + return item; + } + + // ----------------------------------------------------------------------- + // Event-Handler + // ----------------------------------------------------------------------- + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (!event.getInventory().equals(inv)) return; + if (!event.getWhoClicked().equals(player)) return; + event.setCancelled(true); + + ItemStack clicked = event.getCurrentItem(); + if (clicked == null || clicked.getType() == Material.GRAY_STAINED_GLASS_PANE) return; + + int slot = event.getRawSlot(); + // Nur Klicks in unserer GUI (0–26) verarbeiten + if (slot < 0 || slot > 26) return; + + // Schrittgröße: Shift = 15 Min (250 Ticks), sonst 1 Std (1000 Ticks) + long step = event.isShiftClick() ? 250L : 1000L; + + if (slot == 10) { + // Öffnungszeit anpassen + if (event.isLeftClick()) openTime = (openTime + step + 24000) % 24000; + if (event.isRightClick()) openTime = (openTime - step + 24000) % 24000; + inv.setItem(10, makeTimeItem(Material.LIME_DYE, "§a§lÖffnungszeit", openTime, + "§7Linksklick: §f+1 Stunde", "§7Rechtsklick: §f−1 Stunde", + "§7Shift+Links: §f+15 Minuten", "§7Shift+Rechts: §f−15 Minuten")); + + } else if (slot == 13) { + // Aktivierung umschalten + enabled = !enabled; + renderItems(); + + } else if (slot == 16) { + // Schließzeit anpassen + if (event.isLeftClick()) closeTime = (closeTime + step + 24000) % 24000; + if (event.isRightClick()) closeTime = (closeTime - step + 24000) % 24000; + inv.setItem(16, makeTimeItem(Material.RED_DYE, "§c§lSchließzeit", closeTime, + "§7Linksklick: §f+1 Stunde", "§7Rechtsklick: §f−1 Stunde", + "§7Shift+Links: §f+15 Minuten", "§7Shift+Rechts: §f−15 Minuten")); + + } else if (slot == 22) { + // Speichern + save(); + player.closeInventory(); + } + } + + @EventHandler + public void onInventoryDrag(InventoryDragEvent event) { + if (event.getInventory().equals(inv) && event.getWhoClicked().equals(player)) + event.setCancelled(true); + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (!event.getPlayer().equals(player) || !event.getInventory().equals(inv)) return; + InventoryClickEvent.getHandlerList().unregister(this); + InventoryDragEvent.getHandlerList().unregister(this); + InventoryCloseEvent.getHandlerList().unregister(this); + } + + // ----------------------------------------------------------------------- + // Speichern + // ----------------------------------------------------------------------- + + private void save() { + if (enabled) { + dataManager.setScheduleOpenTime(buttonId, openTime); + dataManager.setScheduleCloseTime(buttonId, closeTime); + player.sendMessage("§a[BC] §7Zeitplan gespeichert: §aÖffnet §7um §e" + + plugin.ticksToTime(openTime) + + " §7· §cSchließt §7um §e" + + plugin.ticksToTime(closeTime)); + } else { + dataManager.clearSchedule(buttonId); + player.sendMessage("§7[BC] Zeitplan deaktiviert."); + } + } + +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 96bcb7a..0346b79 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,4 +1,6 @@ -# Maximale Anzahl an steuerbaren Blöcken pro Controller +# ButtonControl – Konfiguration + +# ── Maximale Anzahl verbundener Blöcke pro Controller ────────────────────── max-doors: 20 max-lamps: 50 max-noteblocks: 10 @@ -6,13 +8,28 @@ max-gates: 20 max-trapdoors: 20 max-bells: 5 -# Standard-Instrument für Notenblöcke +# ── Notenblöcke ───────────────────────────────────────────────────────────── default-note: "PIANO" - -# Doppelter Notenblock-Ton double-note-enabled: true double-note-delay-ms: 1000 -# Bewegungsmelder-Einstellungen +# ── Bewegungsmelder ────────────────────────────────────────────────────────── +# Gilt als Standardwert wenn für einen Sensor nichts individuell gesetzt wurde motion-detection-radius: 5.0 -motion-close-delay-ms: 5000 \ No newline at end of file +motion-close-delay-ms: 5000 +# Cooldown: wie lange nach dem Schließen der Sensor nicht erneut auslöst +motion-trigger-cooldown-ms: 2000 + +# ── Sounds beim Öffnen/Schließen (NEU) ────────────────────────────────────── +# Auf false setzen um alle Controller-Sounds zu deaktivieren +sounds: + enabled: true + # Holztüren, Zauntore, Falltüren + door-open: BLOCK_WOODEN_DOOR_OPEN + door-close: BLOCK_WOODEN_DOOR_CLOSE + # Eisentüren und Eisenfalltüren + iron-door-open: BLOCK_IRON_DOOR_OPEN + iron-door-close: BLOCK_IRON_DOOR_CLOSE + # Redstone-Lampen + lamp-on: BLOCK_LEVER_CLICK + lamp-off: BLOCK_LEVER_CLICK \ No newline at end of file diff --git a/src/main/resources/lang.yml b/src/main/resources/lang.yml index e9509b4..dc0eb28 100644 --- a/src/main/resources/lang.yml +++ b/src/main/resources/lang.yml @@ -1,57 +1,64 @@ -# lang.yml - ButtonControl Nachrichten +# lang.yml – ButtonControl Nachrichten -# --- Bestehende Nachrichten (Türen/Tore/Falltüren) --- +# ── Holztüren ──────────────────────────────────────────────────────────────── tueren-geoeffnet: "§aTüren wurden geöffnet." tueren-geschlossen: "§cTüren wurden geschlossen." max-tueren-erreicht: "§cMaximale Anzahl an Türen erreicht." +# ── Eisentüren (NEU) ───────────────────────────────────────────────────────── +eisentueren-geoeffnet: "§aEisentüren wurden geöffnet." +eisentueren-geschlossen: "§cEisentüren wurden geschlossen." +eisenfallturen-geoeffnet: "§aEisen-Falltüren wurden geöffnet." +eisenfallturen-geschlossen: "§cEisen-Falltüren wurden geschlossen." + +# ── Zauntore ───────────────────────────────────────────────────────────────── gates-geoeffnet: "§aZauntore wurden geöffnet." gates-geschlossen: "§cZauntore wurden geschlossen." max-gates-erreicht: "§cMaximale Anzahl an Zauntoren erreicht." +# ── Falltüren ──────────────────────────────────────────────────────────────── fallturen-geoeffnet: "§aFalltüren wurden geöffnet." fallturen-geschlossen: "§cFalltüren wurden geschlossen." max-fallturen-erreicht: "§cMaximale Anzahl an Falltüren erreicht." -# --- Lampen & Glocken --- +# ── Lampen & Glocken ───────────────────────────────────────────────────────── lampen-eingeschaltet: "§aLampen wurden eingeschaltet." lampen-ausgeschaltet: "§cLampen wurden ausgeschaltet." max-lampen-erreicht: "§cMaximale Anzahl an Lampen erreicht." glocke-gelaeutet: "§aGlocke wurde geläutet." max-glocken-erreicht: "§cMaximale Anzahl an Glocken erreicht." -# --- Notenblöcke & Instrumente --- +# ── Notenblöcke ────────────────────────────────────────────────────────────── notenblock-ausgeloest: "§aNotenblock-Klingel wurde ausgelöst." -instrument-gesetzt: "§aDein Notenblock-Instrument wurde auf %s gesetzt." +instrument-gesetzt: "§aDein Instrument wurde auf %s gesetzt." ungueltiges-instrument: "§cUngültiges Instrument! Verwende: /bc note " max-notenbloecke-erreicht: "§cMaximale Anzahl an Notenblöcken erreicht." -# --- Kolben (Erweiterung) --- +# ── Kolben (vorbereitet für zukünftige Erweiterung) ───────────────────────── kolben-ausgefahren: "§6[ButtonControl] §7Kolben wurden ausgefahren." -kolben-eingefahren: "§6[ButtonControl] §7Kolben wurden eingefahren." +kolben-eingefahren: "§6[ButtonControl] §7Kolben wurden eingezogen." max-kolben-erreicht: "§6[ButtonControl] §7Maximale Anzahl an Kolben erreicht." -# --- Controller & Verbindung --- +# ── Controller & Verbindung ────────────────────────────────────────────────── block-verbunden: "§aBlock verbunden." block-bereits-verbunden: "§cBlock ist bereits verbunden." +block-verbindung-entfernt: "§7Verbindung zu abgebautem Block automatisch entfernt." keine-bloecke-verbunden: "§cKeine Blöcke sind verbunden." bloecke-umgeschaltet: "§eBlöcke wurden umgeschaltet." controller-platziert: "§aController platziert." controller-entfernt: "§cController entfernt." +controller-umbenannt: "§aController umbenannt zu: §f%s" -# --- Trust- & Sicherheitssystem --- +# ── Trust- & Sicherheitssystem ─────────────────────────────────────────────── keine-berechtigung: "§cDu hast keine Berechtigung für diesen Befehl!" -keine-berechtigung-controller: "§cDu hast keine Berechtigung, diesen Controller zu benutzen!" -nur-besitzer-abbauen: "§cNur der Besitzer darf diesen Controller verwalten oder abbauen!" +keine-berechtigung-controller: "§cDu darfst diesen Controller nicht benutzen!" +nur-besitzer-abbauen: "§cNur der Besitzer kann diesen Controller verwalten!" spieler-nicht-gefunden: "§cDieser Spieler wurde nicht gefunden." +# %s = "§aÖffentlich" oder "§cPrivat" +status-geandert: "§6[BC] §7Controller ist nun %s§7." +trust-hinzugefuegt: "§a%s darf diesen Controller nun benutzen." +trust-entfernt: "§c%s wurde das Vertrauen entzogen." +kein-controller-im-blick: "§cBitte sieh einen Controller direkt an!" -# %s wird durch "§aÖffentlich" oder "§cPrivat" ersetzt -status-geandert: "§6[ButtonControl] §7Der Controller ist nun %s§7." - -# Trust Nachrichten -trust-hinzugefuegt: "§aDu hast %s Vertrauen für diesen Controller gegeben." -trust-entfernt: "§cDu hast %s das Vertrauen für diesen Controller entzogen." -kein-controller-im-blick: "§cDu musst einen Controller (Button, Haken oder Sensor) direkt ansehen!" - -# --- System --- +# ── System ─────────────────────────────────────────────────────────────────── konfiguration-neugeladen: "§aKonfiguration und Daten erfolgreich neu geladen!" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e5b2323..31823a3 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,26 +1,32 @@ name: ButtonControl -version: 1.5 +version: 1.6 main: viper.ButtonControl api-version: 1.21 author: M_Viper -description: Ein Plugin, um Türen, Redstone-Lampen und Notenblöcke mit einem Button, Tageslichtsensor oder Bewegungsmelder zu steuern. +description: > + Steuert Türen, Eisentüren, Lampen, Notenblöcke und Glocken mit Buttons, + Tageslichtsensoren, Bewegungsmeldern, Teppich-Sensoren und Schildern. + Unterstützt Zeitpläne, Trust-System und Admin-Bypass. commands: bc: description: Hauptbefehl für ButtonControl - usage: /bc + usage: /bc aliases: [buttoncontrol] permissions: + buttoncontrol.admin: + description: Admin-Bypass – Zugriff auf und Verwaltung aller Controller + default: op buttoncontrol.reload: - description: Erlaubt das Neuladen der Plugin-Konfiguration + description: Konfiguration neu laden default: op buttoncontrol.note: - description: Erlaubt das Ändern des Notenblock-Instruments + description: Notenblock-Instrument ändern default: true buttoncontrol.update: - description: Erlaubt das Empfangen von Update-Benachrichtigungen + description: Update-Benachrichtigungen empfangen default: op buttoncontrol.trust: - description: Erlaubt das Verwalten von Vertrauen und Status (Public/Private) + description: Trust-System und Public/Private-Status verwalten default: true \ No newline at end of file