From 943ed76c114578373baaf4bf27e33aaecd608f92 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Wed, 31 Dec 2025 10:28:33 +0000 Subject: [PATCH] src/main/java/com/viper/autosortchest/Main.java aktualisiert --- .../java/com/viper/autosortchest/Main.java | 2930 +++++++++-------- 1 file changed, 1468 insertions(+), 1462 deletions(-) diff --git a/src/main/java/com/viper/autosortchest/Main.java b/src/main/java/com/viper/autosortchest/Main.java index 3f899cf..92702b6 100644 --- a/src/main/java/com/viper/autosortchest/Main.java +++ b/src/main/java/com/viper/autosortchest/Main.java @@ -1,1463 +1,1469 @@ -package com.viper.autosortchest; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.Chest; -import org.bukkit.block.Sign; -import org.bukkit.block.data.type.WallSign; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -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.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.ChatColor; -import org.bukkit.Material; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.*; - -public class Main extends JavaPlugin implements Listener, CommandExecutor { - private File playerDataFile; - private FileConfiguration playerData; - 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.3"; - - @Override - public void onEnable() { - // Lade Standard-Konfiguration - saveDefaultConfig(); - config = getConfig(); - - // Lade players.yml - playerDataFile = new File(getDataFolder(), "players.yml"); - if (!playerDataFile.exists()) { - saveResource("players.yml", false); - } - loadPlayerData(); - - // Registriere Events - getServer().getPluginManager().registerEvents(this, this); - - // Registriere Befehle - this.getCommand("asc").setExecutor(this); - - // Starte periodischen Task - new BukkitRunnable() { - @Override - public void run() { - checkInputChests(); - } - }.runTaskTimer(this, 20L, 20L); // Alle 1 Sekunde (20 Ticks) - - // Starte Task zum Bereinigen des Message-Trackers - new BukkitRunnable() { - @Override - public void run() { - cleanMessageTracker(); - } - }.runTaskTimer(this, 20L * 60, 20L * 60); // Alle Minute - - // Aktualisiere Konfiguration und Schilder - updateConfig(); - updateExistingSigns(); - - getLogger().info("AutoSortChest Plugin aktiviert! Version: " + getDescription().getVersion()); - } - - @Override - public void onDisable() { - savePlayerData(); - getLogger().info("AutoSortChest Plugin deaktiviert!"); - } - - private void savePlayerData() { - if (playerData == null || playerDataFile == null) { - getLogger().warning("Kann players.yml nicht speichern: playerData oder playerDataFile ist null"); - return; - } - try { - playerData.save(playerDataFile); - getLogger().info("players.yml erfolgreich gespeichert"); - } catch (IOException e) { - getLogger().warning("Fehler beim Speichern der players.yml: " + e.getMessage()); - } - } - - private void loadPlayerData() { - if (playerDataFile == null) { - playerDataFile = new File(getDataFolder(), "players.yml"); - } - try { - if (!playerDataFile.exists()) { - saveResource("players.yml", false); - getLogger().info("Neue players.yml erstellt"); - } - playerData = YamlConfiguration.loadConfiguration(playerDataFile); - if (playerData.getConfigurationSection("players") == null) { - getLogger().warning("Abschnitt 'players' in players.yml fehlt. Initialisiere leer."); - playerData.createSection("players"); - savePlayerData(); - } else { - int playerCount = playerData.getConfigurationSection("players").getKeys(false).size(); - getLogger().info("players.yml geladen mit " + playerCount + " Spieler-Einträgen"); - } - } catch (Exception e) { - getLogger().warning("Fehler beim Laden von players.yml: " + e.getMessage()); - playerData = new YamlConfiguration(); - playerData.createSection("players"); - savePlayerData(); - } - } - - private void updateConfig() { - File configFile = new File(getDataFolder(), "config.yml"); - InputStream defaultConfigStream = getResource("config.yml"); - FileConfiguration defaultConfig; - - // Lade Standardkonfiguration - if (defaultConfigStream == null) { - getLogger().warning("Standard-config.yml nicht gefunden in Plugin-Ressourcen! Verwende Fallback-Werte."); - defaultConfig = new YamlConfiguration(); - } else { - try { - defaultConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(defaultConfigStream)); - getLogger().info("Standard-config.yml erfolgreich geladen."); - } catch (Exception e) { - getLogger().warning("Fehler beim Laden von Standard-config.yml: " + e.getMessage()); - defaultConfig = new YamlConfiguration(); - } - } - - // Prüfe Konfigurationsversion - String currentVersion = config.getString("version", "1.0"); - boolean versionUpdated = false; - if (!currentVersion.equals(CONFIG_VERSION)) { - getLogger().info("Aktualisiere config.yml von Version " + currentVersion + " auf " + CONFIG_VERSION); - config.set("version", CONFIG_VERSION); - versionUpdated = true; - } - - // Setze Standardwerte für fehlende Schlüssel - if (!config.contains("debug")) { - config.set("debug", defaultConfig.getBoolean("debug", false)); - getLogger().info("Setze Standardwert: debug = " + config.getBoolean("debug")); - } - - // 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", "&f")); - getLogger().info("Setze Standardwerte für sign-colors.input"); - } else { - if (!config.contains("sign-colors.input.line1")) { - config.set("sign-colors.input.line1", defaultConfig.getString("sign-colors.input.line1", "&6")); - getLogger().info("Setze Standardwert: sign-colors.input.line1 = " + config.getString("sign-colors.input.line1")); - } - if (!config.contains("sign-colors.input.line2")) { - config.set("sign-colors.input.line2", defaultConfig.getString("sign-colors.input.line2", "&0")); - getLogger().info("Setze Standardwert: sign-colors.input.line2 = " + config.getString("sign-colors.input.line2")); - } - if (!config.contains("sign-colors.input.line4")) { - config.set("sign-colors.input.line4", defaultConfig.getString("sign-colors.input.line4", "&f")); - getLogger().info("Setze Standardwert: sign-colors.input.line4 = " + config.getString("sign-colors.input.line4")); - } - } - - // Prüfe und setze sign-colors.target - if (!config.contains("sign-colors.target")) { - config.createSection("sign-colors.target"); - 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", "&f")); - getLogger().info("Setze Standardwerte für sign-colors.target"); - } else { - if (!config.contains("sign-colors.target.line1")) { - config.set("sign-colors.target.line1", defaultConfig.getString("sign-colors.target.line1", "&6")); - getLogger().info("Setze Standardwert: sign-colors.target.line1 = " + config.getString("sign-colors.target.line1")); - } - if (!config.contains("sign-colors.target.line2")) { - config.set("sign-colors.target.line2", defaultConfig.getString("sign-colors.target.line2", "&0")); - getLogger().info("Setze Standardwert: sign-colors.target.line2 = " + config.getString("sign-colors.target.line2")); - } - if (!config.contains("sign-colors.target.line3")) { - config.set("sign-colors.target.line3", defaultConfig.getString("sign-colors.target.line3", "&f")); - getLogger().info("Setze Standardwert: sign-colors.target.line3 = " + config.getString("sign-colors.target.line3")); - } - if (!config.contains("sign-colors.target.line4")) { - config.set("sign-colors.target.line4", defaultConfig.getString("sign-colors.target.line4", "&f")); - getLogger().info("Setze Standardwert: sign-colors.target.line4 = " + config.getString("sign-colors.target.line4")); - } - } - - // Prüfe und setze sign-colors.full - if (!config.contains("sign-colors.full")) { - config.createSection("sign-colors.full"); - 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", "&e")); - getLogger().info("Setze Standardwerte für sign-colors.full"); - } else { - if (!config.contains("sign-colors.full.line1")) { - config.set("sign-colors.full.line1", defaultConfig.getString("sign-colors.full.line1", "&c")); - getLogger().info("Setze Standardwert: sign-colors.full.line1 = " + config.getString("sign-colors.full.line1")); - } - if (!config.contains("sign-colors.full.line2")) { - config.set("sign-colors.full.line2", defaultConfig.getString("sign-colors.full.line2", "&4")); - getLogger().info("Setze Standardwert: sign-colors.full.line2 = " + config.getString("sign-colors.full.line2")); - } - if (!config.contains("sign-colors.full.line3")) { - config.set("sign-colors.full.line3", defaultConfig.getString("sign-colors.full.line3", "&e")); - getLogger().info("Setze Standardwert: sign-colors.full.line3 = " + config.getString("sign-colors.full.line3")); - } - if (!config.contains("sign-colors.full.line4")) { - config.set("sign-colors.full.line4", defaultConfig.getString("sign-colors.full.line4", "&e")); - getLogger().info("Setze Standardwert: sign-colors.full.line4 = " + config.getString("sign-colors.full.line4")); - } - } - - // Prüfe und setze messages - if (!config.contains("messages.no-chest-near-sign")) { - config.set("messages.no-chest-near-sign", defaultConfig.getString("messages.no-chest-near-sign", "&cKeine Truhe in der Nähe des Schildes!")); - getLogger().info("Setze Standardwert: messages.no-chest-near-sign"); - } - if (!config.contains("messages.no-item-in-hand")) { - config.set("messages.no-item-in-hand", defaultConfig.getString("messages.no-item-in-hand", "&cDu musst ein Item in der Hand halten!")); - getLogger().info("Setze Standardwert: messages.no-item-in-hand"); - } - if (!config.contains("messages.not-your-chest")) { - config.set("messages.not-your-chest", defaultConfig.getString("messages.not-your-chest", "&cDiese Truhe gehört dir nicht!")); - getLogger().info("Setze Standardwert: messages.not-your-chest"); - } - if (!config.contains("messages.input-chest-set")) { - config.set("messages.input-chest-set", defaultConfig.getString("messages.input-chest-set", "&aEingangstruhe erfolgreich gesetzt!")); - 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!")); - getLogger().info("Setze Standardwert: messages.target-chest-set"); - } - if (!config.contains("messages.target-chest-missing")) { - config.set("messages.target-chest-missing", defaultConfig.getString("messages.target-chest-missing", "&cZieltruhe für %item% fehlt!")); - getLogger().info("Setze Standardwert: messages.target-chest-missing"); - } - String targetChestFull = config.getString("messages.target-chest-full", ""); - String defaultTargetChestFull = defaultConfig.getString("messages.target-chest-full", "&cZieltruhe für %item% ist voll! Koordinaten: (%x%, %y%, %z%)"); - if (!config.contains("messages.target-chest-full") || !targetChestFull.equals(defaultTargetChestFull)) { - config.set("messages.target-chest-full", defaultTargetChestFull); - getLogger().info("Setze oder aktualisiere Standardwert: messages.target-chest-full = " + defaultTargetChestFull); - } - - // --- NEUE NACHRICHTEN FÜR ÖFFENTLICHEN MODUS --- - if (!config.contains("messages.mode-changed")) { - config.set("messages.mode-changed", defaultConfig.getString("messages.mode-changed", "&aModus gewechselt: &e%mode%")); - getLogger().info("Setze Standardwert: messages.mode-changed"); - } - if (!config.contains("messages.mode-public")) { - config.set("messages.mode-public", defaultConfig.getString("messages.mode-public", "&aÖffentlich")); - getLogger().info("Setze Standardwert: messages.mode-public"); - } - if (!config.contains("messages.mode-private")) { - config.set("messages.mode-private", defaultConfig.getString("messages.mode-private", "&cPrivat")); - getLogger().info("Setze Standardwert: messages.mode-private"); - } - // ---------------------------------------------- - - 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" + - "&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"); - } - 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"); - } - if (!config.contains("messages.reload-success")) { - config.set("messages.reload-success", defaultConfig.getString("messages.reload-success", "&aKonfiguration erfolgreich neu geladen!")); - getLogger().info("Setze Standardwert: messages.reload-success"); - } - if (!config.contains("messages.sign-break-denied")) { - config.set("messages.sign-break-denied", defaultConfig.getString("messages.sign-break-denied", "&cDu musst Shift gedrückt halten, um dieses Schild oder die Truhe abzubauen!")); - getLogger().info("Setze Standardwert: messages.sign-break-denied"); - } - - // Speichere die aktualisierte Konfiguration - try { - config.save(configFile); - if (configFile.exists() && configFile.length() > 0) { - getLogger().info("config.yml erfolgreich aktualisiert und gespeichert auf Version " + CONFIG_VERSION); - } else { - getLogger().warning("config.yml wurde nicht korrekt gespeichert (Datei existiert nicht oder ist leer)"); - } - } catch (IOException e) { - getLogger().warning("Fehler beim Speichern der config.yml: " + e.getMessage()); - } - } - - private void updateExistingSigns() { - if (playerData == null) { - getLogger().warning("playerData ist null. Kann bestehende Schilder nicht aktualisieren."); - return; - } - if (playerData.getConfigurationSection("players") == null) { - getLogger().warning("Abschnitt 'players' in players.yml fehlt. Keine Schilder zum Aktualisieren."); - return; - } - - for (String uuidString : playerData.getConfigurationSection("players").getKeys(false)) { - UUID playerUUID; - try { - playerUUID = UUID.fromString(uuidString); - } catch (IllegalArgumentException e) { - getLogger().warning("Ungültige UUID in players.yml: " + uuidString); - continue; - } - - String path = "players." + uuidString; - - // Eingangstruhe - String inputPath = path + ".input-chest"; - if (playerData.contains(inputPath)) { - String worldName = playerData.getString(inputPath + ".world"); - World world = getServer().getWorld(worldName); - if (world == null) { - getLogger().warning("Welt " + worldName + " für Eingangstruhe von Spieler UUID " + uuidString + " nicht gefunden"); - continue; - } - int x = playerData.getInt(inputPath + ".x"); - int y = playerData.getInt(inputPath + ".y"); - int z = playerData.getInt(inputPath + ".z"); - Location chestLocation = new Location(world, x, y, z); - Block chestBlock = chestLocation.getBlock(); - - if (!(chestBlock.getState() instanceof Chest)) { - getLogger().warning("Eingangstruhe bei " + chestLocation + " ist keine Truhe"); - continue; - } - - for (Block face : new Block[] { - chestBlock.getRelative(1, 0, 0), - chestBlock.getRelative(-1, 0, 0), - chestBlock.getRelative(0, 0, 1), - chestBlock.getRelative(0, 0, -1) - }) { - if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chestBlock)) { - String[] lines = sign.getLines(); - String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); - String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); - String line3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); - if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("input")) { - sign.setLine(0, getSignColor("input", "line1") + "[asc]"); - sign.setLine(1, getSignColor("input", "line2") + "input"); - sign.setLine(3, getSignColor("input", "line4") + line3); - sign.update(); - if (isDebug()) { - getLogger().fine("Eingangsschild bei " + face.getLocation() + " für Spieler UUID " + uuidString + " aktualisiert"); - } - } - } - } - } - - // Zieltruhen - String targetPath = path + ".target-chests"; - if (playerData.contains(targetPath)) { - for (String itemType : playerData.getConfigurationSection(targetPath).getKeys(false)) { - String targetChestPath = targetPath + "." + itemType; - String worldName = playerData.getString(targetChestPath + ".world"); - World world = getServer().getWorld(worldName); - if (world == null) { - getLogger().warning("Welt " + worldName + " für Zieltruhe von Item " + itemType + " nicht gefunden"); - continue; - } - int x = playerData.getInt(targetChestPath + ".x"); - int y = playerData.getInt(targetChestPath + ".y"); - int z = playerData.getInt(targetChestPath + ".z"); - Location chestLocation = new Location(world, x, y, z); - Block chestBlock = chestLocation.getBlock(); - - if (!(chestBlock.getState() instanceof Chest)) { - getLogger().warning("Zieltruhe für Item " + itemType + " bei " + chestLocation + " ist keine Truhe"); - continue; - } - - Chest chest = (Chest) chestBlock.getState(); - Inventory inventory = chest.getInventory(); - boolean isFull = isInventoryFull(inventory); - - for (Block face : new Block[] { - chestBlock.getRelative(1, 0, 0), - chestBlock.getRelative(-1, 0, 0), - chestBlock.getRelative(0, 0, 1), - chestBlock.getRelative(0, 0, -1) - }) { - if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chestBlock)) { - String[] lines = sign.getLines(); - String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); - String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); - String line2 = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); - String line3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); - if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { - String colorType = isFull ? "full" : "target"; - sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); - sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); - sign.setLine(2, getSignColor(colorType, "line3") + line2); - sign.setLine(3, getSignColor(colorType, "line4") + line3); - sign.update(); - if (isDebug()) { - getLogger().fine("Zieltruhe-Schild für Item " + itemType + " bei " + face.getLocation() + " aktualisiert (voll: " + isFull + ")"); - } - } - } - } - } - } - } - } - - private boolean isInventoryFull(Inventory inventory) { - for (ItemStack item : inventory.getContents()) { - if (item == null || item.getAmount() < item.getMaxStackSize()) { - return false; - } - } - return true; - } - - private boolean isChestPublic(Sign sign) { - String line3 = sign.getLine(3); // MIT Farbcodes! - String line3Clean = ChatColor.stripColor(line3); - return line3Clean.toLowerCase().contains("[public]"); - } - - private boolean isDebug() { - return config != null && config.getBoolean("debug", false); - } - - private String getMessage(String key) { - if (config == null) { - return ChatColor.RED + "Fehlende Konfiguration: " + key; - } - String message = config.getString("messages." + key, "Fehlende Nachricht: " + key); - return ChatColor.translateAlternateColorCodes('&', message); - } - - private String getSignColor(String type, String line) { - if (config == null) { - return "&f"; - } - String color = config.getString("sign-colors." + type + "." + line, "&f"); - return ChatColor.translateAlternateColorCodes('&', color); - } - - private boolean isSignAttachedToChest(Block signBlock, Block chestBlock) { - if (!(signBlock.getBlockData() instanceof WallSign)) { - if (isDebug()) getLogger().fine("Schild bei " + signBlock.getLocation() + " ist kein Wandschild"); - return false; - } - WallSign wallSign = (WallSign) signBlock.getBlockData(); - Block attachedBlock = signBlock.getRelative(wallSign.getFacing().getOppositeFace()); - boolean attached = attachedBlock.equals(chestBlock); - if (!attached && isDebug()) { - getLogger().fine("Schild bei " + signBlock.getLocation() + " ist nicht an Truhe bei " + chestBlock.getLocation() + " angebracht"); - } - return attached; - } - - private void setInputChestLocation(UUID playerUUID, Location location) { - String path = "players." + playerUUID + ".input-chest"; - 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(); - } - - private void setTargetChestLocation(UUID playerUUID, Location location, Material itemType) { - String path = "players." + playerUUID + ".target-chests." + itemType.name(); - 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(); - getLogger().info("Zieltruhe für " + getServer().getPlayer(playerUUID).getName() + " (" + itemType.name() + ") gesetzt bei " + location); - } - - private Location getTargetChestLocation(UUID playerUUID, Material itemType) { - String path = "players." + playerUUID + ".target-chests." + itemType.name(); - if (!playerData.contains(path)) { - if (isDebug()) getLogger().fine("Keine Zieltruhe für Item " + itemType.name() + " für Spieler UUID " + playerUUID); - return null; - } - - String worldName = playerData.getString(path + ".world"); - World world = getServer().getWorld(worldName); - if (world == null) { - getLogger().warning("Welt " + worldName + " für Zieltruhe von Item " + itemType.name() + " nicht gefunden"); - return null; - } - - int x = playerData.getInt(path + ".x"); - int y = playerData.getInt(path + ".y"); - int z = playerData.getInt(path + ".z"); - - return new Location(world, x, y, z); - } - - private void cleanMessageTracker() { - long currentTime = System.currentTimeMillis(); - fullChestMessageTracker.entrySet().removeIf(entry -> { - Map messages = entry.getValue(); - messages.entrySet().removeIf(msg -> currentTime - msg.getValue() > MESSAGE_COOLDOWN); - return messages.isEmpty(); - }); - } - - private boolean canSendFullChestMessage(UUID playerUUID, Material material) { - Map playerMessages = fullChestMessageTracker.computeIfAbsent(playerUUID, k -> new HashMap<>()); - Long lastMessageTime = playerMessages.get(material); - long currentTime = System.currentTimeMillis(); - - if (lastMessageTime == null || currentTime - lastMessageTime > MESSAGE_COOLDOWN) { - playerMessages.put(material, currentTime); - return true; - } - 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")); - return true; - } - reloadConfig(); - config = getConfig(); - loadPlayerData(); - updateConfig(); - updateExistingSigns(); - player.sendMessage(getMessage("reload-success")); - getLogger().info("Konfiguration und Spielerdaten neu geladen durch " + player.getName()); - return true; - } - - String helpMessage = getMessage("help"); - player.sendMessage(helpMessage.split("\n")); - return true; - } - - @EventHandler - public void onSignChange(SignChangeEvent event) { - Player player = event.getPlayer(); - UUID playerUUID = player.getUniqueId(); - Block signBlock = event.getBlock(); - String[] lines = event.getLines(); - - if (lines.length >= 2 && lines[0].equalsIgnoreCase("[asc]") && lines[1].equalsIgnoreCase("input")) { - Block chestBlock = null; - if (signBlock.getBlockData() instanceof WallSign wallSign) { - Block attachedBlock = signBlock.getRelative(wallSign.getFacing().getOppositeFace()); - if (attachedBlock.getState() instanceof Chest) { - chestBlock = attachedBlock; - } - } - - if (chestBlock == null) { - player.sendMessage(getMessage("no-chest-near-sign")); - getLogger().warning("Keine Truhe an Schild bei " + signBlock.getLocation() + " für Spieler " + player.getName()); - return; - } - - 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()); - 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) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; - - Player player = event.getPlayer(); - UUID playerUUID = player.getUniqueId(); - Block clickedBlock = event.getClickedBlock(); - ItemStack itemInHand = event.getItem(); - - if (clickedBlock == null) return; - - // Schild-Interaktion - if (clickedBlock.getState() instanceof Sign sign) { - String[] lines = sign.getLines(); - if (lines.length >= 2 && ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")).equalsIgnoreCase("[asc]")) { - - // Truhe finden - Block chestBlock = null; - if (sign.getBlockData() instanceof WallSign wallSign) { - Block attachedBlock = sign.getBlock().getRelative(wallSign.getFacing().getOppositeFace()); - if (attachedBlock.getState() instanceof Chest) { - chestBlock = attachedBlock; - } - } - - if (chestBlock == null) { - player.sendMessage(getMessage("no-chest-near-sign")); - event.setCancelled(true); - return; - } - - String line1Clean = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); - - // --- LOGIK FÜR ZIELTRUHEN (ZIEL) --- - if (line1Clean.equalsIgnoreCase("ziel")) { - String line3Raw = lines[3] != null ? lines[3] : ""; - String line3Clean = ChatColor.stripColor(line3Raw); - String pureOwnerName = line3Clean.replace("[Public]", "").replace("[public]", "").trim(); - - 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)) { - if (isOwner) { - boolean isPublic = isChestPublic(sign); - String newModeText; - String newLine4; - - if (isPublic) { - // Wechsel zu Privat - newModeText = getMessage("mode-private"); - newLine4 = getSignColor("target", "line4") + pureOwnerName; - } else { - // Wechsel zu Öffentlich - newModeText = getMessage("mode-public"); - newLine4 = getSignColor("target", "line4") + pureOwnerName + " " + ChatColor.RESET + "[Public]"; - } - - sign.setLine(3, newLine4); - sign.update(); - player.sendMessage(getMessage("mode-changed").replace("%mode%", newModeText)); - event.setCancelled(true); - return; - } else { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - } - // 2. FALL: ITEM ZUWEISEN (Klick + Item in Hand) - String line2Clean = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); - - if (line2Clean.isEmpty()) { - if (itemInHand == null || itemInHand.getType() == Material.AIR) { - player.sendMessage(getMessage("no-item-in-hand")); - event.setCancelled(true); - return; - } - - // Wenn das Schild noch KEINEN Owner hat, darf jeder es "übernehmen" - if (!pureOwnerName.isEmpty() && !pureOwnerName.equalsIgnoreCase("Unknown") && !isOwner) { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - - Chest chest = (Chest) chestBlock.getState(); - boolean isFull = isInventoryFull(chest.getInventory()); - String colorType = isFull ? "full" : "target"; - - sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); - sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); - 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(); - } - - sign.setLine(3, finalLine4); - sign.update(); - - setTargetChestLocation(player.getUniqueId(), chestBlock.getLocation(), itemInHand.getType()); - player.sendMessage(getMessage("target-chest-set").replace("%item%", itemInHand.getType().name())); - event.setCancelled(true); - return; - } - - // Zugriffsschutz für konfigurierte Truhen - // Wenn nicht Owner und NICHT öffentlich -> Zugriff verweigern - if (!isOwner && !isChestPublic(sign)) { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - - // Wenn Owner oder Öffentlich -> Truhe öffnen lassen (Event nicht canceln) - return; - } - - // --- LOGIK FÜR EINGANGSTRUHEN (INPUT) --- - if (line1Clean.equalsIgnoreCase("input")) { - String line3Raw = lines[3] != null ? lines[3] : ""; - String line3Clean = ChatColor.stripColor(line3Raw); - String pureOwnerName = line3Clean.replace("[Public]", "").replace("[public]", "").trim(); - boolean isOwner = pureOwnerName.equalsIgnoreCase(player.getName()); - - // 1. FALL: MODUS WECHSELN (Shift + Klick + Leere Hand) - if (player.isSneaking() && (itemInHand == null || itemInHand.getType() == Material.AIR)) { - if (isOwner) { - boolean isPublic = isChestPublic(sign); - String newModeText; - String newLine4; - - if (isPublic) { - newModeText = getMessage("mode-private"); - newLine4 = getSignColor("input", "line4") + pureOwnerName; - } else { - newModeText = getMessage("mode-public"); - newLine4 = getSignColor("input", "line4") + pureOwnerName + " " + ChatColor.RESET + "[Public]"; - } - - sign.setLine(3, newLine4); - sign.update(); - player.sendMessage(getMessage("mode-changed").replace("%mode%", newModeText)); - event.setCancelled(true); - return; - } else { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - } - - // Zugriffsschutz - if (!isOwner && !isChestPublic(sign)) { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - } - } - return; - } - - // Truhe-Interaktion (wenn man direkt auf die Truhe klickt und nicht auf das Schild) - if (clickedBlock.getState() instanceof Chest) { - Block chestBlock = clickedBlock; - Block signBlock = null; - - // Suche Schild - for (Block face : new Block[] { - chestBlock.getRelative(1, 0, 0), - chestBlock.getRelative(-1, 0, 0), - chestBlock.getRelative(0, 0, 1), - chestBlock.getRelative(0, 0, -1) - }) { - if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chestBlock)) { - String[] lines = sign.getLines(); - if (lines.length >= 2 && ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")).equalsIgnoreCase("[asc]")) { - signBlock = face; - break; - } - } - } - - if (signBlock != null) { - Sign sign = (Sign) signBlock.getState(); - String[] lines = sign.getLines(); - String line1Clean = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); - - // NUR FÜR ZIELTRUHEN - if (line1Clean.equalsIgnoreCase("ziel")) { - String line3Raw = lines[3] != null ? lines[3] : ""; - String line3Clean = ChatColor.stripColor(line3Raw); - String pureOwnerName = line3Clean.replace("[public]", "").replace("[Public]", "").trim(); - boolean isOwner = pureOwnerName.equalsIgnoreCase(player.getName()); - - // 1. FALL: MODUS WECHSELN - if (player.isSneaking() && (itemInHand == null || itemInHand.getType() == Material.AIR)) { - if (isOwner) { - boolean isPublic = isChestPublic(sign); - String newModeText; - String newLine4; - - String baseName = getSignColor("target", "line4") + pureOwnerName; - - if (isPublic) { - newModeText = getMessage("mode-private"); - newLine4 = baseName; - } else { - newModeText = getMessage("mode-public"); - newLine4 = baseName + " " + ChatColor.RESET + "[Public]"; - } - - sign.setLine(3, newLine4); - sign.update(); - player.sendMessage(getMessage("mode-changed").replace("%mode%", newModeText)); - event.setCancelled(true); - return; - } else { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - } - - // 2. FALL: ITEM ZUWEISEN - String line2Clean = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); - if (line2Clean.isEmpty()) { - if (itemInHand == null || itemInHand.getType() == Material.AIR) { - player.sendMessage(getMessage("no-item-in-hand")); - event.setCancelled(true); - return; - } - - if (!pureOwnerName.isEmpty() && !pureOwnerName.equalsIgnoreCase("Unknown") && !isOwner) { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - - Chest chest = (Chest) chestBlock.getState(); - boolean isFull = isInventoryFull(chest.getInventory()); - String colorType = isFull ? "full" : "target"; - - sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); - sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); - sign.setLine(2, getSignColor(colorType, "line3") + itemInHand.getType().name()); - - String finalLine4 = line3Raw; - if (pureOwnerName.isEmpty() || pureOwnerName.equalsIgnoreCase("Unknown")) { - finalLine4 = getSignColor("target", "line4") + player.getName(); - } - - sign.setLine(3, finalLine4); - sign.update(); - - setTargetChestLocation(player.getUniqueId(), chestBlock.getLocation(), itemInHand.getType()); - player.sendMessage(getMessage("target-chest-set").replace("%item%", itemInHand.getType().name())); - event.setCancelled(true); - return; - } - - // Wenn nicht Owner und nicht öffentlich -> Zugriff verweigern - if (!isOwner && !isChestPublic(sign)) { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - } - - // 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(); - boolean isOwner = pureOwnerName.equalsIgnoreCase(player.getName()); - - // Moduswechsel - if (player.isSneaking() && (itemInHand == null || itemInHand.getType() == Material.AIR)) { - if (isOwner) { - boolean isPublic = isChestPublic(sign); - String newModeText; - String newLine4; - - String baseName = getSignColor("input", "line4") + pureOwnerName; - - if (isPublic) { - newModeText = getMessage("mode-private"); - newLine4 = baseName; - } else { - newModeText = getMessage("mode-public"); - newLine4 = baseName + " " + ChatColor.RESET + "[Public]"; - } - sign.setLine(3, newLine4); - sign.update(); - player.sendMessage(getMessage("mode-changed").replace("%mode%", newModeText)); - event.setCancelled(true); - return; - } else { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - } - - if (!isOwner && !isChestPublic(sign)) { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - return; - } - } - } - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onBlockBreak(BlockBreakEvent event) { - Block block = event.getBlock(); - Player player = event.getPlayer(); - Block signBlock = null; - String signOwner = ""; - boolean isAscChest = false; - - if (isDebug()) { - getLogger().fine("BlockBreakEvent ausgelöst bei " + block.getLocation() + " durch Spieler " + player.getName() + ", GameMode: " + player.getGameMode() + ", Sneaking: " + player.isSneaking() + ", OP: " + player.isOp() + ", Berechtigung autosortchest.bypass: " + player.hasPermission("autosortchest.bypass")); - } - - // Fall 1: Schild wird abgebaut - if (block.getState() instanceof Sign sign) { - String[] lines = sign.getLines(); - 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"))) { - signBlock = block; - signOwner = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); - if (isDebug()) { - getLogger().fine("Schild erkannt bei " + block.getLocation() + ", Besitzer: " + signOwner); - } - } - } - - // Fall 2: Truhe wird abgebaut - if (block.getState() instanceof Chest chestBlock) { - for (Block face : new Block[] { - block.getRelative(1, 0, 0), - block.getRelative(-1, 0, 0), - block.getRelative(0, 0, 1), - block.getRelative(0, 0, -1) - }) { - if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, block)) { - String[] lines = sign.getLines(); - 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"))) { - signBlock = face; - signOwner = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); - isAscChest = true; - if (isDebug()) { - getLogger().fine("Truhe mit [asc]-Schild erkannt bei " + block.getLocation() + ", Schild bei " + face.getLocation() + ", Besitzer: " + signOwner); - } - break; - } - } - } - } - - // Kein [asc]-Schild oder keine Truhe mit [asc]-Schild - if (signBlock == null) { - if (isDebug()) { - getLogger().fine("Kein [asc]-Schild oder Truhe mit [asc]-Schild bei " + block.getLocation()); - } - return; - } - - // Prüfe Besitzer - boolean isOwner = signOwner.isEmpty() || signOwner.equalsIgnoreCase(player.getName()); - - if (!isOwner) { - player.sendMessage(getMessage("not-your-chest")); - event.setCancelled(true); - getLogger().warning("Spieler " + player.getName() + " versuchte, " + (isAscChest ? "Truhe" : "Schild") + " bei " + block.getLocation() + " abzubauen, das nicht ihm gehört (Besitzer: " + signOwner + ")"); - return; - } - - // Prüfe Shift-Taste (Berechtigungen ignorieren) - if (!player.isSneaking()) { - player.sendMessage(getMessage("sign-break-denied")); - event.setCancelled(true); - if (isDebug()) { - getLogger().fine("Spieler " + player.getName() + " versuchte, " + (isAscChest ? "Truhe" : "Schild") + " bei " + block.getLocation() + " ohne Shift abzubauen"); - } - return; - } - - if (isDebug()) { - getLogger().fine("Spieler " + player.getName() + " hat " + (isAscChest ? "Truhe" : "Schild") + " bei " + block.getLocation() + " erfolgreich abgebaut"); - } - } - - @EventHandler - public void onInventoryClose(InventoryCloseEvent event) { - if (!(event.getInventory().getHolder() instanceof Chest chest)) return; - - Player player = (Player) event.getPlayer(); - Block chestBlock = chest.getBlock(); - Block signBlock = null; - - // Suche nach einem an die Truhe angehängten Schild - for (Block face : new Block[] { - chestBlock.getRelative(1, 0, 0), - chestBlock.getRelative(-1, 0, 0), - chestBlock.getRelative(0, 0, 1), - chestBlock.getRelative(0, 0, -1) - }) { - if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chestBlock)) { - String[] lines = sign.getLines(); - if (lines.length >= 2 && ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")).equalsIgnoreCase("[asc]") && ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("ziel")) { - signBlock = face; - break; - } - } - } - - if (signBlock == null) { - if (isDebug()) getLogger().fine("Keine Zieltruhe-Schild an Truhe bei " + chestBlock.getLocation()); - return; - } - - Sign sign = (Sign) signBlock.getState(); - String[] lines = sign.getLines(); - String signOwner = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); - if (!signOwner.equalsIgnoreCase(player.getName())) { - if (isDebug()) getLogger().fine("Schild bei " + signBlock.getLocation() + " gehört nicht Spieler " + player.getName() + " (Besitzer: " + signOwner + ")"); - return; - } - - boolean isFull = isInventoryFull(chest.getInventory()); - String colorType = isFull ? "full" : "target"; - - String currentLine0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); - String currentLine1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); - String currentLine2 = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); - String currentLine3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); - - if (currentLine0.equalsIgnoreCase("[asc]") && currentLine1.equalsIgnoreCase("ziel")) { - sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); - sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); - sign.setLine(2, getSignColor(colorType, "line3") + currentLine2); - sign.setLine(3, getSignColor(colorType, "line4") + currentLine3); - sign.update(); - if (isDebug()) { - getLogger().fine("Zieltruhe-Schild bei " + signBlock.getLocation() + " aktualisiert nach Inventar-Schließung (voll: " + isFull + ")"); - } - } - } - - private void distributeItems(Player player, Inventory sourceInventory) { - UUID playerUUID = player.getUniqueId(); - Block chestBlock = ((Chest) sourceInventory.getHolder()).getBlock(); - - // Prüfe, ob die Truhe leer ist - boolean hasItems = false; - for (ItemStack item : sourceInventory.getContents()) { - if (item != null && item.getType() != Material.AIR) { - hasItems = true; - break; - } - } - if (!hasItems) { - if (isDebug()) getLogger().fine("Eingangstruhe bei " + chestBlock.getLocation() + " ist leer"); - return; - } - - for (int slot = 0; slot < sourceInventory.getSize(); slot++) { - ItemStack item = sourceInventory.getItem(slot); - if (item == null || item.getType() == Material.AIR) continue; - - Location targetChestLocation = getTargetChestLocation(playerUUID, item.getType()); - if (targetChestLocation == null) { - if (isDebug()) getLogger().fine("Keine Zieltruhe für Item " + item.getType().name() + " für Spieler " + player.getName()); - continue; - } - - if (!(targetChestLocation.getBlock().getState() instanceof Chest)) { - if (canSendFullChestMessage(playerUUID, item.getType())) { - player.sendMessage(getMessage("target-chest-missing").replace("%item%", item.getType().name())); - } - playerData.set("players." + playerUUID + ".target-chests." + item.getType().name(), null); - savePlayerData(); - getLogger().warning("Zieltruhe für " + item.getType().name() + " fehlt bei " + targetChestLocation); - continue; - } - - Chest targetChest = (Chest) targetChestLocation.getBlock().getState(); - Inventory targetInventory = targetChest.getInventory(); - - boolean isValidTarget = false; - String signOwner = "Unbekannt"; - Block signBlock = null; - for (Block face : new Block[] { - targetChest.getBlock().getRelative(1, 0, 0), - targetChest.getBlock().getRelative(-1, 0, 0), - targetChest.getBlock().getRelative(0, 0, 1), - targetChest.getBlock().getRelative(0, 0, -1) - }) { - if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, targetChest.getBlock())) { - String[] lines = sign.getLines(); - String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); - String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); - String line3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); - if (isDebug()) { - getLogger().fine("Prüfe Zieltruhe-Schild bei " + face.getLocation() + ": Zeile 1='" + line0 + "', Zeile 2='" + line1 + "', Zeile 4='" + line3 + "' für Spieler " + player.getName()); - } - if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { - signOwner = line3.isEmpty() ? "Unbekannt" : line3; - if (line3.equalsIgnoreCase(player.getName())) { - isValidTarget = true; - signBlock = face; - if (isDebug()) { - getLogger().fine("Gültiges Zieltruhe-Schild gefunden bei " + face.getLocation() + " für Spieler " + player.getName()); - } - break; - } - } else if (isDebug()) { - getLogger().fine("Zieltruhe-Schild bei " + face.getLocation() + " hat ungültige Zeilen: [asc]=" + line0 + ", ziel=" + line1); - } - } - } - - if (!isValidTarget) { - if (canSendFullChestMessage(playerUUID, item.getType())) { - player.sendMessage(getMessage("not-your-chest")); - } - getLogger().warning("Zieltruhe bei " + targetChestLocation + " gehört nicht Spieler " + player.getName() + " (Besitzer: " + signOwner + ")"); - continue; - } - - ItemStack itemToTransfer = item.clone(); - Map leftover = targetInventory.addItem(itemToTransfer); - boolean isFull = isInventoryFull(targetInventory); - - if (signBlock != null) { - Sign sign = (Sign) signBlock.getState(); - String[] lines = sign.getLines(); - String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); - String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); - String line2 = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); - String line3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); - if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { - String colorType = isFull ? "full" : "target"; - sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); - sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); - sign.setLine(2, getSignColor(colorType, "line3") + line2); - sign.setLine(3, getSignColor(colorType, "line4") + line3); - sign.update(); - if (isDebug()) { - getLogger().fine("Zieltruhe-Schild bei " + signBlock.getLocation() + " aktualisiert (voll: " + isFull + ")"); - } - } - } - - if (leftover.isEmpty()) { - sourceInventory.setItem(slot, null); - } else { - if (canSendFullChestMessage(playerUUID, item.getType())) { - String message = getMessage("target-chest-full") - .replace("%item%", item.getType().name()) - .replace("%x%", String.valueOf(targetChestLocation.getBlockX())) - .replace("%y%", String.valueOf(targetChestLocation.getBlockY())) - .replace("%z%", String.valueOf(targetChestLocation.getBlockZ())); - player.sendMessage(message); - } - for (ItemStack leftoverItem : leftover.values()) { - if (leftoverItem != null && leftoverItem.getType() == item.getType()) { - item.setAmount(leftoverItem.getAmount()); - sourceInventory.setItem(slot, item); - break; - } - } - } - } - } - - private void checkInputChests() { - if (playerData == null) { - getLogger().warning("playerData ist null. Kann Eingangstruhe nicht prüfen."); - return; - } - if (playerData.getConfigurationSection("players") == null) { - getLogger().warning("Abschnitt 'players' in players.yml fehlt. Keine Eingangstruhe zu prüfen."); - return; - } - - for (String uuidString : playerData.getConfigurationSection("players").getKeys(false)) { - UUID ownerUUID; - try { - ownerUUID = UUID.fromString(uuidString); - } catch (IllegalArgumentException e) { - getLogger().warning("Ungültige UUID in players.yml: " + uuidString); - continue; - } - - String path = "players." + uuidString + ".input-chest"; - if (!playerData.contains(path)) continue; - - String worldName = playerData.getString(path + ".world"); - World world = getServer().getWorld(worldName); - if (world == null) { - getLogger().warning("Welt " + worldName + " für Eingangstruhe von UUID " + uuidString + " nicht gefunden"); - continue; - } - - int x = playerData.getInt(path + ".x"); - int y = playerData.getInt(path + ".y"); - int z = playerData.getInt(path + ".z"); - Location location = new Location(world, x, y, z); - - if (!(location.getBlock().getState() instanceof Chest)) { - if (isDebug()) { - getLogger().fine("Eingangstruhe bei " + location + " existiert nicht"); - } - continue; - } - - Chest chest = (Chest) location.getBlock().getState(); - Block inputSignBlock = null; - boolean isPublic = false; - String ownerName = "Unknown"; - - // Schild suchen und Status prüfen - for (Block face : new Block[] { - chest.getBlock().getRelative(1, 0, 0), - chest.getBlock().getRelative(-1, 0, 0), - chest.getBlock().getRelative(0, 0, 1), - chest.getBlock().getRelative(0, 0, -1) - }) { - if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chest.getBlock())) { - 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")) { - inputSignBlock = face; - String line3Raw = lines[3] != null ? lines[3] : ""; - String line3Clean = ChatColor.stripColor(line3Raw); - - isPublic = line3Clean.toLowerCase().endsWith("[public]"); - ownerName = line3Clean.replace(" [Public]", "").replace(" [public]", "").trim(); - break; - } - } - } - - if (inputSignBlock == null) { - if (isDebug()) getLogger().fine("Kein Eingangsschild an Truhe bei " + location); - continue; - } - - // Wenn das Inventar leer ist, brauchen wir nichts tun - boolean hasItems = false; - for (ItemStack item : chest.getInventory().getContents()) { - if (item != null && item.getType() != Material.AIR) { - hasItems = true; - break; - } - } - if (!hasItems) continue; - - // Jetzt kommt der entscheidende Teil für die Multiplayer-Funktionalität: - // Wenn PRIVATE: Nur sortieren, wenn der Owner ONLINE ist. - // Wenn ÖFFENTLICH: Immer sortieren, aber wir brauchen einen "fiktiven" Player für die Fehlermeldungen - // oder wir senden Meldungen an alle Online-Spieler, die in der Nähe sind? - // Der Einfachheit halber: Wenn Öffentlich, sortieren wir stumm oder senden Meldungen an den Owner wenn er online ist. - - Player ownerPlayer = getServer().getPlayer(ownerUUID); - - if (!isPublic) { - // Privat: Nur wenn Owner online - if (ownerPlayer == null || !ownerPlayer.isOnline()) continue; - } else { - // Öffentlich: Wenn Owner offline, können wir keine "Truhe voll" Nachrichten an den Owner senden. - // Wir sortieren trotzdem. - // Wir setzen ownerPlayer auf null, damit distributeItems weiß, dass niemand Besitzer ist (für Messages). - } - - // Wir rufen distributeItems auf. - // WICHTIG: distributeItems nutzt `player.getUniqueId()` um die Zieltruhen zu finden. - // Das funktioniert auch, wenn ownerPlayer null ist, solange wir die UUID übergeben. - // Wir müssen aber aufpassen, dass `distributeItems` nicht crasht, wenn player null ist. - - distributeItemsForOwner(ownerUUID, ownerPlayer, chest.getInventory()); - } - } - - // 4. KORRIGIERTE distributeItemsForOwner Methode: - private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory) { - boolean hasItems = false; - for (ItemStack item : sourceInventory.getContents()) { - if (item != null && item.getType() != Material.AIR) { - hasItems = true; - break; - } - } - if (!hasItems) return; - - // Owner-Name ermitteln - String ownerName = "Unknown"; - if (ownerPlayer != null) { - ownerName = ownerPlayer.getName(); - } else { - // Offline-Namen aus PlayerData holen wenn möglich - org.bukkit.OfflinePlayer offlinePlayer = getServer().getOfflinePlayer(ownerUUID); - if (offlinePlayer.hasPlayedBefore()) { - ownerName = offlinePlayer.getName(); - } - } - - for (int slot = 0; slot < sourceInventory.getSize(); slot++) { - ItemStack item = sourceInventory.getItem(slot); - if (item == null || item.getType() == Material.AIR) continue; - - Location targetChestLocation = getTargetChestLocation(ownerUUID, item.getType()); - if (targetChestLocation == null) continue; - - if (!(targetChestLocation.getBlock().getState() instanceof Chest)) { - if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { - ownerPlayer.sendMessage(getMessage("target-chest-missing").replace("%item%", item.getType().name())); - } - playerData.set("players." + ownerUUID + ".target-chests." + item.getType().name(), null); - savePlayerData(); - continue; - } - - Chest targetChest = (Chest) targetChestLocation.getBlock().getState(); - Inventory targetInventory = targetChest.getInventory(); - - boolean isValidTarget = false; - Block signBlock = null; - - for (Block face : new Block[] { - targetChest.getBlock().getRelative(1, 0, 0), - targetChest.getBlock().getRelative(-1, 0, 0), - targetChest.getBlock().getRelative(0, 0, 1), - targetChest.getBlock().getRelative(0, 0, -1) - }) { - if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, targetChest.getBlock())) { - String[] lines = sign.getLines(); - String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); - String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); - String line3Clean = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); - - String signOwnerName = line3Clean.replace("[Public]", "").replace("[public]", "").trim(); - - if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { - if (signOwnerName.equalsIgnoreCase(ownerName) || signOwnerName.equalsIgnoreCase("Unknown")) { - isValidTarget = true; - signBlock = face; - break; - } - } - } - } - - if (!isValidTarget) continue; - - ItemStack itemToTransfer = item.clone(); - Map leftover = targetInventory.addItem(itemToTransfer); - boolean isFull = isInventoryFull(targetInventory); - - if (signBlock != null) { - Sign sign = (Sign) signBlock.getState(); - String[] lines = sign.getLines(); - String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); - String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); - String line2 = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); - String line3Raw = lines[3] != null ? lines[3] : ""; - - if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { - String colorType = isFull ? "full" : "target"; - sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); - sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); - sign.setLine(2, getSignColor(colorType, "line3") + line2); - sign.setLine(3, line3Raw); - sign.update(); - } - } - - if (leftover.isEmpty()) { - sourceInventory.setItem(slot, null); - } else { - if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { - String message = getMessage("target-chest-full") - .replace("%item%", item.getType().name()) - .replace("%x%", String.valueOf(targetChestLocation.getBlockX())) - .replace("%y%", String.valueOf(targetChestLocation.getBlockY())) - .replace("%z%", String.valueOf(targetChestLocation.getBlockZ())); - ownerPlayer.sendMessage(message); - } - for (ItemStack leftoverItem : leftover.values()) { - if (leftoverItem != null && leftoverItem.getType() == item.getType()) { - item.setAmount(leftoverItem.getAmount()); - sourceInventory.setItem(slot, item); - break; - } - } - } - } - } +package com.viper.autosortchest; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.Chest; +import org.bukkit.block.Sign; +import org.bukkit.block.data.type.WallSign; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +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.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.ChatColor; +import org.bukkit.Material; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; + +public class Main extends JavaPlugin implements Listener, CommandExecutor { + private File playerDataFile; + private FileConfiguration playerData; + 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.4"; + + @Override + public void onEnable() { + // Lade Standard-Konfiguration + saveDefaultConfig(); + config = getConfig(); + + // Lade players.yml + playerDataFile = new File(getDataFolder(), "players.yml"); + if (!playerDataFile.exists()) { + saveResource("players.yml", false); + } + loadPlayerData(); + + // Registriere Events + getServer().getPluginManager().registerEvents(this, this); + + // Registriere Befehle + this.getCommand("asc").setExecutor(this); + + // Starte periodischen Task + new BukkitRunnable() { + @Override + public void run() { + checkInputChests(); + } + }.runTaskTimer(this, 20L, 20L); // Alle 1 Sekunde (20 Ticks) + + // Starte Task zum Bereinigen des Message-Trackers + new BukkitRunnable() { + @Override + public void run() { + cleanMessageTracker(); + } + }.runTaskTimer(this, 20L * 60, 20L * 60); // Alle Minute + + // Aktualisiere Konfiguration und Schilder + updateConfig(); + updateExistingSigns(); + + // ASCII ART LOGO + getLogger().info(""); + getLogger().info(" ___ _ _____ _ _____ _ _ "); + getLogger().info(" / _ \\ | | / ___| | | / __ \\ | | | "); + getLogger().info("/ /_\\ \\_ _| |_ ___ \\ `--. ___ _ __| |_| / \\/ |__ ___ ___| |_ "); + getLogger().info("| _ | | | | __/ _ \\ `--. \\/ _ \\| '__| __| | | '_ \\ / _ \\/ __| __|"); + getLogger().info("| | | | |_| | || (_) /\\__/ / (_) | | | |_| \\__/\\ | | | __/\\__ \\ |_ "); + getLogger().info("\\_| |_/\\__,_|\\__\\___/\\____/ \\___/|_| \\__|\\____/_| |_|\\___||___/\\__|"); + getLogger().info(""); + + getLogger().info("AutoSortChest Plugin aktiviert! Version: " + getDescription().getVersion()); + } + + @Override + public void onDisable() { + savePlayerData(); + getLogger().info("AutoSortChest Plugin deaktiviert!"); + } + + private void savePlayerData() { + if (playerData == null || playerDataFile == null) { + getLogger().warning("Kann players.yml nicht speichern: playerData oder playerDataFile ist null"); + return; + } + try { + playerData.save(playerDataFile); + getLogger().info("players.yml erfolgreich gespeichert"); + } catch (IOException e) { + getLogger().warning("Fehler beim Speichern der players.yml: " + e.getMessage()); + } + } + + private void loadPlayerData() { + if (playerDataFile == null) { + playerDataFile = new File(getDataFolder(), "players.yml"); + } + try { + if (!playerDataFile.exists()) { + saveResource("players.yml", false); + getLogger().info("Neue players.yml erstellt"); + } + playerData = YamlConfiguration.loadConfiguration(playerDataFile); + if (playerData.getConfigurationSection("players") == null) { + getLogger().warning("Abschnitt 'players' in players.yml fehlt. Initialisiere leer."); + playerData.createSection("players"); + savePlayerData(); + } else { + int playerCount = playerData.getConfigurationSection("players").getKeys(false).size(); + getLogger().info("players.yml geladen mit " + playerCount + " Spieler-Einträgen"); + } + } catch (Exception e) { + getLogger().warning("Fehler beim Laden von players.yml: " + e.getMessage()); + playerData = new YamlConfiguration(); + playerData.createSection("players"); + savePlayerData(); + } + } + + private void updateConfig() { + File configFile = new File(getDataFolder(), "config.yml"); + InputStream defaultConfigStream = getResource("config.yml"); + FileConfiguration defaultConfig; + + // Lade Standardkonfiguration + if (defaultConfigStream == null) { + getLogger().warning("Standard-config.yml nicht gefunden in Plugin-Ressourcen! Verwende Fallback-Werte."); + defaultConfig = new YamlConfiguration(); + } else { + try { + defaultConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(defaultConfigStream)); + getLogger().info("Standard-config.yml erfolgreich geladen."); + } catch (Exception e) { + getLogger().warning("Fehler beim Laden von Standard-config.yml: " + e.getMessage()); + defaultConfig = new YamlConfiguration(); + } + } + + // Prüfe Konfigurationsversion + String currentVersion = config.getString("version", "1.0"); + boolean versionUpdated = false; + if (!currentVersion.equals(CONFIG_VERSION)) { + getLogger().info("Aktualisiere config.yml von Version " + currentVersion + " auf " + CONFIG_VERSION); + config.set("version", CONFIG_VERSION); + versionUpdated = true; + } + + // Setze Standardwerte für fehlende Schlüssel + if (!config.contains("debug")) { + config.set("debug", defaultConfig.getBoolean("debug", false)); + getLogger().info("Setze Standardwert: debug = " + config.getBoolean("debug")); + } + + // 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", "&f")); + getLogger().info("Setze Standardwerte für sign-colors.input"); + } else { + if (!config.contains("sign-colors.input.line1")) { + config.set("sign-colors.input.line1", defaultConfig.getString("sign-colors.input.line1", "&6")); + getLogger().info("Setze Standardwert: sign-colors.input.line1 = " + config.getString("sign-colors.input.line1")); + } + if (!config.contains("sign-colors.input.line2")) { + config.set("sign-colors.input.line2", defaultConfig.getString("sign-colors.input.line2", "&0")); + getLogger().info("Setze Standardwert: sign-colors.input.line2 = " + config.getString("sign-colors.input.line2")); + } + if (!config.contains("sign-colors.input.line4")) { + config.set("sign-colors.input.line4", defaultConfig.getString("sign-colors.input.line4", "&f")); + getLogger().info("Setze Standardwert: sign-colors.input.line4 = " + config.getString("sign-colors.input.line4")); + } + } + + // Prüfe und setze sign-colors.target + if (!config.contains("sign-colors.target")) { + config.createSection("sign-colors.target"); + 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", "&f")); + getLogger().info("Setze Standardwerte für sign-colors.target"); + } else { + if (!config.contains("sign-colors.target.line1")) { + config.set("sign-colors.target.line1", defaultConfig.getString("sign-colors.target.line1", "&6")); + getLogger().info("Setze Standardwert: sign-colors.target.line1 = " + config.getString("sign-colors.target.line1")); + } + if (!config.contains("sign-colors.target.line2")) { + config.set("sign-colors.target.line2", defaultConfig.getString("sign-colors.target.line2", "&0")); + getLogger().info("Setze Standardwert: sign-colors.target.line2 = " + config.getString("sign-colors.target.line2")); + } + if (!config.contains("sign-colors.target.line3")) { + config.set("sign-colors.target.line3", defaultConfig.getString("sign-colors.target.line3", "&f")); + getLogger().info("Setze Standardwert: sign-colors.target.line3 = " + config.getString("sign-colors.target.line3")); + } + if (!config.contains("sign-colors.target.line4")) { + config.set("sign-colors.target.line4", defaultConfig.getString("sign-colors.target.line4", "&f")); + getLogger().info("Setze Standardwert: sign-colors.target.line4 = " + config.getString("sign-colors.target.line4")); + } + } + + // Prüfe und setze sign-colors.full + if (!config.contains("sign-colors.full")) { + config.createSection("sign-colors.full"); + 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", "&e")); + getLogger().info("Setze Standardwerte für sign-colors.full"); + } else { + if (!config.contains("sign-colors.full.line1")) { + config.set("sign-colors.full.line1", defaultConfig.getString("sign-colors.full.line1", "&c")); + getLogger().info("Setze Standardwert: sign-colors.full.line1 = " + config.getString("sign-colors.full.line1")); + } + if (!config.contains("sign-colors.full.line2")) { + config.set("sign-colors.full.line2", defaultConfig.getString("sign-colors.full.line2", "&4")); + getLogger().info("Setze Standardwert: sign-colors.full.line2 = " + config.getString("sign-colors.full.line2")); + } + if (!config.contains("sign-colors.full.line3")) { + config.set("sign-colors.full.line3", defaultConfig.getString("sign-colors.full.line3", "&e")); + getLogger().info("Setze Standardwert: sign-colors.full.line3 = " + config.getString("sign-colors.full.line3")); + } + if (!config.contains("sign-colors.full.line4")) { + config.set("sign-colors.full.line4", defaultConfig.getString("sign-colors.full.line4", "&e")); + getLogger().info("Setze Standardwert: sign-colors.full.line4 = " + config.getString("sign-colors.full.line4")); + } + } + + // Prüfe und setze messages + if (!config.contains("messages.no-chest-near-sign")) { + config.set("messages.no-chest-near-sign", defaultConfig.getString("messages.no-chest-near-sign", "&cKeine Truhe in der Nähe des Schildes!")); + getLogger().info("Setze Standardwert: messages.no-chest-near-sign"); + } + if (!config.contains("messages.no-item-in-hand")) { + config.set("messages.no-item-in-hand", defaultConfig.getString("messages.no-item-in-hand", "&cDu musst ein Item in der Hand halten!")); + getLogger().info("Setze Standardwert: messages.no-item-in-hand"); + } + if (!config.contains("messages.not-your-chest")) { + config.set("messages.not-your-chest", defaultConfig.getString("messages.not-your-chest", "&cDiese Truhe gehört dir nicht!")); + getLogger().info("Setze Standardwert: messages.not-your-chest"); + } + if (!config.contains("messages.input-chest-set")) { + config.set("messages.input-chest-set", defaultConfig.getString("messages.input-chest-set", "&aEingangstruhe erfolgreich gesetzt!")); + 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!")); + getLogger().info("Setze Standardwert: messages.target-chest-set"); + } + if (!config.contains("messages.target-chest-missing")) { + config.set("messages.target-chest-missing", defaultConfig.getString("messages.target-chest-missing", "&cZieltruhe für %item% fehlt!")); + getLogger().info("Setze Standardwert: messages.target-chest-missing"); + } + String targetChestFull = config.getString("messages.target-chest-full", ""); + String defaultTargetChestFull = defaultConfig.getString("messages.target-chest-full", "&cZieltruhe für %item% ist voll! Koordinaten: (%x%, %y%, %z%)"); + if (!config.contains("messages.target-chest-full") || !targetChestFull.equals(defaultTargetChestFull)) { + config.set("messages.target-chest-full", defaultTargetChestFull); + getLogger().info("Setze oder aktualisiere Standardwert: messages.target-chest-full = " + defaultTargetChestFull); + } + + // --- NEUE NACHRICHTEN FÜR ÖFFENTLICHEN MODUS --- + if (!config.contains("messages.mode-changed")) { + config.set("messages.mode-changed", defaultConfig.getString("messages.mode-changed", "&aModus gewechselt: &e%mode%")); + getLogger().info("Setze Standardwert: messages.mode-changed"); + } + if (!config.contains("messages.mode-public")) { + config.set("messages.mode-public", defaultConfig.getString("messages.mode-public", "&aÖffentlich")); + getLogger().info("Setze Standardwert: messages.mode-public"); + } + if (!config.contains("messages.mode-private")) { + config.set("messages.mode-private", defaultConfig.getString("messages.mode-private", "&cPrivat")); + getLogger().info("Setze Standardwert: messages.mode-private"); + } + // ---------------------------------------------- + + 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" + + "&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"); + } + 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"); + } + if (!config.contains("messages.reload-success")) { + config.set("messages.reload-success", defaultConfig.getString("messages.reload-success", "&aKonfiguration erfolgreich neu geladen!")); + getLogger().info("Setze Standardwert: messages.reload-success"); + } + if (!config.contains("messages.sign-break-denied")) { + config.set("messages.sign-break-denied", defaultConfig.getString("messages.sign-break-denied", "&cDu musst Shift gedrückt halten, um dieses Schild oder die Truhe abzubauen!")); + getLogger().info("Setze Standardwert: messages.sign-break-denied"); + } + + // Speichere die aktualisierte Konfiguration + try { + config.save(configFile); + if (configFile.exists() && configFile.length() > 0) { + getLogger().info("config.yml erfolgreich aktualisiert und gespeichert auf Version " + CONFIG_VERSION); + } else { + getLogger().warning("config.yml wurde nicht korrekt gespeichert (Datei existiert nicht oder ist leer)"); + } + } catch (IOException e) { + getLogger().warning("Fehler beim Speichern der config.yml: " + e.getMessage()); + } + } + + private void updateExistingSigns() { + if (playerData == null) { + getLogger().warning("playerData ist null. Kann bestehende Schilder nicht aktualisieren."); + return; + } + if (playerData.getConfigurationSection("players") == null) { + getLogger().warning("Abschnitt 'players' in players.yml fehlt. Keine Schilder zum Aktualisieren."); + return; + } + + for (String uuidString : playerData.getConfigurationSection("players").getKeys(false)) { + UUID playerUUID; + try { + playerUUID = UUID.fromString(uuidString); + } catch (IllegalArgumentException e) { + getLogger().warning("Ungültige UUID in players.yml: " + uuidString); + continue; + } + + String path = "players." + uuidString; + + // Eingangstruhe + String inputPath = path + ".input-chest"; + if (playerData.contains(inputPath)) { + String worldName = playerData.getString(inputPath + ".world"); + World world = getServer().getWorld(worldName); + if (world == null) { + getLogger().warning("Welt " + worldName + " für Eingangstruhe von Spieler UUID " + uuidString + " nicht gefunden"); + continue; + } + int x = playerData.getInt(inputPath + ".x"); + int y = playerData.getInt(inputPath + ".y"); + int z = playerData.getInt(inputPath + ".z"); + Location chestLocation = new Location(world, x, y, z); + Block chestBlock = chestLocation.getBlock(); + + if (!(chestBlock.getState() instanceof Chest)) { + getLogger().warning("Eingangstruhe bei " + chestLocation + " ist keine Truhe"); + continue; + } + + for (Block face : new Block[] { + chestBlock.getRelative(1, 0, 0), + chestBlock.getRelative(-1, 0, 0), + chestBlock.getRelative(0, 0, 1), + chestBlock.getRelative(0, 0, -1) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chestBlock)) { + String[] lines = sign.getLines(); + String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); + String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + String line3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); + if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("input")) { + sign.setLine(0, getSignColor("input", "line1") + "[asc]"); + sign.setLine(1, getSignColor("input", "line2") + "input"); + sign.setLine(3, getSignColor("input", "line4") + line3); + sign.update(); + if (isDebug()) { + getLogger().fine("Eingangsschild bei " + face.getLocation() + " für Spieler UUID " + uuidString + " aktualisiert"); + } + } + } + } + } + + // Zieltruhen + String targetPath = path + ".target-chests"; + if (playerData.contains(targetPath)) { + for (String itemType : playerData.getConfigurationSection(targetPath).getKeys(false)) { + String targetChestPath = targetPath + "." + itemType; + String worldName = playerData.getString(targetChestPath + ".world"); + World world = getServer().getWorld(worldName); + if (world == null) { + getLogger().warning("Welt " + worldName + " für Zieltruhe von Item " + itemType + " nicht gefunden"); + continue; + } + int x = playerData.getInt(targetChestPath + ".x"); + int y = playerData.getInt(targetChestPath + ".y"); + int z = playerData.getInt(targetChestPath + ".z"); + Location chestLocation = new Location(world, x, y, z); + Block chestBlock = chestLocation.getBlock(); + + if (!(chestBlock.getState() instanceof Chest)) { + getLogger().warning("Zieltruhe für Item " + itemType + " bei " + chestLocation + " ist keine Truhe"); + continue; + } + + Chest chest = (Chest) chestBlock.getState(); + Inventory inventory = chest.getInventory(); + boolean isFull = isInventoryFull(inventory); + + for (Block face : new Block[] { + chestBlock.getRelative(1, 0, 0), + chestBlock.getRelative(-1, 0, 0), + chestBlock.getRelative(0, 0, 1), + chestBlock.getRelative(0, 0, -1) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chestBlock)) { + String[] lines = sign.getLines(); + String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); + String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + String line2 = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); + String line3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); + if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { + String colorType = isFull ? "full" : "target"; + sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); + sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); + sign.setLine(2, getSignColor(colorType, "line3") + line2); + sign.setLine(3, getSignColor(colorType, "line4") + line3); + sign.update(); + if (isDebug()) { + getLogger().fine("Zieltruhe-Schild für Item " + itemType + " bei " + face.getLocation() + " aktualisiert (voll: " + isFull + ")"); + } + } + } + } + } + } + } + } + + private boolean isInventoryFull(Inventory inventory) { + for (ItemStack item : inventory.getContents()) { + if (item == null || item.getAmount() < item.getMaxStackSize()) { + return false; + } + } + return true; + } + + private boolean isChestPublic(Sign sign) { + String line3 = sign.getLine(3); // MIT Farbcodes! + String line3Clean = ChatColor.stripColor(line3); + return line3Clean.toLowerCase().contains("[public]"); + } + + private boolean isDebug() { + return config != null && config.getBoolean("debug", false); + } + + private String getMessage(String key) { + if (config == null) { + return ChatColor.RED + "Fehlende Konfiguration: " + key; + } + String message = config.getString("messages." + key, "Fehlende Nachricht: " + key); + return ChatColor.translateAlternateColorCodes('&', message); + } + + private String getSignColor(String type, String line) { + if (config == null) { + return "&f"; + } + String color = config.getString("sign-colors." + type + "." + line, "&f"); + return ChatColor.translateAlternateColorCodes('&', color); + } + + private boolean isSignAttachedToChest(Block signBlock, Block chestBlock) { + if (!(signBlock.getBlockData() instanceof WallSign)) { + if (isDebug()) getLogger().fine("Schild bei " + signBlock.getLocation() + " ist kein Wandschild"); + return false; + } + WallSign wallSign = (WallSign) signBlock.getBlockData(); + Block attachedBlock = signBlock.getRelative(wallSign.getFacing().getOppositeFace()); + boolean attached = attachedBlock.equals(chestBlock); + if (!attached && isDebug()) { + getLogger().fine("Schild bei " + signBlock.getLocation() + " ist nicht an Truhe bei " + chestBlock.getLocation() + " angebracht"); + } + return attached; + } + + private void setInputChestLocation(UUID playerUUID, Location location) { + String path = "players." + playerUUID + ".input-chest"; + 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(); + } + + private void setTargetChestLocation(UUID playerUUID, Location location, Material itemType) { + String path = "players." + playerUUID + ".target-chests." + itemType.name(); + 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(); + getLogger().info("Zieltruhe für " + getServer().getPlayer(playerUUID).getName() + " (" + itemType.name() + ") gesetzt bei " + location); + } + + private Location getTargetChestLocation(UUID playerUUID, Material itemType) { + String path = "players." + playerUUID + ".target-chests." + itemType.name(); + if (!playerData.contains(path)) { + if (isDebug()) getLogger().fine("Keine Zieltruhe für Item " + itemType.name() + " für Spieler UUID " + playerUUID); + return null; + } + + String worldName = playerData.getString(path + ".world"); + World world = getServer().getWorld(worldName); + if (world == null) { + getLogger().warning("Welt " + worldName + " für Zieltruhe von Item " + itemType.name() + " nicht gefunden"); + return null; + } + + int x = playerData.getInt(path + ".x"); + int y = playerData.getInt(path + ".y"); + int z = playerData.getInt(path + ".z"); + + return new Location(world, x, y, z); + } + + private void cleanMessageTracker() { + long currentTime = System.currentTimeMillis(); + fullChestMessageTracker.entrySet().removeIf(entry -> { + Map messages = entry.getValue(); + messages.entrySet().removeIf(msg -> currentTime - msg.getValue() > MESSAGE_COOLDOWN); + return messages.isEmpty(); + }); + } + + private boolean canSendFullChestMessage(UUID playerUUID, Material material) { + Map playerMessages = fullChestMessageTracker.computeIfAbsent(playerUUID, k -> new HashMap<>()); + Long lastMessageTime = playerMessages.get(material); + long currentTime = System.currentTimeMillis(); + + if (lastMessageTime == null || currentTime - lastMessageTime > MESSAGE_COOLDOWN) { + playerMessages.put(material, currentTime); + return true; + } + 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")); + return true; + } + reloadConfig(); + config = getConfig(); + loadPlayerData(); + updateConfig(); + updateExistingSigns(); + player.sendMessage(getMessage("reload-success")); + getLogger().info("Konfiguration und Spielerdaten neu geladen durch " + player.getName()); + return true; + } + + String helpMessage = getMessage("help"); + player.sendMessage(helpMessage.split("\n")); + return true; + } + + @EventHandler + public void onSignChange(SignChangeEvent event) { + Player player = event.getPlayer(); + UUID playerUUID = player.getUniqueId(); + Block signBlock = event.getBlock(); + String[] lines = event.getLines(); + + if (lines.length >= 2 && lines[0].equalsIgnoreCase("[asc]") && lines[1].equalsIgnoreCase("input")) { + Block chestBlock = null; + if (signBlock.getBlockData() instanceof WallSign wallSign) { + Block attachedBlock = signBlock.getRelative(wallSign.getFacing().getOppositeFace()); + if (attachedBlock.getState() instanceof Chest) { + chestBlock = attachedBlock; + } + } + + if (chestBlock == null) { + player.sendMessage(getMessage("no-chest-near-sign")); + getLogger().warning("Keine Truhe an Schild bei " + signBlock.getLocation() + " für Spieler " + player.getName()); + return; + } + + 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()); + 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) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + + Player player = event.getPlayer(); + UUID playerUUID = player.getUniqueId(); + Block clickedBlock = event.getClickedBlock(); + ItemStack itemInHand = event.getItem(); + + if (clickedBlock == null) return; + + // Schild-Interaktion + if (clickedBlock.getState() instanceof Sign sign) { + String[] lines = sign.getLines(); + if (lines.length >= 2 && ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")).equalsIgnoreCase("[asc]")) { + + // Truhe finden + Block chestBlock = null; + if (sign.getBlockData() instanceof WallSign wallSign) { + Block attachedBlock = sign.getBlock().getRelative(wallSign.getFacing().getOppositeFace()); + if (attachedBlock.getState() instanceof Chest) { + chestBlock = attachedBlock; + } + } + + if (chestBlock == null) { + player.sendMessage(getMessage("no-chest-near-sign")); + event.setCancelled(true); + return; + } + + String line1Clean = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + + // --- LOGIK FÜR ZIELTRUHEN (ZIEL) --- + if (line1Clean.equalsIgnoreCase("ziel")) { + String line3Raw = lines[3] != null ? lines[3] : ""; + String line3Clean = ChatColor.stripColor(line3Raw); + String pureOwnerName = line3Clean.replace("[Public]", "").replace("[public]", "").trim(); + + 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)) { + if (isOwner) { + boolean isPublic = isChestPublic(sign); + String newModeText; + String newLine4; + + if (isPublic) { + // Wechsel zu Privat + newModeText = getMessage("mode-private"); + newLine4 = getSignColor("target", "line4") + pureOwnerName; + } else { + // Wechsel zu Öffentlich + newModeText = getMessage("mode-public"); + newLine4 = getSignColor("target", "line4") + pureOwnerName + " " + ChatColor.RESET + "[Public]"; + } + + sign.setLine(3, newLine4); + sign.update(); + player.sendMessage(getMessage("mode-changed").replace("%mode%", newModeText)); + event.setCancelled(true); + return; + } else { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + } + + // 2. FALL: ITEM ZUWEISEN / AKTUALISIEREN (Klick + Item in Hand) + // ERWEITERUNG: Wir prüfen nicht mehr, ob Zeile 2 leer ist. Wenn ein Item in der Hand ist, wird es aktualisiert. + if (itemInHand != null && itemInHand.getType() != Material.AIR) { + // Prüfe, ob der Spieler das Recht hat (Owner oder unbeansprucht) + if (!pureOwnerName.isEmpty() && !pureOwnerName.equalsIgnoreCase("Unknown") && !isOwner) { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + + Chest chest = (Chest) chestBlock.getState(); + boolean isFull = isInventoryFull(chest.getInventory()); + String colorType = isFull ? "full" : "target"; + + 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 + + // Wenn noch kein Name da war, setze ihn + String finalLine4 = line3Raw; + if (pureOwnerName.isEmpty() || pureOwnerName.equalsIgnoreCase("Unknown")) { + finalLine4 = getSignColor("target", "line4") + player.getName(); + } + + sign.setLine(3, finalLine4); + sign.update(); + + setTargetChestLocation(player.getUniqueId(), chestBlock.getLocation(), itemInHand.getType()); + player.sendMessage(getMessage("target-chest-set").replace("%item%", itemInHand.getType().name())); + event.setCancelled(true); + return; + } + + // Zugriffsschutz für konfigurierte Truhen (wenn kein Item in Hand zum Updaten) + // Wenn nicht Owner und NICHT öffentlich -> Zugriff verweigern + if (!isOwner && !isChestPublic(sign)) { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + + // Wenn Owner oder Öffentlich -> Truhe öffnen lassen (Event nicht canceln) + return; + } + + // --- LOGIK FÜR EINGANGSTRUHEN (INPUT) --- + if (line1Clean.equalsIgnoreCase("input")) { + String line3Raw = lines[3] != null ? lines[3] : ""; + String line3Clean = ChatColor.stripColor(line3Raw); + String pureOwnerName = line3Clean.replace("[Public]", "").replace("[public]", "").trim(); + boolean isOwner = pureOwnerName.equalsIgnoreCase(player.getName()); + + // 1. FALL: MODUS WECHSELN (Shift + Klick + Leere Hand) + if (player.isSneaking() && (itemInHand == null || itemInHand.getType() == Material.AIR)) { + if (isOwner) { + boolean isPublic = isChestPublic(sign); + String newModeText; + String newLine4; + + if (isPublic) { + newModeText = getMessage("mode-private"); + newLine4 = getSignColor("input", "line4") + pureOwnerName; + } else { + newModeText = getMessage("mode-public"); + newLine4 = getSignColor("input", "line4") + pureOwnerName + " " + ChatColor.RESET + "[Public]"; + } + + sign.setLine(3, newLine4); + sign.update(); + player.sendMessage(getMessage("mode-changed").replace("%mode%", newModeText)); + event.setCancelled(true); + return; + } else { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + } + + // Zugriffsschutz + if (!isOwner && !isChestPublic(sign)) { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + } + } + return; + } + + // Truhe-Interaktion (wenn man direkt auf die Truhe klickt und nicht auf das Schild) + if (clickedBlock.getState() instanceof Chest) { + Block chestBlock = clickedBlock; + Block signBlock = null; + + // Suche Schild + for (Block face : new Block[] { + chestBlock.getRelative(1, 0, 0), + chestBlock.getRelative(-1, 0, 0), + chestBlock.getRelative(0, 0, 1), + chestBlock.getRelative(0, 0, -1) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chestBlock)) { + String[] lines = sign.getLines(); + if (lines.length >= 2 && ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")).equalsIgnoreCase("[asc]")) { + signBlock = face; + break; + } + } + } + + if (signBlock != null) { + Sign sign = (Sign) signBlock.getState(); + String[] lines = sign.getLines(); + String line1Clean = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + + // NUR FÜR ZIELTRUHEN + if (line1Clean.equalsIgnoreCase("ziel")) { + String line3Raw = lines[3] != null ? lines[3] : ""; + String line3Clean = ChatColor.stripColor(line3Raw); + String pureOwnerName = line3Clean.replace("[public]", "").replace("[Public]", "").trim(); + boolean isOwner = pureOwnerName.equalsIgnoreCase(player.getName()); + + // 1. FALL: MODUS WECHSELN + if (player.isSneaking() && (itemInHand == null || itemInHand.getType() == Material.AIR)) { + if (isOwner) { + boolean isPublic = isChestPublic(sign); + String newModeText; + String newLine4; + + String baseName = getSignColor("target", "line4") + pureOwnerName; + + if (isPublic) { + newModeText = getMessage("mode-private"); + newLine4 = baseName; + } else { + newModeText = getMessage("mode-public"); + newLine4 = baseName + " " + ChatColor.RESET + "[Public]"; + } + + sign.setLine(3, newLine4); + sign.update(); + player.sendMessage(getMessage("mode-changed").replace("%mode%", newModeText)); + event.setCancelled(true); + return; + } else { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + } + + // 2. FALL: ITEM ZUWEISEN (Nur wenn Zeile 2 leer ist - Logik beibehalten für Klick auf Truhe) + // Hinweis: Um das Item zu aktualisieren, sollte das Schild angeklickt werden, nicht die Truhe direkt. + // Dies verhindert, dass man versehentlich die Sortierung ändert, wenn man Items in die Truhe legen will. + String line2Clean = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); + if (line2Clean.isEmpty()) { + if (itemInHand == null || itemInHand.getType() == Material.AIR) { + player.sendMessage(getMessage("no-item-in-hand")); + event.setCancelled(true); + return; + } + + if (!pureOwnerName.isEmpty() && !pureOwnerName.equalsIgnoreCase("Unknown") && !isOwner) { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + + Chest chest = (Chest) chestBlock.getState(); + boolean isFull = isInventoryFull(chest.getInventory()); + String colorType = isFull ? "full" : "target"; + + sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); + sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); + sign.setLine(2, getSignColor(colorType, "line3") + itemInHand.getType().name()); + + String finalLine4 = line3Raw; + if (pureOwnerName.isEmpty() || pureOwnerName.equalsIgnoreCase("Unknown")) { + finalLine4 = getSignColor("target", "line4") + player.getName(); + } + + sign.setLine(3, finalLine4); + sign.update(); + + setTargetChestLocation(player.getUniqueId(), chestBlock.getLocation(), itemInHand.getType()); + player.sendMessage(getMessage("target-chest-set").replace("%item%", itemInHand.getType().name())); + event.setCancelled(true); + return; + } + + // Wenn nicht Owner und nicht öffentlich -> Zugriff verweigern + if (!isOwner && !isChestPublic(sign)) { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + } + + // 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(); + boolean isOwner = pureOwnerName.equalsIgnoreCase(player.getName()); + + // Moduswechsel + if (player.isSneaking() && (itemInHand == null || itemInHand.getType() == Material.AIR)) { + if (isOwner) { + boolean isPublic = isChestPublic(sign); + String newModeText; + String newLine4; + + String baseName = getSignColor("input", "line4") + pureOwnerName; + + if (isPublic) { + newModeText = getMessage("mode-private"); + newLine4 = baseName; + } else { + newModeText = getMessage("mode-public"); + newLine4 = baseName + " " + ChatColor.RESET + "[Public]"; + } + sign.setLine(3, newLine4); + sign.update(); + player.sendMessage(getMessage("mode-changed").replace("%mode%", newModeText)); + event.setCancelled(true); + return; + } else { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + } + + if (!isOwner && !isChestPublic(sign)) { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + return; + } + } + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockBreak(BlockBreakEvent event) { + Block block = event.getBlock(); + Player player = event.getPlayer(); + Block signBlock = null; + String signOwner = ""; + boolean isAscChest = false; + + if (isDebug()) { + getLogger().fine("BlockBreakEvent ausgelöst bei " + block.getLocation() + " durch Spieler " + player.getName() + ", GameMode: " + player.getGameMode() + ", Sneaking: " + player.isSneaking() + ", OP: " + player.isOp() + ", Berechtigung autosortchest.bypass: " + player.hasPermission("autosortchest.bypass")); + } + + // Fall 1: Schild wird abgebaut + if (block.getState() instanceof Sign sign) { + String[] lines = sign.getLines(); + 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"))) { + signBlock = block; + signOwner = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); + if (isDebug()) { + getLogger().fine("Schild erkannt bei " + block.getLocation() + ", Besitzer: " + signOwner); + } + } + } + + // Fall 2: Truhe wird abgebaut + if (block.getState() instanceof Chest chestBlock) { + for (Block face : new Block[] { + block.getRelative(1, 0, 0), + block.getRelative(-1, 0, 0), + block.getRelative(0, 0, 1), + block.getRelative(0, 0, -1) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, block)) { + String[] lines = sign.getLines(); + 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"))) { + signBlock = face; + signOwner = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); + isAscChest = true; + if (isDebug()) { + getLogger().fine("Truhe mit [asc]-Schild erkannt bei " + block.getLocation() + ", Schild bei " + face.getLocation() + ", Besitzer: " + signOwner); + } + break; + } + } + } + } + + // Kein [asc]-Schild oder keine Truhe mit [asc]-Schild + if (signBlock == null) { + if (isDebug()) { + getLogger().fine("Kein [asc]-Schild oder Truhe mit [asc]-Schild bei " + block.getLocation()); + } + return; + } + + // Prüfe Besitzer + boolean isOwner = signOwner.isEmpty() || signOwner.equalsIgnoreCase(player.getName()); + + if (!isOwner) { + player.sendMessage(getMessage("not-your-chest")); + event.setCancelled(true); + getLogger().warning("Spieler " + player.getName() + " versuchte, " + (isAscChest ? "Truhe" : "Schild") + " bei " + block.getLocation() + " abzubauen, das nicht ihm gehört (Besitzer: " + signOwner + ")"); + return; + } + + // Prüfe Shift-Taste (Berechtigungen ignorieren) + if (!player.isSneaking()) { + player.sendMessage(getMessage("sign-break-denied")); + event.setCancelled(true); + if (isDebug()) { + getLogger().fine("Spieler " + player.getName() + " versuchte, " + (isAscChest ? "Truhe" : "Schild") + " bei " + block.getLocation() + " ohne Shift abzubauen"); + } + return; + } + + if (isDebug()) { + getLogger().fine("Spieler " + player.getName() + " hat " + (isAscChest ? "Truhe" : "Schild") + " bei " + block.getLocation() + " erfolgreich abgebaut"); + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (!(event.getInventory().getHolder() instanceof Chest chest)) return; + + Player player = (Player) event.getPlayer(); + Block chestBlock = chest.getBlock(); + Block signBlock = null; + + // Suche nach einem an die Truhe angehängten Schild + for (Block face : new Block[] { + chestBlock.getRelative(1, 0, 0), + chestBlock.getRelative(-1, 0, 0), + chestBlock.getRelative(0, 0, 1), + chestBlock.getRelative(0, 0, -1) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chestBlock)) { + String[] lines = sign.getLines(); + if (lines.length >= 2 && ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")).equalsIgnoreCase("[asc]") && ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")).equalsIgnoreCase("ziel")) { + signBlock = face; + break; + } + } + } + + if (signBlock == null) { + if (isDebug()) getLogger().fine("Keine Zieltruhe-Schild an Truhe bei " + chestBlock.getLocation()); + return; + } + + Sign sign = (Sign) signBlock.getState(); + String[] lines = sign.getLines(); + String signOwner = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); + if (!signOwner.equalsIgnoreCase(player.getName())) { + if (isDebug()) getLogger().fine("Schild bei " + signBlock.getLocation() + " gehört nicht Spieler " + player.getName() + " (Besitzer: " + signOwner + ")"); + return; + } + + boolean isFull = isInventoryFull(chest.getInventory()); + String colorType = isFull ? "full" : "target"; + + String currentLine0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); + String currentLine1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + String currentLine2 = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); + String currentLine3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); + + if (currentLine0.equalsIgnoreCase("[asc]") && currentLine1.equalsIgnoreCase("ziel")) { + sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); + sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); + sign.setLine(2, getSignColor(colorType, "line3") + currentLine2); + sign.setLine(3, getSignColor(colorType, "line4") + currentLine3); + sign.update(); + if (isDebug()) { + getLogger().fine("Zieltruhe-Schild bei " + signBlock.getLocation() + " aktualisiert nach Inventar-Schließung (voll: " + isFull + ")"); + } + } + } + + private void distributeItems(Player player, Inventory sourceInventory) { + UUID playerUUID = player.getUniqueId(); + Block chestBlock = ((Chest) sourceInventory.getHolder()).getBlock(); + + // Prüfe, ob die Truhe leer ist + boolean hasItems = false; + for (ItemStack item : sourceInventory.getContents()) { + if (item != null && item.getType() != Material.AIR) { + hasItems = true; + break; + } + } + if (!hasItems) { + if (isDebug()) getLogger().fine("Eingangstruhe bei " + chestBlock.getLocation() + " ist leer"); + return; + } + + for (int slot = 0; slot < sourceInventory.getSize(); slot++) { + ItemStack item = sourceInventory.getItem(slot); + if (item == null || item.getType() == Material.AIR) continue; + + Location targetChestLocation = getTargetChestLocation(playerUUID, item.getType()); + if (targetChestLocation == null) { + if (isDebug()) getLogger().fine("Keine Zieltruhe für Item " + item.getType().name() + " für Spieler " + player.getName()); + continue; + } + + if (!(targetChestLocation.getBlock().getState() instanceof Chest)) { + if (canSendFullChestMessage(playerUUID, item.getType())) { + player.sendMessage(getMessage("target-chest-missing").replace("%item%", item.getType().name())); + } + playerData.set("players." + playerUUID + ".target-chests." + item.getType().name(), null); + savePlayerData(); + getLogger().warning("Zieltruhe für " + item.getType().name() + " fehlt bei " + targetChestLocation); + continue; + } + + Chest targetChest = (Chest) targetChestLocation.getBlock().getState(); + Inventory targetInventory = targetChest.getInventory(); + + boolean isValidTarget = false; + String signOwner = "Unbekannt"; + Block signBlock = null; + for (Block face : new Block[] { + targetChest.getBlock().getRelative(1, 0, 0), + targetChest.getBlock().getRelative(-1, 0, 0), + targetChest.getBlock().getRelative(0, 0, 1), + targetChest.getBlock().getRelative(0, 0, -1) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, targetChest.getBlock())) { + String[] lines = sign.getLines(); + String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); + String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + String line3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); + if (isDebug()) { + getLogger().fine("Prüfe Zieltruhe-Schild bei " + face.getLocation() + ": Zeile 1='" + line0 + "', Zeile 2='" + line1 + "', Zeile 4='" + line3 + "' für Spieler " + player.getName()); + } + if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { + signOwner = line3.isEmpty() ? "Unbekannt" : line3; + if (line3.equalsIgnoreCase(player.getName())) { + isValidTarget = true; + signBlock = face; + if (isDebug()) { + getLogger().fine("Gültiges Zieltruhe-Schild gefunden bei " + face.getLocation() + " für Spieler " + player.getName()); + } + break; + } + } else if (isDebug()) { + getLogger().fine("Zieltruhe-Schild bei " + face.getLocation() + " hat ungültige Zeilen: [asc]=" + line0 + ", ziel=" + line1); + } + } + } + + if (!isValidTarget) { + if (canSendFullChestMessage(playerUUID, item.getType())) { + player.sendMessage(getMessage("not-your-chest")); + } + getLogger().warning("Zieltruhe bei " + targetChestLocation + " gehört nicht Spieler " + player.getName() + " (Besitzer: " + signOwner + ")"); + continue; + } + + ItemStack itemToTransfer = item.clone(); + Map leftover = targetInventory.addItem(itemToTransfer); + boolean isFull = isInventoryFull(targetInventory); + + if (signBlock != null) { + Sign sign = (Sign) signBlock.getState(); + String[] lines = sign.getLines(); + String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); + String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + String line2 = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); + String line3 = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); + if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { + String colorType = isFull ? "full" : "target"; + sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); + sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); + sign.setLine(2, getSignColor(colorType, "line3") + line2); + sign.setLine(3, getSignColor(colorType, "line4") + line3); + sign.update(); + if (isDebug()) { + getLogger().fine("Zieltruhe-Schild bei " + signBlock.getLocation() + " aktualisiert (voll: " + isFull + ")"); + } + } + } + + if (leftover.isEmpty()) { + sourceInventory.setItem(slot, null); + } else { + if (canSendFullChestMessage(playerUUID, item.getType())) { + String message = getMessage("target-chest-full") + .replace("%item%", item.getType().name()) + .replace("%x%", String.valueOf(targetChestLocation.getBlockX())) + .replace("%y%", String.valueOf(targetChestLocation.getBlockY())) + .replace("%z%", String.valueOf(targetChestLocation.getBlockZ())); + player.sendMessage(message); + } + for (ItemStack leftoverItem : leftover.values()) { + if (leftoverItem != null && leftoverItem.getType() == item.getType()) { + item.setAmount(leftoverItem.getAmount()); + sourceInventory.setItem(slot, item); + break; + } + } + } + } + } + + private void checkInputChests() { + if (playerData == null) { + getLogger().warning("playerData ist null. Kann Eingangstruhe nicht prüfen."); + return; + } + if (playerData.getConfigurationSection("players") == null) { + getLogger().warning("Abschnitt 'players' in players.yml fehlt. Keine Eingangstruhe zu prüfen."); + return; + } + + for (String uuidString : playerData.getConfigurationSection("players").getKeys(false)) { + UUID ownerUUID; + try { + ownerUUID = UUID.fromString(uuidString); + } catch (IllegalArgumentException e) { + getLogger().warning("Ungültige UUID in players.yml: " + uuidString); + continue; + } + + String path = "players." + uuidString + ".input-chest"; + if (!playerData.contains(path)) continue; + + String worldName = playerData.getString(path + ".world"); + World world = getServer().getWorld(worldName); + if (world == null) { + getLogger().warning("Welt " + worldName + " für Eingangstruhe von UUID " + uuidString + " nicht gefunden"); + continue; + } + + int x = playerData.getInt(path + ".x"); + int y = playerData.getInt(path + ".y"); + int z = playerData.getInt(path + ".z"); + Location location = new Location(world, x, y, z); + + if (!(location.getBlock().getState() instanceof Chest)) { + if (isDebug()) { + getLogger().fine("Eingangstruhe bei " + location + " existiert nicht"); + } + continue; + } + + Chest chest = (Chest) location.getBlock().getState(); + Block inputSignBlock = null; + boolean isPublic = false; + String ownerName = "Unknown"; + + // Schild suchen und Status prüfen + for (Block face : new Block[] { + chest.getBlock().getRelative(1, 0, 0), + chest.getBlock().getRelative(-1, 0, 0), + chest.getBlock().getRelative(0, 0, 1), + chest.getBlock().getRelative(0, 0, -1) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, chest.getBlock())) { + 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")) { + inputSignBlock = face; + String line3Raw = lines[3] != null ? lines[3] : ""; + String line3Clean = ChatColor.stripColor(line3Raw); + + isPublic = line3Clean.toLowerCase().endsWith("[public]"); + ownerName = line3Clean.replace(" [Public]", "").replace(" [public]", "").trim(); + break; + } + } + } + + if (inputSignBlock == null) { + if (isDebug()) getLogger().fine("Kein Eingangsschild an Truhe bei " + location); + continue; + } + + // Wenn das Inventar leer ist, brauchen wir nichts tun + boolean hasItems = false; + for (ItemStack item : chest.getInventory().getContents()) { + if (item != null && item.getType() != Material.AIR) { + hasItems = true; + break; + } + } + if (!hasItems) continue; + + // Jetzt kommt der entscheidende Teil für die Multiplayer-Funktionalität: + // Wenn PRIVATE: Nur sortieren, wenn der Owner ONLINE ist. + // Wenn ÖFFENTLICH: Immer sortieren, aber wir brauchen einen "fiktiven" Player für die Fehlermeldungen + // oder wir senden Meldungen an alle Online-Spieler, die in der Nähe sind? + // Der Einfachheit halber: Wenn Öffentlich, sortieren wir stumm oder senden Meldungen an den Owner wenn er online ist. + + Player ownerPlayer = getServer().getPlayer(ownerUUID); + + if (!isPublic) { + // Privat: Nur wenn Owner online + if (ownerPlayer == null || !ownerPlayer.isOnline()) continue; + } else { + // Öffentlich: Wenn Owner offline, können wir keine "Truhe voll" Nachrichten an den Owner senden. + // Wir sortieren trotzdem. + // Wir setzen ownerPlayer auf null, damit distributeItems weiß, dass niemand Besitzer ist (für Messages). + } + + // Wir rufen distributeItems auf. + // WICHTIG: distributeItems nutzt `player.getUniqueId()` um die Zieltruhen zu finden. + // Das funktioniert auch, wenn ownerPlayer null ist, solange wir die UUID übergeben. + // Wir müssen aber aufpassen, dass `distributeItems` nicht crasht, wenn player null ist. + + distributeItemsForOwner(ownerUUID, ownerPlayer, chest.getInventory()); + } + } + + // 4. KORRIGIERTE distributeItemsForOwner Methode: + private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory) { + boolean hasItems = false; + for (ItemStack item : sourceInventory.getContents()) { + if (item != null && item.getType() != Material.AIR) { + hasItems = true; + break; + } + } + if (!hasItems) return; + + // Owner-Name ermitteln + String ownerName = "Unknown"; + if (ownerPlayer != null) { + ownerName = ownerPlayer.getName(); + } else { + // Offline-Namen aus PlayerData holen wenn möglich + org.bukkit.OfflinePlayer offlinePlayer = getServer().getOfflinePlayer(ownerUUID); + if (offlinePlayer.hasPlayedBefore()) { + ownerName = offlinePlayer.getName(); + } + } + + for (int slot = 0; slot < sourceInventory.getSize(); slot++) { + ItemStack item = sourceInventory.getItem(slot); + if (item == null || item.getType() == Material.AIR) continue; + + Location targetChestLocation = getTargetChestLocation(ownerUUID, item.getType()); + if (targetChestLocation == null) continue; + + if (!(targetChestLocation.getBlock().getState() instanceof Chest)) { + if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { + ownerPlayer.sendMessage(getMessage("target-chest-missing").replace("%item%", item.getType().name())); + } + playerData.set("players." + ownerUUID + ".target-chests." + item.getType().name(), null); + savePlayerData(); + continue; + } + + Chest targetChest = (Chest) targetChestLocation.getBlock().getState(); + Inventory targetInventory = targetChest.getInventory(); + + boolean isValidTarget = false; + Block signBlock = null; + + for (Block face : new Block[] { + targetChest.getBlock().getRelative(1, 0, 0), + targetChest.getBlock().getRelative(-1, 0, 0), + targetChest.getBlock().getRelative(0, 0, 1), + targetChest.getBlock().getRelative(0, 0, -1) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, targetChest.getBlock())) { + String[] lines = sign.getLines(); + String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); + String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + String line3Clean = ChatColor.stripColor((String) (lines[3] != null ? lines[3] : "")); + + String signOwnerName = line3Clean.replace("[Public]", "").replace("[public]", "").trim(); + + if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { + if (signOwnerName.equalsIgnoreCase(ownerName) || signOwnerName.equalsIgnoreCase("Unknown")) { + isValidTarget = true; + signBlock = face; + break; + } + } + } + } + + if (!isValidTarget) continue; + + ItemStack itemToTransfer = item.clone(); + Map leftover = targetInventory.addItem(itemToTransfer); + boolean isFull = isInventoryFull(targetInventory); + + if (signBlock != null) { + Sign sign = (Sign) signBlock.getState(); + String[] lines = sign.getLines(); + String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); + String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + String line2 = ChatColor.stripColor((String) (lines[2] != null ? lines[2] : "")); + String line3Raw = lines[3] != null ? lines[3] : ""; + + if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("ziel")) { + String colorType = isFull ? "full" : "target"; + sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); + sign.setLine(1, getSignColor(colorType, "line2") + "ziel"); + sign.setLine(2, getSignColor(colorType, "line3") + line2); + sign.setLine(3, line3Raw); + sign.update(); + } + } + + if (leftover.isEmpty()) { + sourceInventory.setItem(slot, null); + } else { + if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { + String message = getMessage("target-chest-full") + .replace("%item%", item.getType().name()) + .replace("%x%", String.valueOf(targetChestLocation.getBlockX())) + .replace("%y%", String.valueOf(targetChestLocation.getBlockY())) + .replace("%z%", String.valueOf(targetChestLocation.getBlockZ())); + ownerPlayer.sendMessage(message); + } + for (ItemStack leftoverItem : leftover.values()) { + if (leftoverItem != null && leftoverItem.getType() == item.getType()) { + item.setAmount(leftoverItem.getAmount()); + sourceInventory.setItem(slot, item); + break; + } + } + } + } + } } \ No newline at end of file