diff --git a/src/main/java/com/viper/autosortchest/Main.java b/src/main/java/com/viper/autosortchest/Main.java index 7ee265b..f57484b 100644 --- a/src/main/java/com/viper/autosortchest/Main.java +++ b/src/main/java/com/viper/autosortchest/Main.java @@ -13,6 +13,7 @@ import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.Chest; +import org.bukkit.block.Barrel; import org.bukkit.block.DoubleChest; import org.bukkit.block.Sign; import org.bukkit.block.data.type.WallSign; @@ -334,36 +335,38 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b private volatile boolean playerDataDirty = false; private volatile boolean saveInProgress = false; - private static final String CONFIG_VERSION = "2.5"; + private static final String CONFIG_VERSION = "2.8"; private boolean updateAvailable = false; private String latestVersion = ""; private static final String HELP_DE = "&6&l=== AutoSortChest Hilfe ===\n" + - "&eEingangstruhe erstellen:\n" + - "&f1. Platziere ein Schild an einer Truhe.\n" + + "&eEingangstruhe/-fass erstellen:\n" + + "&f1. Platziere ein Schild an einer Truhe oder einem Fass.\n" + "&f2. Schreibe:\n" + " &7[asc]\n" + " &7input\n" + - "&f3. Fülle die Truhe mit Items.\n" + - "&eZieltruhe erstellen:\n" + - "&f1. Platziere ein Schild an einer Truhe.\n" + + "&f3. Fülle die Truhe/das Fass mit Items.\n" + + "&eZieltruhe/-fass erstellen:\n" + + "&f1. Platziere ein Schild an einer Truhe oder einem Fass.\n" + "&f2. Schreibe:\n" + " &7[asc]\n" + " &7ziel\n" + "&f3. Rechtsklicke mit einem Item in der Hand.\n" + - "&eRest-Truhe (Fallback) erstellen:\n" + - "&f1. Platziere ein Schild an einer Truhe.\n" + + "&eRest-Truhe/-fass (Fallback) erstellen:\n" + + "&f1. Platziere ein Schild an einer Truhe oder einem Fass.\n" + "&f2. Schreibe:\n" + " &7[asc]\n" + " &7rest\n" + - "&eMülltruhe erstellen:\n" + - "&f1. Platziere ein Schild an einer Truhe.\n" + + "&eMülltruhe/-fass erstellen:\n" + + "&f1. Platziere ein Schild an einer Truhe oder einem Fass.\n" + "&f2. Schreibe:\n" + " &7[asc]\n" + " &7trash\n" + "&f3. Rechtsklicke das Schild zum Konfigurieren.\n" + + "&7Hinweis: &fFässer und Truhen können gemischt werden!\n" + + "&7Limits (input/rest/target) gelten für Truhen + Fässer zusammen.\n" + "&eBungeeCord:\n" + "&fSetze 'server_name' in config.yml für serverübergreifendes Sortieren.\n" + "&eBefehle:\n" + @@ -374,35 +377,37 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b "&f- &b/asc export &f- Exportiert Daten aus MySQL in players.yml (OP).\n" + "&f- &b/asc list &f- Zeigt Truhen-Übersicht eines Spielers (Admin).\n" + "&f- &b/asc autosign [item|hand]\n" + - " &7Setzt automatisch ein ASC-Schild an die angeschaute Truhe.\n" + + " &7Setzt automatisch ein ASC-Schild an die angeschaute Truhe oder das Fass.\n" + " &7Beispiele: &b/asc autosign ziel IRON_ORE &7| &b/asc autosign ziel hand\n" + "&6&l========================"; private static final String HELP_EN = "&6&l=== AutoSortChest Help ===\n" + - "&eCreate Input Chest:\n" + - "&f1. Place a sign on a chest.\n" + + "&eCreate Input Chest/Barrel:\n" + + "&f1. Place a sign on a chest or barrel.\n" + "&f2. Write:\n" + " &7[asc]\n" + " &7input\n" + - "&f3. Fill the chest with items.\n" + - "&eCreate Target Chest:\n" + - "&f1. Place a sign on a chest.\n" + + "&f3. Fill the chest/barrel with items.\n" + + "&eCreate Target Chest/Barrel:\n" + + "&f1. Place a sign on a chest or barrel.\n" + "&f2. Write:\n" + " &7[asc]\n" + " &7target\n" + "&f3. Right-click with an item in hand.\n" + - "&eCreate Rest Chest (Fallback):\n" + - "&f1. Place a sign on a chest.\n" + + "&eCreate Rest Chest/Barrel (Fallback):\n" + + "&f1. Place a sign on a chest or barrel.\n" + "&f2. Write:\n" + " &7[asc]\n" + " &7rest\n" + - "&eCreate Trash Chest:\n" + - "&f1. Place a sign on a chest.\n" + + "&eCreate Trash Chest/Barrel:\n" + + "&f1. Place a sign on a chest or barrel.\n" + "&f2. Write:\n" + " &7[asc]\n" + " &7trash\n" + "&f3. Right-click the sign to configure.\n" + + "&7Note: &fBarrels and chests can be mixed freely!\n" + + "&7Limits (input/rest/target) count chests + barrels together.\n" + "&eBungeeCord:\n" + "&fSet 'server_name' in config.yml for cross-server sorting.\n" + "&eCommands:\n" + @@ -413,7 +418,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b "&f- &b/asc export &f- Exports data from MySQL into players.yml (OP only).\n" + "&f- &b/asc list &f- Shows chest overview of a player (Admin).\n" + "&f- &b/asc autosign [item|hand]\n" + - " &7Automatically places an ASC sign on the chest you are looking at.\n" + + " &7Automatically places an ASC sign on the chest or barrel you are looking at.\n" + " &7Examples: &b/asc autosign target IRON_ORE &7| &b/asc autosign target hand\n" + "&6&l========================"; @@ -963,6 +968,91 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b return blocks; } + // ── Barrel-/Container-Hilfsmethoden ────────────────────────────────────── + + /** Gibt true zurück wenn der Block eine Truhe (single/double) oder ein Fass ist. */ + private boolean isContainer(Block block) { + BlockState state = block.getState(); + return state instanceof Chest || state instanceof Barrel; + } + + /** Gibt das Inventory eines Containers zurück (Truhe oder Fass). */ + private Inventory getContainerInventory(Block block) { + BlockState state = block.getState(); + if (state instanceof Chest chest) return chest.getInventory(); + if (state instanceof Barrel barrel) return barrel.getInventory(); + return null; + } + + /** + * Gibt alle Blöcke zurück, aus denen ein Container besteht. + * Truhe: 1–2 Blöcke (Doppeltruhe); Fass: immer 1 Block. + */ + private List getContainerBlocks(Block block) { + BlockState state = block.getState(); + if (state instanceof Chest chest) return getChestBlocks(chest); + if (state instanceof Barrel) return java.util.Collections.singletonList(block); + return java.util.Collections.emptyList(); + } + + /** + * Kombiniertes Limit-Zählen für Truhen UND Fässer. + * Gibt die Gesamtanzahl registrierter Container (Truhen + Fässer) zurück. + * Die Limits gelten gemeinsam – 50 target = 50 Truhen ODER 50 Fässer ODER gemischt. + */ + private int countAllContainersYaml(UUID playerUUID, String type) { + int count = 0; + String basePath; + switch (type) { + case "input": basePath = "players." + playerUUID + ".input-chests"; break; + case "rest": basePath = "players." + playerUUID + ".rest-chests"; break; + case "target": { + String tp = "players." + playerUUID + ".target-chests"; + if (!playerData.contains(tp) || !playerData.isConfigurationSection(tp)) return 0; + for (String item : playerData.getConfigurationSection(tp).getKeys(false)) { + for (Location loc : getTargetChestSlotsYaml(playerUUID, item)) { + if (loc != null) count++; + } + } + return count; + } + default: return 0; + } + if (playerData.contains(basePath) && playerData.isConfigurationSection(basePath)) { + count = playerData.getConfigurationSection(basePath).getKeys(false).size(); + } + return count; + } + + /** + * Öffnet einen Container (Truhe oder Fass) mit benutzerdefiniertem Titel. + * Bei Fässern: direkt öffnen (keine NMS-Reflection für Deckel nötig). + */ + private void openTitledContainerInventory(Player player, Block containerBlock, String title) { + BlockState state = containerBlock.getState(); + if (state instanceof Chest) { + openTitledChestInventory(player, containerBlock, title); + } else if (state instanceof Barrel barrel) { + player.openInventory(barrel.getInventory()); + if (title != null && !title.isEmpty()) { + final String finalTitle = title; + org.bukkit.Bukkit.getScheduler().runTaskLater(this, () -> { + if (player.isOnline()) { + trySetInventoryTitle(player, finalTitle); + player.updateInventory(); + } + }, 1L); + } + } + } + + /** Öffnet Container mit Barrel-Erkennung und korrektem Titel. */ + private void openTitledContainerInventory(Player player, Block containerBlock, Sign sign, String type) { + boolean isBarrel = containerBlock.getState() instanceof Barrel; + String title = buildChestTitle(sign, type, isBarrel); + openTitledContainerInventory(player, containerBlock, title); + } + private void savePlayerData() { if (playerData == null || playerDataFile == null) { getLogger().warning("Kann players.yml nicht speichern: playerData oder playerDataFile ist null"); @@ -982,15 +1072,17 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (!playerDataDirty || playerData == null || playerDataFile == null) return; if (saveInProgress) return; saveInProgress = true; - playerDataDirty = false; + // BUGFIX: dirty-Flag erst NACH erfolgreichem Save löschen, um Race-Condition zu vermeiden. + // (Altes Verhalten: false vor dem async-Save → gleichzeitiges dirty=true ging verloren) final FileConfiguration snapshot = playerData; final File target = playerDataFile; Bukkit.getScheduler().runTaskAsynchronously(this, () -> { try { snapshot.save(target); + playerDataDirty = false; // BUGFIX: erst nach erfolgreichem Save auf false setzen } catch (IOException e) { getLogger().warning("Fehler beim asynchronen Speichern von players.yml: " + e.getMessage()); - playerDataDirty = true; // Erneuter Versuch beim nächsten Flush + // playerDataDirty bleibt true → nächster Flush versucht es erneut } finally { saveInProgress = false; } @@ -1202,8 +1294,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b for (Location loc : inputLocs) { Block chestBlock = loc.getBlock(); - if (!(chestBlock.getState() instanceof Chest)) continue; - for (Block b : getChestBlocks((Chest) chestBlock.getState())) { + if (!isContainer(chestBlock)) continue; + for (Block b : getContainerBlocks(chestBlock)) { for (Block face : adjacentFaces(b)) { if (!(face.getState() instanceof Sign sign)) continue; if (!isSignAttachedToChest(face, b)) continue; @@ -1224,10 +1316,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b for (Location loc : getTargetChestSlotsYaml(playerUUID, itemType)) { if (loc == null) continue; Block chestBlock = loc.getBlock(); - if (!(chestBlock.getState() instanceof Chest)) continue; - Chest chest = (Chest) chestBlock.getState(); - boolean isFull = isInventoryFull(chest.getInventory()); - for (Block b : getChestBlocks(chest)) { + if (!isContainer(chestBlock)) continue; + Inventory containerInv = getContainerInventory(chestBlock); + boolean isFull = containerInv != null && isInventoryFull(containerInv); + for (Block b : getContainerBlocks(chestBlock)) { for (Block face : adjacentFaces(b)) { if (!(face.getState() instanceof Sign sign)) continue; if (!isSignAttachedToChest(face, b)) continue; @@ -1246,10 +1338,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b for (Location loc : getRestChestLocations(playerUUID)) { if (loc == null) continue; Block chestBlock = loc.getBlock(); - if (!(chestBlock.getState() instanceof Chest)) continue; - Chest chest = (Chest) chestBlock.getState(); - boolean isFull = isInventoryFull(chest.getInventory()); - for (Block b : getChestBlocks(chest)) { + if (!isContainer(chestBlock)) continue; + Inventory containerInv = getContainerInventory(chestBlock); + boolean isFull = containerInv != null && isInventoryFull(containerInv); + for (Block b : getContainerBlocks(chestBlock)) { for (Block face : adjacentFaces(b)) { if (!(face.getState() instanceof Sign sign)) continue; if (!isSignAttachedToChest(face, b)) continue; @@ -1270,11 +1362,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b Location loc = entry.getValue(); if (loc == null) continue; Block chestBlock = loc.getBlock(); - if (!(chestBlock.getState() instanceof Chest)) continue; + if (!isContainer(chestBlock)) continue; String ownerName = ""; org.bukkit.OfflinePlayer op = Bukkit.getOfflinePlayer(ownerUUID); if (op.getName() != null) ownerName = op.getName(); - for (Block b : getChestBlocks((Chest) chestBlock.getState())) { + for (Block b : getContainerBlocks(chestBlock)) { for (Block face : adjacentFaces(b)) { if (!(face.getState() instanceof Sign sign)) continue; if (!isSignAttachedToChest(face, b)) continue; @@ -1399,6 +1491,12 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b }; } + /** Gibt true zurück wenn ein Container (Truhe oder Fass) voll ist. */ + private boolean isContainerFull(Block block) { + Inventory inv = getContainerInventory(block); + return inv != null && isInventoryFull(inv); + } + private boolean isInventoryFull(Inventory inventory) { for (ItemStack item : inventory.getContents()) { if (item == null || item.getAmount() < item.getMaxStackSize()) return false; @@ -1415,7 +1513,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (sign.getBlockData() instanceof WallSign wallSign) { attachedBlock = sign.getBlock().getRelative(wallSign.getFacing().getOppositeFace()); } - if (attachedBlock == null || !(attachedBlock.getState() instanceof Chest)) return false; + if (attachedBlock == null || !isContainer(attachedBlock)) return false; Location loc = attachedBlock.getLocation(); String world = loc.getWorld().getName(); @@ -1466,25 +1564,31 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b * @return Titel-String mit §-Farbcodes (bereits übersetzt). */ private String buildChestTitle(Sign sign, String type) { + return buildChestTitle(sign, type, false); + } + + private String buildChestTitle(Sign sign, String type, boolean isBarrel) { boolean isEn = "en".equalsIgnoreCase( config != null ? config.getString("language", "de") : "de"); String lang = isEn ? "en" : "de"; - // Fallback-Werte falls config-Eintrag fehlt + // Barrel-Titel-Key verwenden wenn verfügbar + String configKey = isBarrel ? type + "-barrel" : type; + String fallback; switch (type) { case "target": fallback = isEn ? "&6%item%" : "&6%item%"; break; - case "input": fallback = isEn ? "&6Input Chest" : "&6Eingangstruhe"; break; - case "rest": fallback = isEn ? "&6Rest Chest" : "&6Rest-Truhe"; break; - case "trash": fallback = isEn ? "&4Trash Chest" : "&4Mülltruhe"; break; - default: fallback = isEn ? "&6Chest" : "&6Truhe"; break; + case "input": fallback = isBarrel ? (isEn ? "&6Input Barrel" : "&6Eingangsfass") : (isEn ? "&6Input Chest" : "&6Eingangstruhe"); break; + case "rest": fallback = isBarrel ? (isEn ? "&6Rest Barrel" : "&6Rest-Fass") : (isEn ? "&6Rest Chest" : "&6Rest-Truhe"); break; + case "trash": fallback = isBarrel ? (isEn ? "&4Trash Barrel" : "&4Müllfass") : (isEn ? "&4Trash Chest" : "&4Mülltruhe"); break; + default: fallback = isEn ? "&6Chest" : "&6Truhe"; break; } String template = config != null - ? config.getString("chest-titles." + type + "." + lang, fallback) + ? config.getString("chest-titles." + configKey + "." + lang, + config.getString("chest-titles." + type + "." + lang, fallback)) : fallback; - // %item%-Platzhalter für Zieltruhen ersetzen if (template.contains("%item%")) { String raw = ""; if (sign != null) { @@ -2091,11 +2195,86 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b private void markChestFull(Location loc) { fullChestLocationCache.put(locKey(loc), System.currentTimeMillis()); + // FIX: Schild sofort auf "voll"-Farbe setzen, ohne auf den naechsten + // onInventoryClose oder Block-Event zu warten. + updateSignFullState(loc, true); + } + + /** + * Aktualisiert das ASC-Schild an einer Container-Location auf voll/nicht-voll. + * Wird von markChestFull() aufgerufen damit die Schildfarbe sofort wechselt. + * + * @param loc Location des Containers (Truhe oder Fass). + * @param isFull true = "full"-Farbe, false = normale Farbe zuruecksetzen. + */ + private void updateSignFullState(Location loc, boolean isFull) { + if (loc == null || loc.getWorld() == null) return; + Block block = loc.getBlock(); + if (!isContainer(block)) return; + for (Block b : getContainerBlocks(block)) { + for (Block face : adjacentFaces(b)) { + if (!(face.getState() instanceof Sign sign)) continue; + if (!isSignAttachedToChest(face, b)) continue; + if (!isAscSign(sign, "target") && !isAscSign(sign, "rest")) continue; + String[] lines = sign.getLines(); + String line0 = ChatColor.stripColor(lines[0] != null ? lines[0] : ""); + String line1 = ChatColor.stripColor(lines[1] != null ? lines[1] : ""); + String line2 = ChatColor.stripColor(lines[2] != null ? lines[2] : ""); + String line3Raw = lines[3] != null ? lines[3] : ""; + boolean isPublic = isChestPublic(sign); + if (isCleanTargetSign(sign)) { + applyCleanTargetSign(sign, line0, getCleanSignOwner(sign), isPublic, isFull); + sign.update(); + } else if (isCleanRestSign(sign)) { + applyCleanRestSign(sign, getCleanSignOwnerAny(sign), isPublic, isFull); + sign.update(); + } else if (line0.equalsIgnoreCase("[asc]") && (isZiel(line1) || line1.equalsIgnoreCase("rest"))) { + String configType = line1.equalsIgnoreCase("rest") ? "rest" : "target"; + String colorType = isFull ? "full" : configType; + sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); + sign.setLine(1, getSignColor(colorType, "line2") + line1); + sign.setLine(2, getSignColor(colorType, "line3") + line2); + sign.setLine(3, line3Raw); + sign.update(); + } + return; // pro Container nur ein Schild relevant + } + } } private void cleanFullChestCache() { long now = System.currentTimeMillis(); - fullChestLocationCache.entrySet().removeIf(e -> now - e.getValue() >= FULL_CHEST_CACHE_DURATION); + // FIX: Wenn der Full-Cache-Eintrag ablaeuft, pruefen ob die Truhe + // wirklich noch voll ist. Wenn nicht mehr voll: Schild sofort zurueck + // auf normale Farbe setzen (kein Warten auf naechsten Block-Event). + List expiredKeys = new ArrayList<>(); + fullChestLocationCache.entrySet().removeIf(e -> { + if (now - e.getValue() >= FULL_CHEST_CACHE_DURATION) { + expiredKeys.add(e.getKey()); + return true; + } + return false; + }); + for (String key : expiredKeys) { + // Key-Format: "world:x:y:z" + String[] parts = key.split(":"); + if (parts.length < 4) continue; + try { + World w = Bukkit.getWorld(parts[0]); + if (w == null) continue; + int x = Integer.parseInt(parts[1]); + int y = Integer.parseInt(parts[2]); + int z = Integer.parseInt(parts[3]); + Location loc = new Location(w, x, y, z); + Block block = loc.getBlock(); + if (!isContainer(block)) continue; + Inventory inv = getContainerInventory(block); + // Nur zuruecksetzen wenn Truhe jetzt nicht mehr voll ist + if (inv != null && !isInventoryFull(inv)) { + updateSignFullState(loc, false); + } + } catch (NumberFormatException ignored) {} + } } private boolean canSendFullChestMessage(UUID playerUUID, Material material) { @@ -2168,12 +2347,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (targetBlock.getBlockData() instanceof WallSign wallSignBD) { attached = targetBlock.getRelative(wallSignBD.getFacing().getOppositeFace()); } - if (attached == null || !(attached.getState() instanceof Chest)) { + if (attached == null || !isContainer(attached)) { player.sendMessage(getMessage("priority-not-attached")); return true; } - Chest chest = (Chest) attached.getState(); - Location chestLoc = chest.getLocation(); + Location chestLoc = attached.getLocation(); UUID playerUUID = player.getUniqueId(); // Item-Typ ermitteln: // Normales Schild → Index 2 (Zeile 3) enthält den Item-Namen (z.B. "IRON_ORE") @@ -2856,7 +3034,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b // ── 1. Ziel-Block ermitteln ────────────────────────────────────────── Block targetBlock = player.getTargetBlockExact(5); - if (targetBlock == null || !(targetBlock.getState() instanceof Chest)) { + if (targetBlock == null || !(targetBlock.getState() instanceof Chest || targetBlock.getState() instanceof Barrel)) { player.sendMessage(getMessage("autosign-no-chest")); return true; } @@ -2903,8 +3081,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b // ── 4. Freie Schildfläche an der Truhe suchen (Blickrichtung bevorzugen) ── - Chest chestState = (Chest) targetBlock.getState(); - List chestBlocks = getChestBlocks(chestState); + List chestBlocks = getContainerBlocks(targetBlock); org.bukkit.block.BlockFace[] faces = { org.bukkit.block.BlockFace.NORTH, org.bukkit.block.BlockFace.SOUTH, @@ -3023,7 +3200,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b String w = (String) map.get("world"); int tx = (int) map.get("x"), ty = (int) map.get("y"), tz = (int) map.get("z"); World bw = Bukkit.getWorld(w); - if (bw != null && bw.getBlockAt(tx, ty, tz).getState() instanceof Chest) { + if (bw != null && isContainer(bw.getBlockAt(tx, ty, tz))) { uniqueLocs.add(w + ":" + tx + ":" + ty + ":" + tz); if (finalMaterial.name().equals(map.get("item"))) countForThisItem++; } @@ -3036,7 +3213,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (slotLoc == null || slotLoc.getWorld() == null) continue; int tx = slotLoc.getBlockX(), ty = slotLoc.getBlockY(), tz = slotLoc.getBlockZ(); World bw = slotLoc.getWorld(); - if (bw.getBlockAt(tx, ty, tz).getState() instanceof Chest) { + if (isContainer(bw.getBlockAt(tx, ty, tz))) { uniqueLocs.add(bw.getName() + ":" + tx + ":" + ty + ":" + tz); if (finalMaterial.name().equals(item)) countForThisItem++; } @@ -3199,9 +3376,24 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b Block chestBlock = null; if (signBlock.getBlockData() instanceof WallSign wallSign) { Block attachedBlock = signBlock.getRelative(wallSign.getFacing().getOppositeFace()); - if (attachedBlock.getState() instanceof Chest) chestBlock = attachedBlock; + if (isContainer(attachedBlock)) chestBlock = attachedBlock; } if (chestBlock == null) { player.sendMessage(getMessage("no-chest-near-sign")); return; } + // Prüfe, ob bereits ein ASC-Schild an der Truhe oder am Fass ist + java.util.List blocksToCheck = getContainerBlocks(chestBlock); + for (Block cb : blocksToCheck) { + for (org.bukkit.block.BlockFace face : org.bukkit.block.BlockFace.values()) { + Block relative = cb.getRelative(face); + if (relative.getState() instanceof org.bukkit.block.Sign otherSign) { + String l0 = org.bukkit.ChatColor.stripColor(otherSign.getLine(0) != null ? otherSign.getLine(0) : ""); + if (l0.equalsIgnoreCase("[asc]") || isAnyCleanSign(otherSign)) { + player.sendMessage("§cAn diesem Behälter ist bereits ein ASC-Schild!"); + event.setCancelled(true); + return; + } + } + } + } // Limit-Check: Kein Rang = keine Truhe if (chestLimitsEnabled && getChestLimitForPlayer(player, "input") == 0 && getChestLimitForPlayer(player, "target") == 0) { @@ -3233,9 +3425,24 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b Block chestBlock = null; if (signBlock.getBlockData() instanceof WallSign wallSign) { Block attachedBlock = signBlock.getRelative(wallSign.getFacing().getOppositeFace()); - if (attachedBlock.getState() instanceof Chest) chestBlock = attachedBlock; + if (isContainer(attachedBlock)) chestBlock = attachedBlock; } if (chestBlock == null) { player.sendMessage(getMessage("no-chest-near-sign")); return; } + // Prüfe, ob bereits ein ASC-Schild an der Truhe oder am Fass ist + java.util.List blocksToCheck = getContainerBlocks(chestBlock); + for (Block cb : blocksToCheck) { + for (org.bukkit.block.BlockFace face : org.bukkit.block.BlockFace.values()) { + Block relative = cb.getRelative(face); + if (relative.getState() instanceof org.bukkit.block.Sign otherSign) { + String l0 = org.bukkit.ChatColor.stripColor(otherSign.getLine(0) != null ? otherSign.getLine(0) : ""); + if (l0.equalsIgnoreCase("[asc]") || isAnyCleanSign(otherSign)) { + player.sendMessage("§cAn diesem Behälter ist bereits ein ASC-Schild!"); + event.setCancelled(true); + return; + } + } + } + } // ── Limit-Pruefung: Rest-Truhe ───────────────────────────────────── if (chestLimitsEnabled) { int maxRest = getChestLimitForPlayer(player, "rest"); @@ -3283,6 +3490,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } else { event.setLine(0, getSignColor("rest", "line1") + "[asc]"); event.setLine(1, getSignColor("rest", "line2") + "rest"); + event.setLine(2, ""); // BUGFIX: Zeile 3 (Index 2) explizit leeren event.setLine(3, getSignColor("rest", "line4") + player.getName()); } setRestChestLocation(playerUUID, chestBlock.getLocation()); @@ -3295,9 +3503,24 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b Block chestBlock = null; if (signBlock.getBlockData() instanceof WallSign wallSign) { Block attachedBlock = signBlock.getRelative(wallSign.getFacing().getOppositeFace()); - if (attachedBlock.getState() instanceof Chest) chestBlock = attachedBlock; + if (isContainer(attachedBlock)) chestBlock = attachedBlock; } if (chestBlock == null) { player.sendMessage(getMessage("no-chest-near-sign")); return; } + // Prüfe, ob bereits ein ASC-Schild an der Truhe oder am Fass ist + java.util.List blocksToCheck = getContainerBlocks(chestBlock); + for (Block cb : blocksToCheck) { + for (org.bukkit.block.BlockFace face : org.bukkit.block.BlockFace.values()) { + Block relative = cb.getRelative(face); + if (relative.getState() instanceof org.bukkit.block.Sign otherSign) { + String l0 = org.bukkit.ChatColor.stripColor(otherSign.getLine(0) != null ? otherSign.getLine(0) : ""); + if (l0.equalsIgnoreCase("[asc]") || isAnyCleanSign(otherSign)) { + player.sendMessage("§cAn diesem Behälter ist bereits ein ASC-Schild!"); + event.setCancelled(true); + return; + } + } + } + } if (chestLimitsEnabled) { int maxInput = getChestLimitForPlayer(player, "input"); int currentInput = 0; @@ -3371,9 +3594,24 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b Block chestBlock = null; if (signBlock.getBlockData() instanceof WallSign wallSign) { Block attachedBlock = signBlock.getRelative(wallSign.getFacing().getOppositeFace()); - if (attachedBlock.getState() instanceof Chest) chestBlock = attachedBlock; + if (isContainer(attachedBlock)) chestBlock = attachedBlock; } if (chestBlock == null) { player.sendMessage(getMessage("no-chest-near-sign")); return; } + // Prüfe, ob bereits ein ASC-Schild an der Truhe oder am Fass ist + java.util.List blocksToCheck = getContainerBlocks(chestBlock); + for (Block cb : blocksToCheck) { + for (org.bukkit.block.BlockFace face : org.bukkit.block.BlockFace.values()) { + Block relative = cb.getRelative(face); + if (relative.getState() instanceof org.bukkit.block.Sign otherSign) { + String l0 = org.bukkit.ChatColor.stripColor(otherSign.getLine(0) != null ? otherSign.getLine(0) : ""); + if (l0.equalsIgnoreCase("[asc]") || isAnyCleanSign(otherSign)) { + player.sendMessage("§cAn diesem Behälter ist bereits ein ASC-Schild!"); + event.setCancelled(true); + return; + } + } + } + } event.setLine(0, getSignColor("target", "line1") + "[asc]"); event.setLine(1, getSignColor("target", "line2") + lines[1]); // "ziel" oder "target" beibehalten event.setLine(2, ""); @@ -3417,15 +3655,16 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b return; } signInteractCooldown.put(cooldownKey, now); - // Cooldown-Map periodisch säubern (einmal pro Spieler, wenn > 200 Einträge) - if (signInteractCooldown.size() > 200) { - signInteractCooldown.entrySet().removeIf(e -> (now - e.getValue()) > SIGN_INTERACT_COOLDOWN_MS * 10); + // BUGFIX: Cleanup-Schwellwert von 200 auf 100 gesenkt und TTL von 10x auf 5x Cooldown + // damit die Map bei vielen gleichzeitigen Spielern nicht unbegrenzt wächst. + if (signInteractCooldown.size() > 100) { + signInteractCooldown.entrySet().removeIf(e -> (now - e.getValue()) > SIGN_INTERACT_COOLDOWN_MS * 5); } 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 (isContainer(attachedBlock)) chestBlock = attachedBlock; } if (chestBlock == null) { player.sendMessage(getMessage("no-chest-near-sign")); @@ -3490,10 +3729,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b // In clean mode: Zeile 3 = Öffentlich/Privat, Marker + Public-Flag auf Zeile 4 String itemName = ChatColor.stripColor(sign.getLine(0)); applyCleanTargetSign(sign, itemName, pureOwnerName, newPublic, - chestBlock != null && chestBlock.getState() instanceof Chest c2 && isInventoryFull(c2.getInventory())); + chestBlock != null && isContainerFull(chestBlock)); } else if (isCleanRestSign(sign)) { applyCleanRestSign(sign, pureOwnerName, newPublic, - chestBlock != null && chestBlock.getState() instanceof Chest c3 && isInventoryFull(c3.getInventory())); + chestBlock != null && isContainerFull(chestBlock)); } else { String newLine4 = newPublic ? getSignColor(colorType, "line4") + pureOwnerName + " " + ChatColor.RESET + "[Public]" @@ -3533,7 +3772,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b String worldName = (String) map.get("world"); int x = (int) map.get("x"); int y = (int) map.get("y"); int z = (int) map.get("z"); World w = Bukkit.getWorld(worldName); - if (w != null && w.getBlockAt(x, y, z).getState() instanceof Chest) { + if (w != null && isContainer(w.getBlockAt(x, y, z))) { uniqueChestLocations.add(worldName + ":" + x + ":" + y + ":" + z); if (itemInHand.getType().name().equals(map.get("item"))) countForThisItem++; } @@ -3547,7 +3786,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (slotLoc == null || slotLoc.getWorld() == null) continue; World w = slotLoc.getWorld(); int x = slotLoc.getBlockX(), y = slotLoc.getBlockY(), z = slotLoc.getBlockZ(); - if (w.getBlockAt(x, y, z).getState() instanceof Chest) { + if (isContainer(w.getBlockAt(x, y, z))) { uniqueChestLocations.add(w.getName() + ":" + x + ":" + y + ":" + z); if (itemInHand.getType().name().equals(item)) countForThisItem++; } @@ -3601,8 +3840,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b // FIX: removeOldTargetEntry handles both YAML and MySQL correctly removeOldTargetEntry(player.getUniqueId(), chestBlock.getLocation(), itemInHand.getType().name()); - Chest chest = (Chest) chestBlock.getState(); - boolean isFull = isInventoryFull(chest.getInventory()); + Inventory containerInvS = getContainerInventory(chestBlock); + boolean isFull = containerInvS != null && isInventoryFull(containerInvS); String colorType = isFull ? "full" : "target"; if (isCleanSignMode()) { String ownerForSign = (pureOwnerName.isEmpty() || pureOwnerName.equalsIgnoreCase("Unknown")) @@ -3632,10 +3871,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b return; } event.setCancelled(true); - if (chestBlock.getState() instanceof Chest) { + if (isContainer(chestBlock)) { // Typ bestimmen: Zeile 1 des Schildes ("ziel"/"target"/"rest") String chestType = line1Clean.equalsIgnoreCase("rest") ? "rest" : "target"; - openTitledChestInventory(player, chestBlock, buildChestTitle(sign, chestType)); + openTitledContainerInventory(player, chestBlock, sign, chestType); } return; } @@ -3680,8 +3919,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b return; } event.setCancelled(true); - if (chestBlock.getState() instanceof Chest) { - openTitledChestInventory(player, chestBlock, buildChestTitle(sign, "input")); + if (isContainer(chestBlock)) { + openTitledContainerInventory(player, chestBlock, sign, "input"); } return; } @@ -3689,7 +3928,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b return; } - if (clickedBlock.getState() instanceof Chest) { + if (clickedBlock.getState() instanceof Chest || clickedBlock.getState() instanceof Barrel) { Block chestBlock = clickedBlock; // ── MÜLLTRUHE: Direktklick auf die Truhe ────────────────────────── @@ -3722,15 +3961,14 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b filterInfo = getMessage("trash-info-filter").replace("%items%", itemList); } player.sendMessage(filterInfo); - // Truhe mit Custom-Titel "Mülltruhe" / "Trash Chest" öffnen event.setCancelled(true); - openTitledChestInventory(player, chestBlock, buildChestTitle(null, "trash")); + openTitledContainerInventory(player, chestBlock, null, "trash"); return; } // ────────────────────────────────────────────────────────────────── Block signBlock = null; - List blocks = getChestBlocks((Chest) chestBlock.getState()); + List blocks = getContainerBlocks(chestBlock); outerLoop: for (Block b : blocks) { @@ -3777,12 +4015,12 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b String colorType = line1Clean.equalsIgnoreCase("rest") ? "rest" : "target"; if (isCleanTargetSign(sign)) { String itemName = ChatColor.stripColor(sign.getLine(0)); - Chest c2 = chestBlock.getState() instanceof Chest cx ? cx : null; - boolean isFull2 = c2 != null && isInventoryFull(c2.getInventory()); + Inventory ci2 = getContainerInventory(chestBlock); + boolean isFull2 = ci2 != null && isInventoryFull(ci2); applyCleanTargetSign(sign, itemName, pureOwnerName, newPublic, isFull2); } else if (isCleanRestSign(sign)) { - Chest c3 = chestBlock.getState() instanceof Chest cx ? cx : null; - boolean isFull3 = c3 != null && isInventoryFull(c3.getInventory()); + Inventory ci3 = getContainerInventory(chestBlock); + boolean isFull3 = ci3 != null && isInventoryFull(ci3); applyCleanRestSign(sign, pureOwnerName, newPublic, isFull3); } else { String baseName = getSignColor(colorType, "line4") + pureOwnerName; @@ -3831,13 +4069,12 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b Set uniqueChestLocations = new HashSet<>(); int countForThisItem = 0; if (mysqlEnabled && mysqlManager != null) { - List> allChests = mysqlManager.getTargetChests(playerUUID.toString()); - for (Map map : allChests) { - String worldName = (String) map.get("world"); - int x = (int) map.get("x"); int y = (int) map.get("y"); int z = (int) map.get("z"); - World w = Bukkit.getWorld(worldName); - if (w != null && w.getBlockAt(x, y, z).getState() instanceof Chest) { - uniqueChestLocations.add(worldName + ":" + x + ":" + y + ":" + z); + for (Map map : mysqlManager.getTargetChests(playerUUID.toString())) { + String w = (String) map.get("world"); + int tx = (int) map.get("x"), ty = (int) map.get("y"), tz = (int) map.get("z"); + World bw = Bukkit.getWorld(w); + if (bw != null && isContainer(bw.getBlockAt(tx, ty, tz))) { + uniqueChestLocations.add(w + ":" + tx + ":" + ty + ":" + tz); if (itemInHand.getType().name().equals(map.get("item"))) countForThisItem++; } } @@ -3849,7 +4086,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (slotLoc == null || slotLoc.getWorld() == null) continue; World w = slotLoc.getWorld(); int x = slotLoc.getBlockX(), y = slotLoc.getBlockY(), z = slotLoc.getBlockZ(); - if (w.getBlockAt(x, y, z).getState() instanceof Chest) { + if (isContainer(w.getBlockAt(x, y, z))) { uniqueChestLocations.add(w.getName() + ":" + x + ":" + y + ":" + z); if (itemInHand.getType().name().equals(itemKey)) countForThisItem++; } @@ -3894,8 +4131,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } } removeOldTargetEntry(player.getUniqueId(), chestBlock.getLocation(), itemInHand.getType().name()); - Chest chest = (Chest) chestBlock.getState(); - boolean isFull = isInventoryFull(chest.getInventory()); + Inventory containerInvD = getContainerInventory(chestBlock); + boolean isFull = containerInvD != null && isInventoryFull(containerInvD); String colorType = isFull ? "full" : "target"; if (isCleanSignMode()) { String ownerForSign2 = (pureOwnerName.isEmpty() || pureOwnerName.equalsIgnoreCase("Unknown")) @@ -3969,7 +4206,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } // ── Alle Permission-Checks bestanden → Truhe mit Custom-Titel öffnen ── - if (signBlock != null && chestBlock.getState() instanceof Chest) { + if (signBlock != null && isContainer(chestBlock)) { Sign sign2 = (Sign) signBlock.getState(); String[] lines2 = sign2.getLines(); String cleanType2 = getCleanSignType(sign2); @@ -3983,7 +4220,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b : isMuell(l1) ? "trash" : (isZiel(l1) ? "target" : "input"); event.setCancelled(true); - openTitledChestInventory(player, chestBlock, buildChestTitle(sign2, chestType2)); + openTitledContainerInventory(player, chestBlock, sign2, chestType2); } } } @@ -4081,6 +4318,31 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } } + // Barrel: gleiche Logik wie Chest – ein Fass ist immer ein einzelner Block + if (signBlock == null && block.getState() instanceof Barrel) { + 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(); + boolean isCleanTs3 = isAnyCleanSign(sign); + if (isCleanTs3) { + signBlock = face; + signOwner = getCleanSignOwnerAny(sign); + isAscChest = true; + break; + } else if (lines.length >= 2 && ChatColor.stripColor(lines[0] != null ? lines[0] : "").equalsIgnoreCase("[asc]") && + (ChatColor.stripColor(lines[1] != null ? lines[1] : "").equalsIgnoreCase("input") || + isZiel(ChatColor.stripColor(lines[1] != null ? lines[1] : "")) || + ChatColor.stripColor(lines[1] != null ? lines[1] : "").equalsIgnoreCase("rest") || + isMuell(ChatColor.stripColor(lines[1] != null ? lines[1] : "")))) { + signBlock = face; + signOwner = ChatColor.stripColor(lines[3] != null ? lines[3] : ""); + isAscChest = true; + break; + } + } + } + } + if (signBlock == null) return; boolean isOwner = signOwner.isEmpty() || signOwner.equalsIgnoreCase(player.getName()); @@ -4249,13 +4511,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b UUID pid = event.getPlayer().getUniqueId(); Location customLoc = openCustomInventories.remove(pid); if (customLoc != null) { - BlockState bs = customLoc.getBlock().getState(); - if (bs instanceof Chest realChest) { - // Inhalte des virtuellen Inventars in die echte Truhe übertragen - realChest.getInventory().setContents(event.getInventory().getContents().clone()); + Block cb = customLoc.getBlock(); + Inventory realInv = getContainerInventory(cb); + if (realInv != null) { + realInv.setContents(event.getInventory().getContents().clone()); } - // Kein return – normaler Schließ-Handler soll danach weiterlaufen - // (z.B. Mülltruhe-Processing, Sort-Trigger etc.) } InventoryHolder holder = event.getInventory().getHolder(); @@ -4267,6 +4527,60 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b DoubleChest dc = (DoubleChest) holder; if (dc.getLeftSide() instanceof Chest) chestsToCheck.add((Chest) dc.getLeftSide()); if (dc.getRightSide() instanceof Chest) chestsToCheck.add((Chest) dc.getRightSide()); + } else if (holder instanceof Barrel barrel) { + // Barrel: Mülltruhe + Sign-Update direkt behandeln + Block barrelBlock = barrel.getBlock(); + UUID trashOwnerBarrel = trashChestManager.getTrashChestOwner(barrelBlock.getLocation()); + if (trashOwnerBarrel != null) { + final UUID finalTrashOwnerB = trashOwnerBarrel; + final Location finalLocB = barrelBlock.getLocation(); + new BukkitRunnable() { + @Override public void run() { + if (finalLocB.getBlock().getState() instanceof Barrel freshBarrel) { + trashChestManager.processTrashChestInventory(finalTrashOwnerB, freshBarrel.getInventory()); + } + } + }.runTaskLater(this, 1L); + return; + } + // Sign-Update für Barrel (Ziel/Rest) + for (Block face : adjacentFaces(barrelBlock)) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, barrelBlock)) { + String[] lines = sign.getLines(); + String line1 = ChatColor.stripColor(lines[1] != null ? lines[1] : ""); + boolean cleanOk = isCleanTargetSign(sign) || isCleanRestSign(sign); + boolean normOk = lines.length >= 2 && ChatColor.stripColor(lines[0] != null ? lines[0] : "").equalsIgnoreCase("[asc]") + && (isZiel(line1) || line1.equalsIgnoreCase("rest")); + if (cleanOk || normOk) { + Inventory barrelInv = barrel.getInventory(); + boolean isFull = isInventoryFull(barrelInv); + boolean isCleanInvSign = isAnyCleanSign(sign); + String cleanInvType = getCleanSignType(sign); + String signOwner = isCleanInvSign + ? getCleanSignOwnerAny(sign) + : ChatColor.stripColor(lines[3] != null ? lines[3] : ""); + boolean isPublic = isChestPublic(sign); + if ("target".equals(cleanInvType)) { + String itemName = ChatColor.stripColor(sign.getLine(0)); + applyCleanTargetSign(sign, itemName, signOwner, isPublic, isFull); + sign.update(); + } else if ("rest".equals(cleanInvType)) { + applyCleanRestSign(sign, signOwner, isPublic, isFull); + sign.update(); + } else { + String currentLine1 = ChatColor.stripColor(lines[1] != null ? lines[1] : ""); + String colorType = isFull ? "full" : (currentLine1.equalsIgnoreCase("rest") ? "rest" : "target"); + sign.setLine(0, getSignColor(colorType, "line1") + "[asc]"); + sign.setLine(1, getSignColor(colorType, "line2") + currentLine1); + sign.setLine(2, getSignColor(colorType, "line3") + ChatColor.stripColor(lines[2] != null ? lines[2] : "")); + sign.setLine(3, lines[3] != null ? lines[3] : ""); + sign.update(); + } + break; + } + } + } + return; } else { return; } @@ -4284,8 +4598,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b new BukkitRunnable() { @Override public void run() { - if (finalLoc.getBlock().getState() instanceof Chest freshChest) { - trashChestManager.processTrashChestInventory(finalTrashOwner, freshChest.getInventory()); + Block b = finalLoc.getBlock(); + Inventory inv = getContainerInventory(b); + if (inv != null) { + trashChestManager.processTrashChestInventory(finalTrashOwner, inv); } } }.runTaskLater(this, 1L); @@ -4364,7 +4680,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b && (int) e.get("x") == loc.getBlockX() && (int) e.get("y") == loc.getBlockY() && (int) e.get("z") == loc.getBlockZ()) { - mysqlManager.removeTargetChest(uuid.toString(), existingItem); + // BUGFIX: Nur diesen einzelnen Slot löschen, nicht ALLE Slots des Items. + // removeTargetChest() würde alle Zieltruchen für das Item löschen! + int slot = e.containsKey("slot") ? (int) e.get("slot") : 0; + mysqlManager.removeTargetChestSlot(uuid.toString(), existingItem, slot); } } return; @@ -4522,10 +4841,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } if (targetLoc == null) continue; - if (!(targetLoc.getBlock().getState() instanceof Chest)) continue; + if (!isContainer(targetLoc.getBlock())) continue; - Chest targetChest = (Chest) targetLoc.getBlock().getState(); - Map leftover = targetChest.getInventory().addItem(new ItemStack(mat, amount)); + Inventory targetInvCL = getContainerInventory(targetLoc.getBlock()); + if (targetInvCL == null) continue; + Map leftover = targetInvCL.addItem(new ItemStack(mat, amount)); int transferred = amount - (leftover.isEmpty() ? 0 : leftover.get(0).getAmount()); if (transferred > 0) { @@ -4620,9 +4940,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b for (Location restLoc : restLocs) { if (restLoc == null) continue; if (isWorldBlacklisted(restLoc.getWorld())) continue; - if (!(restLoc.getBlock().getState() instanceof Chest restChest)) continue; + if (!isContainer(restLoc.getBlock())) continue; - Inventory restInv = restChest.getInventory(); + Inventory restInv = getContainerInventory(restLoc.getBlock()); + if (restInv == null) continue; boolean anyMoved = false; for (int slot = 0; slot < restInv.getSize(); slot++) { @@ -4635,11 +4956,14 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (t != null && !isChestCachedFull(t)) { targetLoc = t; break; } } if (targetLoc == null) continue; - if (!(targetLoc.getBlock().getState() instanceof Chest targetChest)) continue; + if (!isContainer(targetLoc.getBlock())) continue; + + Inventory targetInv = getContainerInventory(targetLoc.getBlock()); + if (targetInv == null) continue; // Schild-Validierung: Zieltruhe muss dem Besitzer gehören boolean validTarget = false; - for (Block b2 : getChestBlocks(targetChest)) { + for (Block b2 : getContainerBlocks(targetLoc.getBlock())) { for (Block face2 : new Block[]{b2.getRelative(1,0,0), b2.getRelative(-1,0,0), b2.getRelative(0,0,1), b2.getRelative(0,0,-1)}) { if (face2.getState() instanceof Sign ts2 && isSignAttachedToChest(face2, b2)) { String sOwner; @@ -4659,7 +4983,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } if (!validTarget) continue; - Map leftover = targetChest.getInventory().addItem(item.clone()); + Map leftover = targetInv.addItem(item.clone()); boolean full = !leftover.isEmpty(); if (full) { markChestFull(targetLoc); @@ -4677,10 +5001,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } // Schild der Zieltruhe aktualisieren (voll/nicht voll) - for (Block b2 : getChestBlocks(targetChest)) { + for (Block b2 : getContainerBlocks(targetLoc.getBlock())) { for (Block face2 : new Block[]{b2.getRelative(1,0,0), b2.getRelative(-1,0,0), b2.getRelative(0,0,1), b2.getRelative(0,0,-1)}) { if (face2.getState() instanceof Sign ts2 && isSignAttachedToChest(face2, b2)) { - boolean isFull2 = isInventoryFull(targetChest.getInventory()); + boolean isFull2 = isInventoryFull(targetInv); if (isCleanTargetSign(ts2)) { String cOwner = getCleanSignOwner(ts2); boolean cPub = isChestPublic(ts2); @@ -4817,7 +5141,12 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b for (String chestId : chestsToCheck) { String path = inputListPath + "." + chestId; Location loc = getLocationFromPath(path); - if (loc == null) { playerData.set(path, null); continue; } + if (loc == null) { + // BUGFIX: savePlayerData() nach Entfernen eines ungültigen Eintrags aufrufen + playerData.set(path, null); + savePlayerData(); + continue; + } boolean stillValid = checkSingleInputChest(ownerUUID, loc, chestId, false); if (!stillValid) { playerData.set(path, null); savePlayerData(); } } @@ -4883,11 +5212,12 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } if (targetLoc == null) continue; - if (!(targetLoc.getBlock().getState() instanceof Chest)) continue; + if (!isContainer(targetLoc.getBlock())) continue; - Chest targetChest = (Chest) targetLoc.getBlock().getState(); + Inventory targetInvRC = getContainerInventory(targetLoc.getBlock()); + if (targetInvRC == null) continue; ItemStack toAdd = new ItemStack(mat, amount); - Map leftover = targetChest.getInventory().addItem(toAdd); + Map leftover = targetInvRC.addItem(toAdd); int transferred = amount - (leftover.isEmpty() ? 0 : leftover.get(0).getAmount()); if (transferred > 0) { @@ -4939,14 +5269,16 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b private boolean checkSingleInputChest(UUID ownerUUID, Location location, String debugId, boolean crosslinkMode, Map> preloadedTargets, List preloadedRests) { if (isWorldBlacklisted(location.getWorld())) return true; - if (!(location.getBlock().getState() instanceof Chest)) return false; + if (!isContainer(location.getBlock())) return false; + + Inventory sourceInv = getContainerInventory(location.getBlock()); + if (sourceInv == null) return false; - Chest chest = (Chest) location.getBlock().getState(); Block inputSignBlock = null; boolean isPublic = false; String ownerName = "Unknown"; - List chestBlocks = getChestBlocks(chest); + List chestBlocks = getContainerBlocks(location.getBlock()); outerLoop: for (Block b : chestBlocks) { for (Block face : new Block[]{b.getRelative(1,0,0), b.getRelative(-1,0,0), b.getRelative(0,0,1), b.getRelative(0,0,-1)}) { @@ -4976,7 +5308,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (inputSignBlock == null) return false; boolean hasItems = false; - for (ItemStack item : chest.getInventory().getContents()) { + for (ItemStack item : sourceInv.getContents()) { if (item != null && item.getType() != Material.AIR) { hasItems = true; break; } } if (!hasItems) return true; @@ -5005,7 +5337,6 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b String basePath = "players." + ownerUUID + ".target-chests"; if (playerData.contains(basePath)) { for (String item : playerData.getConfigurationSection(basePath).getKeys(false)) { - // FIX: handle both old flat + new slotted format for (Location tLoc : getTargetChestSlotsYaml(ownerUUID, item)) { effectiveTargets.computeIfAbsent(item, k -> new ArrayList<>()).add(tLoc); } @@ -5017,7 +5348,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b effectiveRests = getRestChestLocations(ownerUUID); } - distributeItemsForOwner(ownerUUID, ownerPlayer, chest.getInventory(), ownerName, location, effectiveTargets, effectiveRests); + distributeItemsForOwner(ownerUUID, ownerPlayer, sourceInv, ownerName, location, effectiveTargets, effectiveRests); return true; } @@ -5172,12 +5503,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (targetChestLocation == null) continue; - if (!(targetChestLocation.getBlock().getState() instanceof Chest)) { + if (!isContainer(targetChestLocation.getBlock())) { if (!isTrashDest && ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { ownerPlayer.sendMessage(getMessage("target-chest-missing").replace("%item%", isRestChest ? "Rest-Truhe" : item.getType().name())); } if (isTrashDest) { - // Mülltruhe existiert nicht mehr → überspringen, kein Datenlösch-Cleanup nötig continue; } else if (isRestChest) { if (mysqlEnabled && mysqlManager != null) { @@ -5210,8 +5540,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b continue; } - Chest targetChest = (Chest) targetChestLocation.getBlock().getState(); - Inventory targetInventory = targetChest.getInventory(); + Inventory targetInventory = getContainerInventory(targetChestLocation.getBlock()); + if (targetInventory == null) continue; boolean isValidTarget = false; Block signBlock = null; @@ -5219,7 +5549,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b // Mülltruhe braucht kein [asc]-Schild als Ziel – direkt als gültig markieren isValidTarget = true; } else { - List chestBlocks = getChestBlocks(targetChest); + List chestBlocks = getContainerBlocks(targetChestLocation.getBlock()); outerLoop: for (Block b : chestBlocks) { for (Block face : new Block[]{b.getRelative(1,0,0), b.getRelative(-1,0,0), b.getRelative(0,0,1), b.getRelative(0,0,-1)}) { @@ -5228,7 +5558,6 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b String line0 = ChatColor.stripColor(lines[0] != null ? lines[0] : ""); String line1 = ChatColor.stripColor(lines[1] != null ? lines[1] : ""); String line3Clean = ChatColor.stripColor(lines[3] != null ? lines[3] : ""); - // Clean-Schild: kein [asc]/ziel, aber Marker auf Z.4 if (isCleanTargetSign(sign)) { String signOwnerName = getCleanSignOwner(sign); if (signOwnerName.equalsIgnoreCase(ownerName) || signOwnerName.equalsIgnoreCase("Unknown")) { @@ -5260,14 +5589,13 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } // end else (nicht isTrashDest) if (!isValidTarget) { - // Kein gültiges ASC-Schild an der registrierten Zieltruhe - // → Fallback auf Rest-Truhe statt Item zu überspringen if (allRestChestsFull) continue; if (activeRestChestLocation != null) { targetChestLocation = activeRestChestLocation; isRestChest = true; - if (!(targetChestLocation.getBlock().getState() instanceof Chest)) continue; - targetInventory = ((Chest) targetChestLocation.getBlock().getState()).getInventory(); + if (!isContainer(targetChestLocation.getBlock())) continue; + targetInventory = getContainerInventory(targetChestLocation.getBlock()); + if (targetInventory == null) continue; } else { continue; } @@ -5330,9 +5658,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b final Location fullLoc = targetChestLocation; for (Location loc : targetChestList) { if (loc == null || isChestCachedFull(loc) || loc.equals(fullLoc)) continue; - if (!(loc.getBlock().getState() instanceof Chest)) continue; - Chest nextChest = (Chest) loc.getBlock().getState(); - Map leftover2 = nextChest.getInventory().addItem(remaining.clone()); + if (!isContainer(loc.getBlock())) continue; + Inventory nextInv = getContainerInventory(loc.getBlock()); + if (nextInv == null) continue; + Map leftover2 = nextInv.addItem(remaining.clone()); if (leftover2.isEmpty()) { sourceInventory.setItem(slot, null); spawnTransferParticles(null, loc); @@ -5347,20 +5676,21 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b if (!placed) { // Alle Zieltruhen voll → Fallback auf Rest-Truhe - if (activeRestChestLocation != null && activeRestChestLocation.getBlock().getState() instanceof Chest) { - Chest restChest = (Chest) activeRestChestLocation.getBlock().getState(); - Map leftover3 = restChest.getInventory().addItem(remaining.clone()); - if (leftover3.isEmpty()) { - sourceInventory.setItem(slot, null); - spawnTransferParticles(null, activeRestChestLocation); - } else { - markChestFull(activeRestChestLocation); - item.setAmount(leftover3.get(0).getAmount()); - sourceInventory.setItem(slot, item); + if (activeRestChestLocation != null && isContainer(activeRestChestLocation.getBlock())) { + Inventory restInvFB = getContainerInventory(activeRestChestLocation.getBlock()); + if (restInvFB != null) { + Map leftover3 = restInvFB.addItem(remaining.clone()); + if (leftover3.isEmpty()) { + sourceInventory.setItem(slot, null); + spawnTransferParticles(null, activeRestChestLocation); + } else { + markChestFull(activeRestChestLocation); + item.setAmount(leftover3.get(0).getAmount()); + sourceInventory.setItem(slot, item); + } } } else { // Keine Rest-Truhe verfügbar – verbleibenden Stack in Input-Truhe schreiben - // + Spieler über volle Zieltruhe informieren if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { String message = getMessage("target-chest-full") .replace("%item%", item.getType().name()) @@ -5381,19 +5711,21 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b nextRestLoc = loc; break; } } - if (nextRestLoc != null && nextRestLoc.getBlock().getState() instanceof Chest) { - Chest nextRestChest = (Chest) nextRestLoc.getBlock().getState(); - Map leftover2 = nextRestChest.getInventory().addItem( - leftover.isEmpty() ? new ItemStack(item.getType(), 0) : leftover.get(0).clone()); - if (leftover2.isEmpty()) { - sourceInventory.setItem(slot, null); - spawnTransferParticles(null, nextRestLoc); - continue; - } else { - // BUG FIX: nextRestLoc als voll markieren - markChestFull(nextRestLoc); - item.setAmount(leftover2.get(0).getAmount()); - sourceInventory.setItem(slot, item); + if (nextRestLoc != null && isContainer(nextRestLoc.getBlock())) { + Inventory nextRestInv = getContainerInventory(nextRestLoc.getBlock()); + if (nextRestInv != null) { + Map leftover2 = nextRestInv.addItem( + leftover.isEmpty() ? new ItemStack(item.getType(), 0) : leftover.get(0).clone()); + if (leftover2.isEmpty()) { + sourceInventory.setItem(slot, null); + spawnTransferParticles(null, nextRestLoc); + continue; + } else { + // BUG FIX: nextRestLoc als voll markieren + markChestFull(nextRestLoc); + item.setAmount(leftover2.get(0).getAmount()); + sourceInventory.setItem(slot, item); + } } } else { if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { @@ -5420,16 +5752,17 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b public void onInventoryMoveItem(InventoryMoveItemEvent event) { Inventory destination = event.getDestination(); InventoryHolder holder = destination.getHolder(); - if (!(holder instanceof Chest) && !(holder instanceof DoubleChest)) return; + if (!(holder instanceof Chest) && !(holder instanceof DoubleChest) && !(holder instanceof Barrel)) return; - final Chest destChest = (holder instanceof Chest) ? (Chest) holder : null; List blocksToScan = new ArrayList<>(); if (holder instanceof DoubleChest) { DoubleChest dc = (DoubleChest) holder; if (dc.getLeftSide() instanceof Chest) blocksToScan.add(((Chest) dc.getLeftSide()).getBlock()); if (dc.getRightSide() instanceof Chest) blocksToScan.add(((Chest) dc.getRightSide()).getBlock()); - } else if (holder instanceof Chest) { + } else if (holder instanceof Chest destChest) { blocksToScan.add(destChest.getBlock()); + } else if (holder instanceof Barrel destBarrel) { + blocksToScan.add(destBarrel.getBlock()); } // ── Mülltruche: Items via Hopper → sofort löschen ──────────────────── @@ -5437,15 +5770,17 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b // Bei DoubleChest nehmen wir den ersten Block aus blocksToScan. final Location destLocation = !blocksToScan.isEmpty() ? blocksToScan.get(0).getLocation() - : (destChest != null ? destChest.getLocation() : null); + : null; if (destLocation != null) { final UUID trashOwnerUUID = trashChestManager.getTrashChestOwner(destLocation); if (trashOwnerUUID != null) { new BukkitRunnable() { @Override public void run() { - if (destLocation.getBlock().getState() instanceof Chest fresh) { - trashChestManager.processTrashChestInventory(trashOwnerUUID, fresh.getInventory()); + Block b = destLocation.getBlock(); + Inventory inv = getContainerInventory(b); + if (inv != null) { + trashChestManager.processTrashChestInventory(trashOwnerUUID, inv); } } }.runTaskLater(Main.this, 1L); @@ -5485,6 +5820,27 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor, org.b } } - // Kein Input- oder Rest-Schild gefunden → nichts zu tun + // ── Zieltruhe: nach Hopper-Move auf Vollstaendigkeit pruefen ────────── + // Wenn ein Hopper ein Item in eine Zieltruhe befoerdert und diese + // dabei voll wird, muss das Schild sofort aktualisiert werden. + // Wir pruefen 1 Tick spaeter (nachdem das Item tatsaechlich drin ist). + if (!blocksToScan.isEmpty()) { + final List finalBlocks = new ArrayList<>(blocksToScan); + new BukkitRunnable() { + @Override + public void run() { + for (Block b : finalBlocks) { + Inventory inv = getContainerInventory(b); + if (inv != null && isInventoryFull(inv)) { + // Schild auf "voll" setzen und Cache aktualisieren + Location loc = b.getLocation(); + if (!isChestCachedFull(loc)) { + markChestFull(loc); + } + } + } + } + }.runTaskLater(Main.this, 1L); + } } } \ No newline at end of file diff --git a/src/main/java/com/viper/autosortchest/MySQLManager.java b/src/main/java/com/viper/autosortchest/MySQLManager.java index 67990fb..476c304 100644 --- a/src/main/java/com/viper/autosortchest/MySQLManager.java +++ b/src/main/java/com/viper/autosortchest/MySQLManager.java @@ -4,36 +4,38 @@ import java.sql.*; import java.util.*; public class MySQLManager { - /** - * Setzt die Priorität (prio) für eine Zieltruhe (target chest) anhand von uuid, item, slot. - * Legt die Spalte prio an, falls sie noch nicht existiert. - */ - public void setTargetChestPrio(String uuid, String item, int slot, int prio) { - ensureConnected(); - // Spalte prio anlegen, falls sie fehlt - try (Statement st = connection.createStatement()) { - ResultSet rs = connection.getMetaData().getColumns(connection.getCatalog(), null, "asc_target_chests", "prio"); - if (!rs.next()) { - st.execute("ALTER TABLE asc_target_chests ADD COLUMN prio INT DEFAULT 0;"); - } - rs.close(); - } catch (SQLException e) { - if (!e.getMessage().toLowerCase().contains("duplicate column")) { - e.printStackTrace(); - } + + /** + * Setzt die Priorität (prio) für eine Zieltruhe (target chest) anhand von uuid, item, slot. + * Legt die Spalte prio an, falls sie noch nicht existiert. + */ + public void setTargetChestPrio(String uuid, String item, int slot, int prio) { + ensureConnected(); + // Spalte prio anlegen, falls sie fehlt (lazy migration) + try (Statement st = connection.createStatement()) { + ResultSet rs = connection.getMetaData().getColumns(connection.getCatalog(), null, "asc_target_chests", "prio"); + if (!rs.next()) { + st.execute("ALTER TABLE asc_target_chests ADD COLUMN prio INT DEFAULT 0;"); } - // Prio setzen - try (PreparedStatement ps = connection.prepareStatement( - "UPDATE asc_target_chests SET prio=? WHERE uuid=? AND item=? AND slot=?;")) { - ps.setInt(1, prio); - ps.setString(2, uuid); - ps.setString(3, item); - ps.setInt(4, slot); - ps.executeUpdate(); - } catch (SQLException e) { + rs.close(); + } catch (SQLException e) { + if (!e.getMessage().toLowerCase().contains("duplicate column")) { e.printStackTrace(); } } + // Prio setzen + try (PreparedStatement ps = connection.prepareStatement( + "UPDATE asc_target_chests SET prio=? WHERE uuid=? AND item=? AND slot=?;")) { + ps.setInt(1, prio); + ps.setString(2, uuid); + ps.setString(3, item); + ps.setInt(4, slot); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + public Connection getConnection() { return connection; } @@ -372,6 +374,7 @@ public class MySQLManager { * serverName gesetzt → nur Transfers mit target_server == serverName ODER target_server == '' (Legacy). */ public List> getPendingTransfers(String uuid, String serverName) { + ensureConnected(); List> list = new ArrayList<>(); try { PreparedStatement ps; @@ -573,6 +576,7 @@ public class MySQLManager { /** Löscht eine spezifische Slot-Zieltruhe und verschiebt höhere Slots nach unten. */ public void removeTargetChestSlot(String uuid, String item, int slot) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "DELETE FROM asc_target_chests WHERE uuid=? AND item=? AND slot=?;")) { ps.setString(1, uuid); @@ -596,6 +600,7 @@ public class MySQLManager { /** Gibt den nächsten freien Slot für ein Item zurück. */ public int getNextTargetSlot(String uuid, String item) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "SELECT COALESCE(MAX(slot)+1, 0) AS next_slot FROM asc_target_chests WHERE uuid=? AND item=?;")) { ps.setString(1, uuid); @@ -610,6 +615,7 @@ public class MySQLManager { /** Zählt wie viele Zieltruhen für ein bestimmtes Item registriert sind. */ public int countTargetChestsForItem(String uuid, String item) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "SELECT COUNT(*) FROM asc_target_chests WHERE uuid=? AND item=?;")) { ps.setString(1, uuid); @@ -753,6 +759,7 @@ public class MySQLManager { /** Gibt den nächsten freien Slot für Rest-Truhen zurück. */ public int getNextRestSlot(String uuid) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "SELECT COALESCE(MAX(slot)+1, 0) AS next_slot FROM asc_rest_chests WHERE uuid=?;")) { ps.setString(1, uuid); @@ -768,6 +775,7 @@ public class MySQLManager { * Gibt den Slot zurück, den diese Location bereits belegt, oder -1 wenn nicht gefunden. */ public int getRestSlotForLocation(String uuid, String world, int x, int y, int z) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "SELECT slot FROM asc_rest_chests WHERE uuid=? AND world=? AND x=? AND y=? AND z=?;")) { ps.setString(1, uuid); @@ -849,6 +857,7 @@ public class MySQLManager { /** Löscht eine spezifische Rest-Truhe anhand des Slots. */ public void removeRestChestSlot(String uuid, int slot) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "DELETE FROM asc_rest_chests WHERE uuid=? AND slot=?;")) { ps.setString(1, uuid); @@ -883,6 +892,7 @@ public class MySQLManager { /** Löscht ALLE Rest-Truhen eines Spielers. */ public void removeRestChest(String uuid) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "DELETE FROM asc_rest_chests WHERE uuid=?;")) { ps.setString(1, uuid); @@ -895,6 +905,7 @@ public class MySQLManager { // --- Hilfsmethoden für serverCrosslink (unverändert) --- public List> getAllInputChests() { + ensureConnected(); List> list = new ArrayList<>(); try (PreparedStatement ps = connection.prepareStatement("SELECT * FROM asc_input_chests;")) { ResultSet rs = ps.executeQuery(); @@ -918,6 +929,7 @@ public class MySQLManager { } public List> getAllTargetChests() { + ensureConnected(); List> list = new ArrayList<>(); try (PreparedStatement ps = connection.prepareStatement("SELECT * FROM asc_target_chests;")) { ResultSet rs = ps.executeQuery(); @@ -941,6 +953,7 @@ public class MySQLManager { } public Map getAnyRestChest() { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "SELECT * FROM asc_rest_chests WHERE `public`=1 ORDER BY uuid, slot LIMIT 1;")) { ResultSet rs = ps.executeQuery(); @@ -969,6 +982,7 @@ public class MySQLManager { /** Legt eine Mülltruche an oder aktualisiert sie. */ public void setTrashChest(String uuid, String world, int x, int y, int z, String server) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "INSERT INTO asc_trash_chests (uuid, world, x, y, z, server) VALUES (?,?,?,?,?,?) " + "ON DUPLICATE KEY UPDATE world=VALUES(world), x=VALUES(x), y=VALUES(y), z=VALUES(z), server=VALUES(server)")) { @@ -984,6 +998,7 @@ public class MySQLManager { /** Gibt die Mülltruche eines Spielers zurück (nur die des eigenen Servers). */ public Map getTrashChest(String uuid, String serverName) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "SELECT * FROM asc_trash_chests WHERE uuid=? AND (server=? OR server='') LIMIT 1")) { ps.setString(1, uuid); @@ -1004,6 +1019,7 @@ public class MySQLManager { /** Gibt ALLE Mülltruchen zurück (für sign-update oder cross-server). */ public List> getAllTrashChests() { + ensureConnected(); List> list = new ArrayList<>(); try (PreparedStatement ps = connection.prepareStatement("SELECT * FROM asc_trash_chests")) { ResultSet rs = ps.executeQuery(); @@ -1023,6 +1039,7 @@ public class MySQLManager { /** Entfernt die Mülltruche eines Spielers. */ public void removeTrashChest(String uuid) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "DELETE FROM asc_trash_chests WHERE uuid=?")) { ps.setString(1, uuid); @@ -1032,6 +1049,7 @@ public class MySQLManager { /** Speichert die komplette Filter-Liste (ersetzt alte Einträge). */ public void setTrashItems(String uuid, List items) { + ensureConnected(); try (PreparedStatement del = connection.prepareStatement( "DELETE FROM asc_trash_items WHERE uuid=?")) { del.setString(1, uuid); @@ -1051,6 +1069,7 @@ public class MySQLManager { /** Lädt die Filter-Liste eines Spielers. */ public List getTrashItems(String uuid) { + ensureConnected(); List list = new ArrayList<>(); try (PreparedStatement ps = connection.prepareStatement( "SELECT item FROM asc_trash_items WHERE uuid=?")) { @@ -1063,6 +1082,7 @@ public class MySQLManager { /** Fügt ein einzelnes Item zur Filter-Liste hinzu. */ public void addTrashItem(String uuid, String item) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "INSERT IGNORE INTO asc_trash_items (uuid, item) VALUES (?,?)")) { ps.setString(1, uuid); @@ -1073,6 +1093,7 @@ public class MySQLManager { /** Entfernt ein einzelnes Item aus der Filter-Liste. */ public void removeTrashItem(String uuid, String item) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "DELETE FROM asc_trash_items WHERE uuid=? AND item=?")) { ps.setString(1, uuid); @@ -1083,6 +1104,7 @@ public class MySQLManager { /** Entfernt alle Filter-Items eines Spielers. */ public void removeAllTrashItems(String uuid) { + ensureConnected(); try (PreparedStatement ps = connection.prepareStatement( "DELETE FROM asc_trash_items WHERE uuid=?")) { ps.setString(1, uuid); diff --git a/src/main/java/com/viper/autosortchest/TrashChestManager.java b/src/main/java/com/viper/autosortchest/TrashChestManager.java index 869fb76..a0129a1 100644 --- a/src/main/java/com/viper/autosortchest/TrashChestManager.java +++ b/src/main/java/com/viper/autosortchest/TrashChestManager.java @@ -6,6 +6,8 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Chest; +import org.bukkit.block.BlockState; +import org.bukkit.block.Barrel; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; @@ -303,7 +305,7 @@ public class TrashChestManager { */ public void removeTrashChest(UUID uuid, Location loc) { trashChestLocations.remove(uuid); - locationToOwner.remove(locKey(loc)); + if (loc != null) locationToOwner.remove(locKey(loc)); trashFilterLists.remove(uuid); if (plugin.isMysqlEnabled() && plugin.getMysqlManager() != null) { plugin.getMysqlManager().removeTrashChest(uuid.toString()); @@ -379,7 +381,9 @@ public class TrashChestManager { public void clearTrashChest(UUID ownerUUID) { Location loc = trashChestLocations.get(ownerUUID); if (loc == null || loc.getWorld() == null) return; - if (loc.getBlock().getState() instanceof Chest chest) chest.getInventory().clear(); + BlockState state = loc.getBlock().getState(); + if (state instanceof Chest chest) chest.getInventory().clear(); + else if (state instanceof Barrel barrel) barrel.getInventory().clear(); } // ══════════════════════════════════════════════════════════════════════════ @@ -473,8 +477,11 @@ public class TrashChestManager { for (Map.Entry entry : new HashMap<>(trashChestLocations).entrySet()) { Location loc = entry.getValue(); if (loc == null || loc.getWorld() == null) continue; - if (loc.getBlock().getState() instanceof Chest chest) { + BlockState state = loc.getBlock().getState(); + if (state instanceof Chest chest) { processTrashChestInventory(entry.getKey(), chest.getInventory()); + } else if (state instanceof Barrel barrel) { + processTrashChestInventory(entry.getKey(), barrel.getInventory()); } } } @@ -822,6 +829,7 @@ public class TrashChestManager { } private String locKey(Location loc) { + if (loc == null || loc.getWorld() == null) return "null"; return loc.getWorld().getName() + ":" + loc.getBlockX() + ":" + loc.getBlockY() + ":" + loc.getBlockZ(); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e0cdd21..d4e9966 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -12,7 +12,7 @@ # ============================================================ # Version der Konfigurationsdatei – bitte nicht ändern! -version: "2.5" +version: "2.8" # Debug-Modus: true = Ausführliche Logs in der Konsole (nur zum Entwickeln) debug: false @@ -95,11 +95,17 @@ chest_limits: # WICHTIG: Spieler OHNE jegliche autosortchest.limit.*-Permission können bei # aktivierten Limits KEINE Truhen erstellen (Limit = 0). # Vergib autosortchest.limit.default an alle normalen Spieler (z.B. in LuckPerms). + # + # BARREL-UNTERSTÜTZUNG: + # Fässer (Barrel) und Truhen (Chest) werden gemeinsam gezählt! + # Das Limit gilt für BEIDE zusammen. + # Beispiel: target: 50 = max. 50 Truhen ODER 50 Fässer ODER z.B. 30 Truhen + 20 Fässer. + # Fässer können genau wie Truhen als Input, Ziel, Rest oder Mülltruhe eingesetzt werden. default: - input: 1 # Eingangstruhen - rest: 1 # Rest-Truhen (Fallback) - target: 50 # Zieltruhen gesamt - target_per_item: 1 # Zieltruhen pro Item-Typ + input: 1 # Eingangstruhen + Eingangsfässer gesamt + rest: 1 # Rest-Truhen + Rest-Fässer gesamt + target: 50 # Zieltruhen + Zielfässer gesamt + target_per_item: 1 # Zieltruhen/Zielfässer pro Item-Typ vip: input: 2 @@ -264,6 +270,19 @@ chest-titles: trash: de: "&4Mülltruhe" en: "&4Trash Chest" + # Fass-Titel (Barrel) – werden automatisch verwendet wenn ein Fass geöffnet wird + input-barrel: + de: "&6Eingangsfass" + en: "&6Input Barrel" + target-barrel: + de: "&6%item%" + en: "&6%item%" + rest-barrel: + de: "&6Rest-Fass" + en: "&6Rest Barrel" + trash-barrel: + de: "&4Müllfass" + en: "&4Trash Barrel" # ============================================================ # SYSTEM-NACHRICHTEN (Spieler-Feedback) @@ -329,7 +348,7 @@ messages: # --- AutoSign-Befehl (/asc autosign) --- # Platzhalter: %item% = Item-Name - autosign-no-chest: "&cDu schaust auf keine Truhe! &7(max. 5 Blöcke)" + autosign-no-chest: "&cDu schaust auf keine Truhe oder kein Fass! &7(max. 5 Blöcke)" autosign-invalid-type:"&cUngültiger Typ! Nutze: input, ziel, rest, trash" autosign-unknown-item:"&cUnbekanntes Item: &e%item%" autosign-no-space: "&cKein freier Platz für ein Schild an dieser Truhe!" @@ -353,6 +372,10 @@ messages: # --- Konsolen-Hinweis (wenn Spieler-Befehl per Konsole aufgerufen) --- console-only: "&cDieser Befehl ist nur für Spieler! (Konsole: reload, import, export, list)" + # --- Autosign-Verwendungshinweis --- + autosign-player-only: "&cDieser Befehl ist nur für Spieler!" + autosign-usage: "&cVerwendung: /asc autosign [item|hand]" + # ============================================================ # MÜLLTRUHEN-GUI # ============================================================ @@ -462,8 +485,4 @@ trash-gui: export-backup: "&7 Backup: &f%file%" export-backup-skipped: "&7 Backup: &8Übersprungen (players.yml war leer)" export-error: "&cExport fehlgeschlagen: &e%error%" - backup-failed: "&cBackup fehlgeschlagen: &e%error%" - - # --- Autosign-Verwendungshinweis --- - autosign-player-only: "&cDieser Befehl ist nur für Spieler!" - autosign-usage: "&cVerwendung: /asc autosign [item|hand]" \ No newline at end of file + backup-failed: "&cBackup fehlgeschlagen: &e%error%" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 875d2d1..34556e5 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,9 +1,9 @@ name: AutoSortChest -version: 2.7 +version: 2.9 main: com.viper.autosortchest.Main api-version: 1.21 authors: [M_Viper] -description: Ein Plugin zum automatischen Sortieren von Items in Truhen +description: Ein Plugin zum automatischen Sortieren von Items in Truhen und Fässern commands: asc: description: AutoSortChest Befehle @@ -11,7 +11,7 @@ commands: aliases: [autosortchest] permissions: autosortchest.use: - description: Erlaubt das Erstellen von AutoSortChest-Schildern (Eingang, Ziel, Rest, Muelltruhe) sowie die Verwendung von /asc autosign + description: Erlaubt das Erstellen von ASC-Schildern an Truhen und Faessern (Eingang, Ziel, Rest, Muelltruhe) sowie /asc autosign default: true autosortchest.reload: description: Erlaubt das Neuladen der Konfiguration mit /asc reload