diff --git a/src/main/java/com/viper/autosortchest/Main.java b/src/main/java/com/viper/autosortchest/Main.java index c50fe7b..053dabe 100644 --- a/src/main/java/com/viper/autosortchest/Main.java +++ b/src/main/java/com/viper/autosortchest/Main.java @@ -42,7 +42,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { private FileConfiguration config; private final Map> fullChestMessageTracker = new HashMap<>(); private static final long MESSAGE_COOLDOWN = 5 * 60 * 1000; // 5 Minuten in Millisekunden - private static final String CONFIG_VERSION = "1.5"; + private static final String CONFIG_VERSION = "1.7"; // Version erhöht für das Update @Override public void onEnable() { @@ -82,6 +82,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { // Aktualisiere Konfiguration und Schilder updateConfig(); updateExistingSigns(); + + // ERWEITERUNG: Migration alter Daten in das neue Listen-System + migrateInputChests(); // ASCII ART LOGO getLogger().info(""); @@ -102,7 +105,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { getLogger().info("AutoSortChest Plugin deaktiviert!"); } - // --- NEUE HILFSMETHODE FÜR DOPPELTRUHEN --- + // --- HILFSMETHODE FÜR DOPPELTRUHEN --- private List getChestBlocks(Chest chest) { List blocks = new ArrayList<>(); InventoryHolder holder = chest.getInventory().getHolder(); @@ -417,6 +420,55 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { } } + // --- ERWEITERUNG: Hilfsmethode zum Laden von Location aus einem beliebigen Pfad --- + private Location getLocationFromPath(String path) { + String worldName = playerData.getString(path + ".world"); + World world = getServer().getWorld(worldName); + if (world == null) 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); + } + // ----------------------------------------------------------------------- + + // --- ERWEITERUNG: Methode zum Migrieren alter Daten --- + private void migrateInputChests() { + if (playerData.getConfigurationSection("players") == null) return; + boolean migrated = false; + + for (String uuidString : playerData.getConfigurationSection("players").getKeys(false)) { + String oldPath = "players." + uuidString + ".input-chest"; + // Prüfen, ob der alte Eintrag existiert + if (playerData.contains(oldPath)) { + try { + UUID uuid = UUID.fromString(uuidString); + String world = playerData.getString(oldPath + ".world"); + int x = playerData.getInt(oldPath + ".x"); + int y = playerData.getInt(oldPath + ".y"); + int z = playerData.getInt(oldPath + ".z"); + World w = Bukkit.getWorld(world); + + if (w != null) { + Location loc = new Location(w, x, y, z); + // Neue Methode aufrufen, die in die Liste speichert + addInputChestLocation(uuid, loc); + // Alten Eintrag löschen + playerData.set(oldPath, null); + migrated = true; + getLogger().info("Eingangstruhe für Spieler " + uuidString + " in neues System migriert."); + } + } catch (IllegalArgumentException e) { + getLogger().warning("Ungültige UUID beim Migrieren: " + uuidString); + } + } + } + if (migrated) { + savePlayerData(); + } + } + // ----------------------------------------------------------------------- + private void updateExistingSigns() { if (playerData == null) { getLogger().warning("playerData ist null. Kann bestehende Schilder nicht aktualisieren."); @@ -436,27 +488,28 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { continue; } - String path = "players." + uuidString; + String basePath = "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; + // --- ERWEITERUNG: Eingangstruhe (Jetzt Liste) --- + String inputListPath = basePath + ".input-chests"; + // Fallback für altes Format (sollte durch Migration eigentlich weg sein, aber zur Sicherheit) + String oldInputPath = basePath + ".input-chest"; + + List inputLocs = new ArrayList<>(); + + if (playerData.contains(inputListPath)) { + for (String chestId : playerData.getConfigurationSection(inputListPath).getKeys(false)) { + Location loc = getLocationFromPath(inputListPath + "." + chestId); + if (loc != null) inputLocs.add(loc); } - 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); + } else if (playerData.contains(oldInputPath)) { + Location loc = getLocationFromPath(oldInputPath); + if (loc != null) inputLocs.add(loc); + } + + for (Location chestLocation : inputLocs) { Block chestBlock = chestLocation.getBlock(); - - if (!(chestBlock.getState() instanceof Chest)) { - getLogger().warning("Eingangstruhe bei " + chestLocation + " ist keine Truhe"); - continue; - } + if (!(chestBlock.getState() instanceof Chest)) continue; List blocks = getChestBlocks((Chest) chestBlock.getState()); for (Block b : blocks) { @@ -484,22 +537,16 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { } } } + // ---------------------------------------------- // Zieltruhen - String targetPath = path + ".target-chests"; + String targetPath = basePath + ".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); + Location chestLocation = getLocationFromPath(targetChestPath); + if (chestLocation == null) continue; + Block chestBlock = chestLocation.getBlock(); if (!(chestBlock.getState() instanceof Chest)) { @@ -543,18 +590,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { } // --- NEU: Rest-Truhe aktualisieren --- - String restPath = path + ".rest-chest"; + String restPath = basePath + ".rest-chest"; if (playerData.contains(restPath)) { - String worldName = playerData.getString(restPath + ".world"); - World world = getServer().getWorld(worldName); - if (world == null) { - getLogger().warning("Welt " + worldName + " für Rest-Truhe von UUID " + uuidString + " nicht gefunden"); - continue; - } - int x = playerData.getInt(restPath + ".x"); - int y = playerData.getInt(restPath + ".y"); - int z = playerData.getInt(restPath + ".z"); - Location chestLocation = new Location(world, x, y, z); + Location chestLocation = getLocationFromPath(restPath); + if (chestLocation == null) continue; + Block chestBlock = chestLocation.getBlock(); if (!(chestBlock.getState() instanceof Chest)) { @@ -646,14 +686,24 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { return attached; } - private void setInputChestLocation(UUID playerUUID, Location location) { - String path = "players." + playerUUID + ".input-chest"; + // --- ERWEITERUNG: Methode zum Hinzufügen einer Input Chest (Liste) --- + private void addInputChestLocation(UUID playerUUID, Location location) { + String basePath = "players." + playerUUID + ".input-chests"; + String id = UUID.randomUUID().toString(); // Eindeutige ID für jede Truhe + String path = basePath + "." + id; + playerData.set(path + ".world", location.getWorld().getName()); playerData.set(path + ".x", location.getBlockX()); playerData.set(path + ".y", location.getBlockY()); playerData.set(path + ".z", location.getBlockZ()); savePlayerData(); } + + // Original-Methode als Wrapper beibehalten (damit onSignChange nicht geändert werden muss) + private void setInputChestLocation(UUID playerUUID, Location location) { + addInputChestLocation(playerUUID, location); + } + // ----------------------------------------------------------------- private void setTargetChestLocation(UUID playerUUID, Location location, Material itemType) { String path = "players." + playerUUID + ".target-chests." + itemType.name(); @@ -770,6 +820,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { loadPlayerData(); updateConfig(); updateExistingSigns(); + migrateInputChests(); // ERWEITERUNG: Migration erneut aufrufen player.sendMessage(getMessage("reload-success")); getLogger().info("Konfiguration und Spielerdaten neu geladen durch " + player.getName()); return true; @@ -835,7 +886,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { event.setLine(0, getSignColor("input", "line1") + "[asc]"); event.setLine(1, getSignColor("input", "line2") + "input"); event.setLine(3, getSignColor("input", "line4") + player.getName()); - setInputChestLocation(playerUUID, chestBlock.getLocation()); + setInputChestLocation(playerUUID, chestBlock.getLocation()); // Ruft nun addInputChestLocation auf player.sendMessage(getMessage("input-chest-set")); getLogger().info("Eingangstruhe für " + player.getName() + " gesetzt bei " + chestBlock.getLocation()); } @@ -1173,6 +1224,36 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { } } + // --- ERWEITERUNG: Hilfsmethode zum Entfernen einer Input Chest --- + private void removeInputChestByLocation(UUID uuid, Location loc) { + String basePath = "players." + uuid + ".input-chests"; + String oldPath = "players." + uuid + ".input-chest"; + + // Prüfe neues Listen-Format + if (playerData.contains(basePath)) { + for (String chestId : playerData.getConfigurationSection(basePath).getKeys(false)) { + String path = basePath + "." + chestId; + Location savedLoc = getLocationFromPath(path); + if (savedLoc != null && savedLoc.equals(loc)) { + playerData.set(path, null); + savePlayerData(); + if (isDebug()) getLogger().info("Eingangstruhe entfernt bei " + loc); + return; + } + } + } + + // Prüfe altes Einzel-Format (Fallback) + if (playerData.contains(oldPath)) { + Location oldLoc = getLocationFromPath(oldPath); + if (oldLoc != null && oldLoc.equals(loc)) { + playerData.set(oldPath, null); + savePlayerData(); + } + } + } + // --------------------------------------------------------------- + @EventHandler(priority = EventPriority.HIGHEST) public void onBlockBreak(BlockBreakEvent event) { Block block = event.getBlock(); @@ -1263,10 +1344,17 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { // --- Daten bereinigen beim Abbauen --- String[] lines = ((Sign) (isAscChest ? signBlock.getState() : block.getState())).getLines(); String line1 = ChatColor.stripColor(lines[1]); + + // Hole Location der Truhe, die abgebaut wird (für Datenabgleich) + Location chestLoc = isAscChest ? block.getLocation() : ((Sign)block.getState()).getBlock().getRelative(((WallSign)((Sign)block.getState()).getBlockData()).getFacing().getOppositeFace()).getLocation(); + if (line1.equalsIgnoreCase("rest")) { playerData.set("players." + player.getUniqueId() + ".rest-chest", null); savePlayerData(); if (isDebug()) getLogger().info("Rest-Truhe Daten gelöscht für " + player.getName()); + } else if (line1.equalsIgnoreCase("input")) { + // ERWEITERUNG: Input Chest aus Liste löschen + removeInputChestByLocation(player.getUniqueId(), chestLoc); } // -------------------------------- @@ -1553,108 +1641,127 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { continue; } - String path = "players." + uuidString + ".input-chest"; - if (!playerData.contains(path)) continue; + // --- ERWEITERUNG: Eingangstruhe (Jetzt Liste) --- + String inputListPath = "players." + uuidString + ".input-chests"; + // Fallback für altes Format (falls Migration noch nicht lief oder Fehler) + String oldInputPath = "players." + uuidString + ".input-chest"; - 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; - } + List chestsToCheck = new ArrayList<>(); - 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"); + if (playerData.contains(inputListPath)) { + chestsToCheck.addAll(playerData.getConfigurationSection(inputListPath).getKeys(false)); + } else if (playerData.contains(oldInputPath)) { + // Hier führen wir keine Migration durch (nur lesen), da das im laufenden Betrieb zu Problemen führen könnte. + // Wir markieren es für den Task, damit es bei下一次 Update erledigt wird. + Location loc = getLocationFromPath(oldInputPath); + if (loc != null) { + checkSingleInputChest(ownerUUID, loc, "legacy"); } continue; } - Chest chest = (Chest) location.getBlock().getState(); - Block inputSignBlock = null; - boolean isPublic = false; - String ownerName = "Unknown"; + if (chestsToCheck.isEmpty()) continue; - // FIX: Suche Schild an beiden Hälften bei Doppeltruhen - List chestBlocks = getChestBlocks(chest); - - 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) - }) { - if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, b)) { - String[] lines = sign.getLines(); - String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); - String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + for (String chestId : chestsToCheck) { + String path = inputListPath + "." + chestId; + Location loc = getLocationFromPath(path); + + if (loc == null) { + playerData.set(path, null); // Ungültigen Eintrag löschen + continue; + } - if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("input")) { - inputSignBlock = face; - String line3Raw = lines[3] != null ? lines[3] : ""; - String line3Clean = ChatColor.stripColor(line3Raw); + boolean stillValid = checkSingleInputChest(ownerUUID, loc, chestId); + if (!stillValid) { + playerData.set(path, null); + savePlayerData(); + } + } + // ------------------------------------------------- + } + } + + // --- ERWEITERUNG: Hilfsmethode zum Prüfen einzelner Eingangstruhe --- + private boolean checkSingleInputChest(UUID ownerUUID, Location location, String debugId) { + if (!(location.getBlock().getState() instanceof Chest)) return false; - isPublic = line3Clean.toLowerCase().endsWith("[public]"); - ownerName = line3Clean.replace(" [Public]", "").replace(" [public]", "").trim(); - break outerLoop; - } + Chest chest = (Chest) location.getBlock().getState(); + Block inputSignBlock = null; + boolean isPublic = false; + String ownerName = "Unknown"; + + // FIX: Suche Schild an beiden Hälften bei Doppeltruhen + List chestBlocks = getChestBlocks(chest); + + 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) + }) { + if (face.getState() instanceof Sign sign && isSignAttachedToChest(face, b)) { + String[] lines = sign.getLines(); + String line0 = ChatColor.stripColor((String) (lines[0] != null ? lines[0] : "")); + String line1 = ChatColor.stripColor((String) (lines[1] != null ? lines[1] : "")); + + if (line0.equalsIgnoreCase("[asc]") && line1.equalsIgnoreCase("input")) { + 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 outerLoop; } } } - - 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 distributeItemsForOwner weiß, dass niemand Besitzer ist (für Messages). - if (ownerPlayer == null || !ownerPlayer.isOnline()) { - ownerPlayer = null; - } - } - - // Wir rufen distributeItemsForOwner auf. - // WICHTIG: distributeItemsForOwner 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 `distributeItemsForOwner` nicht crasht, wenn player null ist. - - distributeItemsForOwner(ownerUUID, ownerPlayer, chest.getInventory()); } + + if (inputSignBlock == null) { + if (isDebug()) getLogger().fine("Kein Eingangsschild an Truhe " + debugId); + return false; + } + + // 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) return true; + + // 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()) return true; + } 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 distributeItemsForOwner weiß, dass niemand Besitzer ist (für Messages). + if (ownerPlayer == null || !ownerPlayer.isOnline()) { + ownerPlayer = null; + } + } + + // Wir rufen distributeItemsForOwner auf. + distributeItemsForOwner(ownerUUID, ownerPlayer, chest.getInventory(), ownerName); + return true; } + // ---------------------------------------------------------------------- // 4. KORRIGIERTE distributeItemsForOwner Methode: - private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory) { + private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory, String ownerNameOverride) { boolean hasItems = false; for (ItemStack item : sourceInventory.getContents()) { if (item != null && item.getType() != Material.AIR) { @@ -1666,7 +1773,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor { // Owner-Name ermitteln String ownerName = "Unknown"; - if (ownerPlayer != null) { + if (ownerNameOverride != null && !ownerNameOverride.isEmpty()) { + ownerName = ownerNameOverride; + } else if (ownerPlayer != null) { ownerName = ownerPlayer.getName(); } else { // Offline-Namen aus PlayerData holen wenn möglich