diff --git a/src/main/java/com/viper/autosortchest/Main.java b/src/main/java/com/viper/autosortchest/Main.java index ba8588e..5de87e4 100644 --- a/src/main/java/com/viper/autosortchest/Main.java +++ b/src/main/java/com/viper/autosortchest/Main.java @@ -2,9 +2,13 @@ package com.viper.autosortchest; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; +import org.bukkit.Particle; +import org.bukkit.Particle.DustOptions; +import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.Chest; @@ -24,11 +28,14 @@ import org.bukkit.event.block.Action; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; import java.io.File; import java.io.IOException; @@ -42,7 +49,82 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { private FileConfiguration config; private final Map> fullChestMessageTracker = new HashMap<>(); private static final long MESSAGE_COOLDOWN = 5 * 60 * 1000; // 5 Minuten in Millisekunden - private static final String CONFIG_VERSION = "1.6"; // Version erhöht für das Update + private static final String CONFIG_VERSION = "1.8"; + + // --- NEU: Update Checker Variabben --- + private boolean updateAvailable = false; + private String latestVersion = ""; + // -------------------------------------------------- + + // --- Hardcodierte Nachrichten (DE & EN) für Hilfe und Info (Safe in Java) --- + private static final String HELP_DE = + "&6&l=== AutoSortChest Hilfe ===\n" + + "&eEingangstruhe erstellen:\n" + + "&f1. Platziere ein Schild an einer Truhe.\n" + + "&f2. Schreibe:\n" + + " &7[asc]\n" + + " &7input\n" + + "&f3. Fülle die Truhe mit Items.\n" + + "&eZieltruhe erstellen:\n" + + "&f1. Platziere ein Schild an einer Truhe.\n" + + "&f2. Schreibe:\n" + + " &7[asc]\n" + + " &7ziel\n" + + "&f3. Rechtsklicke mit einem Item in der Hand.\n" + + "&eRest-Truhe (Fallback) erstellen:\n" + + "&f1. Platziere ein Schild an einer Truhe.\n" + + "&f2. Schreibe:\n" + + " &7[asc]\n" + + " &7rest\n" + + "&eBefehle:\n" + + "&f- &b/asc help &f- Zeigt diese Hilfe.\n" + + "&f- &b/asc info &f- Zeigt Plugin-Informationen.\n" + + "&f- &b/asc reload &f- Lädt die Konfiguration neu (OP).\n" + + "&6&l========================"; + + private static final String HELP_EN = + "&6&l=== AutoSortChest Help ===\n" + + "&eCreate Input Chest:\n" + + "&f1. Place a sign on a chest.\n" + + "&f2. Write:\n" + + " &7[asc]\n" + + " &7input\n" + + "&f3. Fill the chest with items.\n" + + "&eCreate Target Chest:\n" + + "&f1. Place a sign on a chest.\n" + + "&f2. Write:\n" + + " &7[asc]\n" + + " &7target\n" + + "&f3. Right-click with an item in hand.\n" + + "&eCreate Rest Chest (Fallback):\n" + + "&f1. Place a sign on a chest.\n" + + "&f2. Write:\n" + + " &7[asc]\n" + + " &7rest\n" + + "&eCommands:\n" + + "&f- &b/asc help &f- Shows this help.\n" + + "&f- &b/asc info &f- Shows plugin info.\n" + + "&f- &b/asc reload &f- Reloads the config (OP only).\n" + + "&6&l========================"; + + private static final String INFO_DE = + "&6&l=== AutoSortChest Info ===\n" + + "&ePlugin: &fAutoSortChest\n" + + "&eVersion: &f%version%\n" + + "&eConfig-Version: &f%config_version%\n" + + "&eErsteller: &f%author%\n" + + "&eBeschreibung: &fAutomatisches Sortieren von Items in Truhen.\n" + + "&6&l========================"; + + private static final String INFO_EN = + "&6&l=== AutoSortChest Info ===\n" + + "&ePlugin: &fAutoSortChest\n" + + "&eVersion: &f%version%\n" + + "&eConfig-Version: &f%config_version%\n" + + "&eAuthor: &f%author%\n" + + "&eDescription: &fAutomatically sorts items into chests.\n" + + "&6&l========================"; + // ---------------------------------------------------------------------------------- @Override public void onEnable() { @@ -82,10 +164,33 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { // Aktualisiere Konfiguration und Schilder updateConfig(); updateExistingSigns(); - + // ERWEITERUNG: Migration alter Daten in das neue Listen-System migrateInputChests(); + // --- NEU: Update Checker Start (Ganz oben in onEnable) --- + new UpdateChecker(this, 131309).getVersion(version -> { + Bukkit.getScheduler().runTask(this, () -> { + if (this.getDescription().getVersion().equals(version)) { + this.updateAvailable = false; + this.latestVersion = version; + getLogger().info("Es ist kein neues Update verfügbar."); + } else { + this.updateAvailable = true; + this.latestVersion = version; + + getLogger().info(" "); + getLogger().info("========================================"); + getLogger().info("Eine neue Version ist verfügbar: " + version); + getLogger().info("Deine Version: " + this.getDescription().getVersion()); + getLogger().info("Download: https://www.spigotmc.org/resources/" + 131309 + "/"); + getLogger().info("========================================"); + getLogger().info(" "); + } + }); + }); + // ---------------------------------------------------- + // ASCII ART LOGO getLogger().info(""); getLogger().info(" ___ _ _____ _ _____ _ _ "); @@ -105,6 +210,32 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { getLogger().info("AutoSortChest Plugin deaktiviert!"); } + // --- NEU: In-Game Update Nachricht (Hier platziert wie gewünscht) --- + @EventHandler + public void onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent event) { + // Nur wenn ein Update verfügbar ist + if (!updateAvailable) return; + + Player player = event.getPlayer(); + + // Nur an Admins senden + if (!isAdmin(player)) return; + + // Kurze Verzögerung (1 Sekunde), damit die Chat-Nachricht nicht im "Welcome" Spam untergeht + new BukkitRunnable() { + @Override + public void run() { + player.sendMessage(ChatColor.GOLD + "========================================"); + player.sendMessage(ChatColor.GOLD + "[AutoSortChest] " + ChatColor.YELLOW + "Es ist ein Update verfügbar!"); + player.sendMessage(ChatColor.GRAY + "Deine Version: " + ChatColor.RED + getDescription().getVersion()); + player.sendMessage(ChatColor.GRAY + "Neue Version: " + ChatColor.GREEN + latestVersion); + player.sendMessage(ChatColor.GRAY + "Download: " + ChatColor.AQUA + "https://www.spigotmc.org/resources/" + 131309 + "/"); + player.sendMessage(ChatColor.GOLD + "========================================"); + } + }.runTaskLater(this, 20L * 1); // 1 Sekunde Verzögerung + } + // -------------------------------------------------------------- + // --- NEU: Admin Prüfung --- private boolean isAdmin(Player player) { return player.isOp() || player.hasPermission("autosortchest.admin"); @@ -115,7 +246,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { private List getChestBlocks(Chest chest) { List blocks = new ArrayList<>(); InventoryHolder holder = chest.getInventory().getHolder(); - + if (holder instanceof DoubleChest) { DoubleChest doubleChest = (DoubleChest) holder; // Finde beide Blöcke der Doppeltruhe @@ -205,12 +336,29 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { getLogger().info("Setze Standardwert: debug = " + config.getBoolean("debug")); } + // --- NEU: Sprache --- + if (!config.contains("language")) { + config.set("language", defaultConfig.getString("language", "de")); + getLogger().info("Setze Standardwert: language = " + config.getString("language")); + } + // ------------------- + + // --- NEU: Effekte Konfiguration (Partikel + Sound) --- + if (!config.contains("effects")) { + config.createSection("effects"); + config.set("effects.enabled", defaultConfig.getBoolean("effects.enabled", true)); + config.set("effects.sound", defaultConfig.getBoolean("effects.sound", true)); + config.set("effects.type", defaultConfig.getString("effects.type", "DUST")); + getLogger().info("Setze Standardwerte für effects"); + } + // ---------------------------------- + // Prüfe und setze sign-colors.input if (!config.contains("sign-colors.input")) { config.createSection("sign-colors.input"); config.set("sign-colors.input.line1", defaultConfig.getString("sign-colors.input.line1", "&6")); config.set("sign-colors.input.line2", defaultConfig.getString("sign-colors.input.line2", "&0")); - config.set("sign-colors.input.line4", defaultConfig.getString("sign-colors.input.line4", "&1")); // Korrigiert auf &1 basierend auf user Config + config.set("sign-colors.input.line4", defaultConfig.getString("sign-colors.input.line4", "&1")); getLogger().info("Setze Standardwerte für sign-colors.input"); } else { if (!config.contains("sign-colors.input.line1")) { @@ -233,7 +381,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { config.set("sign-colors.target.line1", defaultConfig.getString("sign-colors.target.line1", "&6")); config.set("sign-colors.target.line2", defaultConfig.getString("sign-colors.target.line2", "&0")); config.set("sign-colors.target.line3", defaultConfig.getString("sign-colors.target.line3", "&f")); - config.set("sign-colors.target.line4", defaultConfig.getString("sign-colors.target.line4", "&1")); // Korrigiert auf &1 + config.set("sign-colors.target.line4", defaultConfig.getString("sign-colors.target.line4", "&1")); getLogger().info("Setze Standardwerte für sign-colors.target"); } else { if (!config.contains("sign-colors.target.line1")) { @@ -260,7 +408,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { config.set("sign-colors.full.line1", defaultConfig.getString("sign-colors.full.line1", "&c")); config.set("sign-colors.full.line2", defaultConfig.getString("sign-colors.full.line2", "&4")); config.set("sign-colors.full.line3", defaultConfig.getString("sign-colors.full.line3", "&e")); - config.set("sign-colors.full.line4", defaultConfig.getString("sign-colors.full.line4", "&1")); // Korrigiert auf &1 + config.set("sign-colors.full.line4", defaultConfig.getString("sign-colors.full.line4", "&1")); getLogger().info("Setze Standardwerte für sign-colors.full"); } else { if (!config.contains("sign-colors.full.line1")) { @@ -287,7 +435,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { config.set("sign-colors.rest.line1", defaultConfig.getString("sign-colors.rest.line1", "&6")); config.set("sign-colors.rest.line2", defaultConfig.getString("sign-colors.rest.line2", "&0")); config.set("sign-colors.rest.line3", defaultConfig.getString("sign-colors.rest.line3", "&f")); - config.set("sign-colors.rest.line4", defaultConfig.getString("sign-colors.rest.line4", "&1")); // Korrigiert auf &1 + config.set("sign-colors.rest.line4", defaultConfig.getString("sign-colors.rest.line4", "&1")); getLogger().info("Setze Standardwerte für sign-colors.rest"); } else { if (!config.contains("sign-colors.rest.line1")) { @@ -326,7 +474,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { getLogger().info("Setze Standardwert: messages.input-chest-set"); } if (!config.contains("messages.target-chest-set")) { - config.set("messages.target-chest-set", defaultConfig.getString("messages.target-chest-set", "&aZieltruhe für %item% erfolgreich gesetzt!")); + config.set("messages.target-chest-set", defaultConfig.getString("messages.target-chest-set", "&aZieltruhe erfolgreich für %item% eingerichtet!")); getLogger().info("Setze Standardwert: messages.target-chest-set"); } // --- NEU: Message für Rest-Truhe --- @@ -362,44 +510,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { } // ---------------------------------------------- - if (!config.contains("messages.help")) { - String helpMessage = "&6&l=== AutoSortChest Hilfe ===\n" + - "&eEingangstruhe erstellen:\n" + - "&f1. Platziere ein Schild an einer Truhe.\n" + - "&f2. Schreibe:\n" + - " &7[asc]\n" + - " &7input\n" + - "&fDein Name wird automatisch in Zeile 4 eingetragen.\n" + - "&eZieltruhe erstellen:\n" + - "&f1. Platziere ein Schild an einer Truhe.\n" + - "&f2. Schreibe:\n" + - " &7[asc]\n" + - " &7ziel\n" + - "&f3. Rechtsklicke mit einem Item in der Hand.\n" + - "&eRest-Truhe (Fallback) erstellen:\n" + - "&f1. Platziere ein Schild an einer Truhe.\n" + - "&f2. Schreibe:\n" + - " &7[asc]\n" + - " &7rest\n" + - "&eBefehle:\n" + - "&f- &b/asc help &f- Zeigt diese Hilfe.\n" + - "&f- &b/asc info &f- Zeigt Plugin-Informationen.\n" + - "&f- &b/asc reload &f- Lädt die Konfiguration neu (OP).\n" + - "&6&l===================="; - config.set("messages.help", defaultConfig.getString("messages.help", helpMessage)); - getLogger().info("Setze Standardwert: messages.help"); - } - if (!config.contains("messages.info")) { - String infoMessage = "&6&l=== AutoSortChest Info ===\n" + - "&ePlugin: &fAutoSortChest\n" + - "&eVersion: &f%version%\n" + - "&eKonfigurationsversion: &f%config_version%\n" + - "&eErsteller: &f%author%\n" + - "&eBeschreibung: &fAutomatisches Sortieren von Items in Truhen.\n" + - "&6&l===================="; - config.set("messages.info", defaultConfig.getString("messages.info", infoMessage)); - getLogger().info("Setze Standardwert: messages.info"); - } + // HILFE UND INFO NICHT MEHR IN CONFIG SPEICHERN! + // Wir benutzen jetzt die statischen Strings HELP_DE, HELP_EN, INFO_DE, INFO_EN aus der Java-Datei. + if (!config.contains("messages.no-permission")) { config.set("messages.no-permission", defaultConfig.getString("messages.no-permission", "&cDu hast keine Berechtigung für diesen Befehl!")); getLogger().info("Setze Standardwert: messages.no-permission"); @@ -442,7 +555,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { private void migrateInputChests() { if (playerData.getConfigurationSection("players") == null) return; boolean migrated = false; - + for (String uuidString : playerData.getConfigurationSection("players").getKeys(false)) { String oldPath = "players." + uuidString + ".input-chest"; // Prüfen, ob der alte Eintrag existiert @@ -454,7 +567,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { int y = playerData.getInt(oldPath + ".y"); int z = playerData.getInt(oldPath + ".z"); World w = Bukkit.getWorld(world); - + if (w != null) { Location loc = new Location(w, x, y, z); // Neue Methode aufrufen, die in die Liste speichert @@ -500,9 +613,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { String inputListPath = basePath + ".input-chests"; // Fallback für altes Format (sollte durch Migration eigentlich weg sein, aber zur Sicherheit) String oldInputPath = basePath + ".input-chest"; - + List inputLocs = new ArrayList<>(); - + if (playerData.contains(inputListPath)) { for (String chestId : playerData.getConfigurationSection(inputListPath).getKeys(false)) { Location loc = getLocationFromPath(inputListPath + "." + chestId); @@ -697,14 +810,14 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { String basePath = "players." + playerUUID + ".input-chests"; String id = UUID.randomUUID().toString(); // Eindeutige ID für jede Truhe String path = basePath + "." + id; - + playerData.set(path + ".world", location.getWorld().getName()); playerData.set(path + ".x", location.getBlockX()); playerData.set(path + ".y", location.getBlockY()); playerData.set(path + ".z", location.getBlockZ()); savePlayerData(); } - + // Original-Methode als Wrapper beibehalten (damit onSignChange nicht geändert werden muss) private void setInputChestLocation(UUID playerUUID, Location location) { addInputChestLocation(playerUUID, location); @@ -790,54 +903,76 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { return false; } - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!command.getName().equalsIgnoreCase("asc")) return false; + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!command.getName().equalsIgnoreCase("asc")) return false; - if (!(sender instanceof Player)) { - sender.sendMessage(ChatColor.RED + "Dieser Befehl ist nur für Spieler!"); - return true; - } - - Player player = (Player) sender; - - if (args.length == 0 || args[0].equalsIgnoreCase("help")) { - String helpMessage = getMessage("help"); - player.sendMessage(helpMessage.split("\n")); - return true; - } - - if (args[0].equalsIgnoreCase("info")) { - String infoMessage = getMessage("info") - .replace("%version%", getDescription().getVersion()) - .replace("%config_version%", config != null ? config.getString("version", CONFIG_VERSION) : CONFIG_VERSION) - .replace("%author%", String.join(", ", getDescription().getAuthors())); - player.sendMessage(infoMessage.split("\n")); - return true; - } - - if (args[0].equalsIgnoreCase("reload")) { - if (!player.hasPermission("autosortchest.reload")) { - player.sendMessage(getMessage("no-permission")); + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "Dieser Befehl ist nur für Spieler!"); return true; } - reloadConfig(); - config = getConfig(); - loadPlayerData(); - updateConfig(); - updateExistingSigns(); - migrateInputChests(); // ERWEITERUNG: Migration erneut aufrufen - player.sendMessage(getMessage("reload-success")); - getLogger().info("Konfiguration und Spielerdaten neu geladen durch " + player.getName()); - return true; - } - String helpMessage = getMessage("help"); + Player player = (Player) sender; + + // --- NEU: Sprache aus Config lesen --- + String lang = config.getString("language", "de"); // Standard Deutsch + if (lang == null) lang = "de"; + + if (args.length == 0 || args[0].equalsIgnoreCase("help")) { + // Sprache prüfen und statischen String wählen + String helpMessage = lang.equalsIgnoreCase("en") ? HELP_EN : HELP_DE; + + // --- FIX: Farben übersetzen (&6 -> §6) --- + // Bukkit versteht & nicht als Farbe, wir müssen es umwandeln + helpMessage = ChatColor.translateAlternateColorCodes('&', helpMessage); + // ---------------------------------------- + + player.sendMessage(helpMessage.split("\n")); + return true; + } + + if (args[0].equalsIgnoreCase("info")) { + // Sprache prüfen und statischen String wählen + String infoMessage = lang.equalsIgnoreCase("en") ? INFO_EN : INFO_DE; + + // Platzhalter ersetzen + infoMessage = infoMessage + .replace("%version%", getDescription().getVersion()) + .replace("%config_version%", config != null ? config.getString("version", CONFIG_VERSION) : CONFIG_VERSION) + .replace("%author%", String.join(", ", getDescription().getAuthors())); + + // --- FIX: Farben übersetzen (&6 -> §6) --- + infoMessage = ChatColor.translateAlternateColorCodes('&', infoMessage); + // ---------------------------------------- + + player.sendMessage(infoMessage.split("\n")); + return true; + } + + if (args[0].equalsIgnoreCase("reload")) { + if (!player.hasPermission("autosortchest.reload")) { + player.sendMessage(getMessage("no-permission")); + return true; + } + reloadConfig(); + config = getConfig(); + loadPlayerData(); + updateConfig(); + updateExistingSigns(); + migrateInputChests(); + player.sendMessage(getMessage("reload-success")); + getLogger().info("Konfiguration und Spielerdaten neu geladen durch " + player.getName()); + return true; + } + + // Default: Hilfe zeigen + String helpMessage = lang.equalsIgnoreCase("en") ? HELP_EN : HELP_DE; + helpMessage = ChatColor.translateAlternateColorCodes('&', helpMessage); player.sendMessage(helpMessage.split("\n")); return true; } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) // <- Geändert: Priorität erhöht und Cancel ignoriert + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onSignChange(SignChangeEvent event) { Player player = event.getPlayer(); UUID playerUUID = player.getUniqueId(); @@ -892,14 +1027,14 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { event.setLine(0, getSignColor("input", "line1") + "[asc]"); event.setLine(1, getSignColor("input", "line2") + "input"); event.setLine(3, getSignColor("input", "line4") + player.getName()); - setInputChestLocation(playerUUID, chestBlock.getLocation()); // Ruft nun addInputChestLocation auf + setInputChestLocation(playerUUID, chestBlock.getLocation()); player.sendMessage(getMessage("input-chest-set")); getLogger().info("Eingangstruhe für " + player.getName() + " gesetzt bei " + chestBlock.getLocation()); } } @EventHandler - public void onPlayerInteract(org.bukkit.event.player.PlayerInteractEvent event) { + public void onPlayerInteract(PlayerInteractEvent event) { if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; Player player = event.getPlayer(); @@ -940,8 +1075,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { boolean isOwner = pureOwnerName.equalsIgnoreCase(player.getName()); - // 1. FALL: MODUS WECHSELN (Shift + Klick + Leere Hand) - if (event.getAction() == Action.RIGHT_CLICK_BLOCK && player.isSneaking() && (itemInHand == null || itemInHand.getType() == Material.AIR)) { + // 1. FALL: MODUS WECHSELN (Shift + Klick + Leere hand) + if (player.isSneaking() && (itemInHand == null || itemInHand.getType() == Material.AIR)) { // Admin Check hinzugefügt if (isOwner || isAdmin(player)) { boolean isPublic = isChestPublic(sign); @@ -992,9 +1127,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); - sign.setLine(2, getSignColor(colorType, "line3") + itemInHand.getType().name()); // Item wird überschrieben + sign.setLine(2, getSignColor(colorType, "line3") + itemInHand.getType().name()); - // Wenn noch kein Name da war, setze ihn String finalLine4 = line3Raw; if (pureOwnerName.isEmpty() || pureOwnerName.equalsIgnoreCase("Unknown")) { finalLine4 = getSignColor("target", "line4") + player.getName(); @@ -1002,15 +1136,6 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { sign.setLine(3, finalLine4); sign.update(); - - // Admins ändern Truhe für den Owner? Nein, das ist komplex. - // Wir erlauben Admins nur den Zugriff hier, oder? - // Wenn Admin Item ändert, gehört die Truhe dann ihm? - // Der Code setzt aktuell targetChestLocation für playerUUID. - // Wenn Admin es ändert, sollte er es wohl für den Owner tun lassen oder es übernehmen. - // Um es einfach zu halten: Wenn Admin es ändert, wird es "seine" Zieltruhe für dieses Item. - // (Oder man fragt sich ab, ob man das will. Der User fragte nur nach "schauen" und "entfernen"). - // Ich lasse das Zuweisen wie gehabt (Owner Check oben), aber Admin darf es ändern. setTargetChestLocation(player.getUniqueId(), chestBlock.getLocation(), itemInHand.getType()); player.sendMessage(getMessage("target-chest-set").replace("%item%", itemInHand.getType().name())); @@ -1027,8 +1152,14 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { return; } - // Wenn Owner oder Öffentlich oder Admin -> Truhe öffnen lassen (Event nicht canceln) + // --- FIX: Zugriff erlaubt -> Event canceln und Truhe öffnen --- + // Dies verhindert das Öffnen des Schild-Editors und erzwingt das Öffnen der Truhe + event.setCancelled(true); + if (chestBlock.getState() instanceof Chest) { + player.openInventory(((Chest) chestBlock.getState()).getInventory()); + } return; + // ----------------------------------------------------------- } // --- LOGIK FÜR EINGANGSTRUHEN (INPUT) --- @@ -1071,6 +1202,14 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { event.setCancelled(true); return; } + + // --- FIX: Zugriff erlaubt -> Event canceln und Truhe öffnen --- + event.setCancelled(true); + if (chestBlock.getState() instanceof Chest) { + player.openInventory(((Chest) chestBlock.getState()).getInventory()); + } + return; + // ----------------------------------------------------------- } } return; @@ -1096,7 +1235,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { String[] lines = sign.getLines(); if (lines.length >= 2 && ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")).equalsIgnoreCase("[asc]")) { signBlock = face; - break outerLoop; // Schild gefunden + break outerLoop; } } } @@ -1111,7 +1250,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { if (line1Clean.equalsIgnoreCase("ziel") || line1Clean.equalsIgnoreCase("rest")) { String line3Raw = lines[3] != null ? lines[3] : ""; String line3Clean = ChatColor.stripColor(line3Raw); - String pureOwnerName = line3Clean.replace("[public]", "").replace("[Public]", "").trim(); + String pureOwnerName = line3Clean.replace("[public]", "").replace("[public]", "").trim(); boolean isOwner = pureOwnerName.equalsIgnoreCase(player.getName()); // 1. FALL: MODUS WECHSELN @@ -1194,13 +1333,14 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { event.setCancelled(true); return; } + // Wenn erlaubt, machen wir nichts, damit die Truhe ganz normal geöffnet wird } // FALL: EINGANGSTRUHE if (line1Clean.equalsIgnoreCase("input")) { String line3Raw = lines[3] != null ? lines[3] : ""; String line3Clean = ChatColor.stripColor(line3Raw); - String pureOwnerName = line3Clean.replace("[public]", "").replace("[Public]", "").trim(); + String pureOwnerName = line3Clean.replace("[Public]", "").replace("[public]", "").trim(); boolean isOwner = pureOwnerName.equalsIgnoreCase(player.getName()); // Moduswechsel @@ -1237,6 +1377,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { event.setCancelled(true); return; } + // Wenn erlaubt, lassen wir die Truhe ganz normal öffnen } } } @@ -1290,7 +1431,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { if (lines.length >= 2 && ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")).equalsIgnoreCase("[asc]") && (ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("input") || ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("ziel") || - ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("rest"))) { // REST hinzugefügt + ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("rest"))) { signBlock = block; signOwner = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); if (isDebug()) { @@ -1317,7 +1458,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { if (lines.length >= 2 && ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")).equalsIgnoreCase("[asc]") && (ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("input") || ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("ziel") || - ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("rest"))) { // REST hinzugefügt + ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("rest"))) { signBlock = face; signOwner = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); isAscChest = true; @@ -1708,6 +1849,55 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { } } + // --- NEU: BUNTE PARTIKEL UM DIE TRUHE (NUR ZIELTRUHE) --- + private void spawnTransferParticles(Location start, Location end) { + if (!config.getBoolean("effects.enabled", true)) return; + if (end.getWorld() == null) return; + + // 1. Sound abspielen (am Zielort) + if (config.getBoolean("effects.sound", true)) { + end.getWorld().playSound(end, Sound.ENTITY_ITEM_PICKUP, 0.2f, 1.2f); + } + + // 2. Bunter Kreis aus Partikeln am Zielort + Location center = end.clone().add(0.5, 0.5, 0.5); // Zentrum der Truhe (etwas erhöht) + + // Wir spawnen einen Kreis aus Partikeln + int count = 12; // Anzahl der Partikel im Kreis + double radius = 0.8; // Radius um den Block herum + float size = 1.2f; + + // Farbpalette (Rot, Grün, Blau, Gelb, Cyan, Magenta) + Color[] colors = { + Color.fromRGB(255, 0, 0), // Rot + Color.fromRGB(0, 255, 0), // Grün + Color.fromRGB(0, 0, 255), // Blau + Color.fromRGB(255, 255, 0), // Gelb + Color.fromRGB(0, 255, 255), // Cyan + Color.fromRGB(255, 0, 255) // Magenta + }; + + for (int i = 0; i < count; i++) { + double angle = (2 * Math.PI / count) * i; + double dx = Math.cos(angle) * radius; + double dz = Math.sin(angle) * radius; + + // Partikel am Boden oder leicht erhöht + Location particleLoc = center.clone().add(dx, -0.1, dz); + + // Zufällige Farbe aus der Liste für "Bunt" + Color color = colors[(int)(Math.random() * colors.length)]; + + // Wir mischen ein wenig Offset, damit es nicht so perfekt wirkt + double offsetX = (Math.random() - 0.5) * 0.2; + double offsetZ = (Math.random() - 0.5) * 0.2; + + DustOptions options = new DustOptions(color, size); + end.getWorld().spawnParticle(Particle.DUST, particleLoc.clone().add(offsetX, 0, offsetZ), 1, 0, 0, 0, 0.02, options); + } + } + // ----------------------------------- + private void checkInputChests() { if (playerData == null) { getLogger().warning("playerData ist null. Kann Eingangstruhe nicht prüfen."); @@ -1737,8 +1927,6 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { if (playerData.contains(inputListPath)) { chestsToCheck.addAll(playerData.getConfigurationSection(inputListPath).getKeys(false)); } else if (playerData.contains(oldInputPath)) { - // Hier führen wir keine Migration durch (nur lesen), da das im laufenden Betrieb zu Problemen führen könnte. - // Wir markieren es für den Task, damit es bei下一次 Update erledigt wird. Location loc = getLocationFromPath(oldInputPath); if (loc != null) { checkSingleInputChest(ownerUUID, loc, "legacy"); @@ -1841,13 +2029,13 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { } // Wir rufen distributeItemsForOwner auf. - distributeItemsForOwner(ownerUUID, ownerPlayer, chest.getInventory(), ownerName); + distributeItemsForOwner(ownerUUID, ownerPlayer, chest.getInventory(), ownerName, location); return true; } // ---------------------------------------------------------------------- // 4. KORRIGIERTE distributeItemsForOwner Methode: - private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory, String ownerNameOverride) { + private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory, String ownerNameOverride, Location sourceLocation) { boolean hasItems = false; for (ItemStack item : sourceInventory.getContents()) { if (item != null && item.getType() != Material.AIR) { @@ -1973,6 +2161,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { if (leftover.isEmpty()) { sourceInventory.setItem(slot, null); + // --- NEU: Effekte (Nur am Zielort) --- + spawnTransferParticles(null, targetChestLocation); + // ------------------------ } else { if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { String message = getMessage("target-chest-full") @@ -1992,4 +2183,86 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { } } } + + // --- NEU: Hopper-Automatisierung --- + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onInventoryMoveItem(InventoryMoveItemEvent event) { + Inventory destination = event.getDestination(); + + // Prüfen, ob das Ziel eine Truhe ist + if (!(destination.getHolder() instanceof Chest)) { + return; + } + + final Chest destChest = (Chest) destination.getHolder(); + + // Wir müssen herausfinden, ob diese Truhe eine Input-Truhe ist. + // Wir scannen die Blöcke der Truhe nach einem [asc] input Schild. + + // Unterstützung für Doppeltruhen + List blocksToScan = new ArrayList<>(); + InventoryHolder holder = destination.getHolder(); + if (holder instanceof DoubleChest) { + DoubleChest dc = (DoubleChest) holder; + if (dc.getLeftSide() instanceof Chest) blocksToScan.add(((Chest) dc.getLeftSide()).getBlock()); + if (dc.getRightSide() instanceof Chest) blocksToScan.add(((Chest) dc.getRightSide()).getBlock()); + } else if (holder instanceof Chest) { + blocksToScan.add(destChest.getBlock()); + } + + UUID ownerUUID = null; + String ownerName = null; + boolean isPublic = false; + + outerLoop: + for (Block b : blocksToScan) { + for (Block face : new Block[] { + b.getRelative(1, 0, 0), + b.getRelative(-1, 0, 0), + b.getRelative(0, 0, 1), + b.getRelative(0, 0, -1) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, b)) { + String[] lines = sign.getLines(); + String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); + String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + + if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("input")) { + // Schild gefunden! Ermittle Owner aus Zeile 4 + String line3Raw = lines[3] != null ? lines[3] : ""; + String line3Clean = ChatColor.stripColor(line3Raw); + + isPublic = line3Clean.toLowerCase().endsWith("[public]"); + ownerName = line3Clean.replace(" [Public]", "").replace(" [public]", "").trim(); + + // Name zu UUID auflösen + OfflinePlayer op = Bukkit.getOfflinePlayer(ownerName); + if (op.hasPlayedBefore()) { + ownerUUID = op.getUniqueId(); + } + break outerLoop; // Wir haben alles was wir brauchen + } + } + } + } + + // Wenn kein [asc] input Schild oder Owner unbekannt -> Abbruch + if (ownerUUID == null || ownerName == null || ownerName.isEmpty()) { + return; + } + + // Schedule Task um sicherzustellen, dass der Hopper das Item fertig platziert hat + final UUID finalOwnerUUID = ownerUUID; + final String finalOwnerName = ownerName; + + new BukkitRunnable() { + @Override + public void run() { + // Wir sortieren auch, wenn der Owner Offline ist (Automation) + // ownerPlayer wird übergeben als null, da es keinen aktiven Spielerkontext gibt + distributeItemsForOwner(finalOwnerUUID, null, destChest.getInventory(), finalOwnerName, destChest.getLocation()); + } + }.runTaskLater(this, 1L); + } + // ----------------------------------- } \ No newline at end of file