Update from Git Manager GUI

This commit is contained in:
2026-02-25 19:02:12 +01:00
parent c61c2dc8b9
commit 62e5c3d7f1
4 changed files with 628 additions and 196 deletions

View File

@@ -222,7 +222,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
private final Map<String, Long> fullChestLocationCache = new HashMap<>(); private final Map<String, Long> fullChestLocationCache = new HashMap<>();
private static final long FULL_CHEST_CACHE_DURATION = 10_000L; // 10 Sekunden private static final long FULL_CHEST_CACHE_DURATION = 10_000L; // 10 Sekunden
private static final String CONFIG_VERSION = "2.1"; // BungeeCord NEU: 2.0 → 2.1 private static final String CONFIG_VERSION = "2.2"; // Multi-Rest: 2.1 → 2.2
private boolean updateAvailable = false; private boolean updateAvailable = false;
private String latestVersion = ""; private String latestVersion = "";
@@ -343,14 +343,29 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
mysqlManager.setTargetChest(uuidString, item, world, x, y, z, isPublic); mysqlManager.setTargetChest(uuidString, item, world, x, y, z, isPublic);
} }
} }
String restPath = "players." + uuidString + ".rest-chest"; // BUG FIX: Mehrere Rest-Truhen migrieren (neues Format + Legacy)
if (playerData.contains(restPath)) { String restBasePath = "players." + uuidString + ".rest-chests";
String world = playerData.getString(restPath + ".world"); if (playerData.contains(restBasePath)) {
int x = playerData.getInt(restPath + ".x"); for (String slotKey : playerData.getConfigurationSection(restBasePath).getKeys(false)) {
int y = playerData.getInt(restPath + ".y"); String rPath = restBasePath + "." + slotKey;
int z = playerData.getInt(restPath + ".z"); String world = playerData.getString(rPath + ".world");
boolean isPublic = playerData.getBoolean(restPath + ".public", false); int x = playerData.getInt(rPath + ".x");
mysqlManager.setRestChest(uuidString, world, x, y, z, isPublic); int y = playerData.getInt(rPath + ".y");
int z = playerData.getInt(rPath + ".z");
boolean isPublic = playerData.getBoolean(rPath + ".public", false);
mysqlManager.setRestChest(uuidString, world, x, y, z, isPublic);
}
} else {
// Legacy: altes single rest-chest Format
String restPath = "players." + uuidString + ".rest-chest";
if (playerData.contains(restPath)) {
String world = playerData.getString(restPath + ".world");
int x = playerData.getInt(restPath + ".x");
int y = playerData.getInt(restPath + ".y");
int z = playerData.getInt(restPath + ".z");
boolean isPublic = playerData.getBoolean(restPath + ".public", false);
mysqlManager.setRestChest(uuidString, world, x, y, z, isPublic);
}
} }
} }
getLogger().info("Migration der YAML-Daten nach MySQL abgeschlossen."); getLogger().info("Migration der YAML-Daten nach MySQL abgeschlossen.");
@@ -405,9 +420,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
exportData.set(path + ".public", chest.get("public")); exportData.set(path + ".public", chest.get("public"));
} }
Map<String, Object> restChest = mysqlManager.getRestChest(uuidString); // BUG FIX: Alle Rest-Truhen exportieren
if (restChest != null) { List<Map<String, Object>> restChests = mysqlManager.getRestChests(uuidString);
String path = "players." + uuidString + ".rest-chest"; for (Map<String, Object> restChest : restChests) {
int restSlot = (int) restChest.get("slot");
String path = "players." + uuidString + ".rest-chests." + restSlot;
exportData.set(path + ".world", restChest.get("world")); exportData.set(path + ".world", restChest.get("world"));
exportData.set(path + ".x", restChest.get("x")); exportData.set(path + ".x", restChest.get("x"));
exportData.set(path + ".y", restChest.get("y")); exportData.set(path + ".y", restChest.get("y"));
@@ -689,10 +706,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
if (!config.contains("messages.rest-chest-set")) config.set("messages.rest-chest-set", defaultConfig.getString("messages.rest-chest-set", "&aRest-Truhe (Fallback) erfolgreich gesetzt!")); if (!config.contains("messages.rest-chest-set")) config.set("messages.rest-chest-set", defaultConfig.getString("messages.rest-chest-set", "&aRest-Truhe (Fallback) erfolgreich gesetzt!"));
if (!config.contains("messages.target-chest-missing")) config.set("messages.target-chest-missing", defaultConfig.getString("messages.target-chest-missing", "&cZieltruhe für %item% fehlt!")); if (!config.contains("messages.target-chest-missing")) config.set("messages.target-chest-missing", defaultConfig.getString("messages.target-chest-missing", "&cZieltruhe für %item% fehlt!"));
String targetChestFull = config.getString("messages.target-chest-full", ""); if (!config.contains("messages.target-chest-full")) {
String defaultTargetChestFull = defaultConfig.getString("messages.target-chest-full", "&cZieltruhe für %item% ist voll! Koordinaten: (%x%, %y%, %z%)"); config.set("messages.target-chest-full", defaultConfig.getString("messages.target-chest-full", "&cZieltruhe für %item% ist voll! Koordinaten: (%x%, %y%, %z%)"));
if (!config.contains("messages.target-chest-full") || !targetChestFull.equals(defaultTargetChestFull)) {
config.set("messages.target-chest-full", defaultTargetChestFull);
} }
if (!config.contains("messages.mode-changed")) config.set("messages.mode-changed", defaultConfig.getString("messages.mode-changed", "&aModus gewechselt: &e%mode%")); if (!config.contains("messages.mode-changed")) config.set("messages.mode-changed", defaultConfig.getString("messages.mode-changed", "&aModus gewechselt: &e%mode%"));
@@ -825,9 +840,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
} }
String restPath = basePath + ".rest-chest"; // BUG FIX: Alle Rest-Truhen iterieren (neues Multi-Format + Legacy)
if (playerData.contains(restPath)) { List<Location> restLocations = getRestChestLocations(playerUUID);
Location chestLocation = getLocationFromPath(restPath); for (Location chestLocation : restLocations) {
if (chestLocation == null) continue; if (chestLocation == null) continue;
Block chestBlock = chestLocation.getBlock(); Block chestBlock = chestLocation.getBlock();
if (!(chestBlock.getState() instanceof Chest)) continue; if (!(chestBlock.getState() instanceof Chest)) continue;
@@ -928,13 +943,27 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
} }
} }
String restPath = "players." + uuid + ".rest-chest"; // BUG FIX: Neues Multi-Format für Rest-Truhen
if (playerData.contains(restPath + ".world")) { String restBasePath = "players." + uuid + ".rest-chests";
if (world.equals(playerData.getString(restPath + ".world")) if (playerData.contains(restBasePath)) {
&& x == playerData.getInt(restPath + ".x") for (String slotKey : playerData.getConfigurationSection(restBasePath).getKeys(false)) {
&& y == playerData.getInt(restPath + ".y") String rPath = restBasePath + "." + slotKey;
&& z == playerData.getInt(restPath + ".z")) { if (world.equals(playerData.getString(rPath + ".world"))
if (playerData.getBoolean(restPath + ".public", false)) return true; && x == playerData.getInt(rPath + ".x")
&& y == playerData.getInt(rPath + ".y")
&& z == playerData.getInt(rPath + ".z")) {
if (playerData.getBoolean(rPath + ".public", false)) return true;
}
}
}
// Legacy
String legacyRestPath = "players." + uuid + ".rest-chest";
if (playerData.contains(legacyRestPath + ".world")) {
if (world.equals(playerData.getString(legacyRestPath + ".world"))
&& x == playerData.getInt(legacyRestPath + ".x")
&& y == playerData.getInt(legacyRestPath + ".y")
&& z == playerData.getInt(legacyRestPath + ".z")) {
if (playerData.getBoolean(legacyRestPath + ".public", false)) return true;
} }
} }
} }
@@ -987,7 +1016,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
String chestId = null; String chestId = null;
List<Map<String, Object>> chests = mysqlManager.getInputChests(playerUUID.toString()); List<Map<String, Object>> chests = mysqlManager.getInputChests(playerUUID.toString());
for (Map<String, Object> chest : chests) { for (Map<String, Object> chest : chests) {
if (chest.get("world").equals(location.getWorld().getName()) String chestWorld = (String) chest.get("world");
if (chestWorld == null) continue; // NPE-Schutz: korrupter DB-Eintrag
if (chestWorld.equals(location.getWorld().getName())
&& ((int) chest.get("x")) == location.getBlockX() && ((int) chest.get("x")) == location.getBlockX()
&& ((int) chest.get("y")) == location.getBlockY() && ((int) chest.get("y")) == location.getBlockY()
&& ((int) chest.get("z")) == location.getBlockZ()) { && ((int) chest.get("z")) == location.getBlockZ()) {
@@ -1075,13 +1106,34 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
private void setRestChestLocation(UUID playerUUID, Location location, boolean isPublic) { private void setRestChestLocation(UUID playerUUID, Location location, boolean isPublic) {
if (mysqlEnabled && mysqlManager != null) { if (mysqlEnabled && mysqlManager != null) {
// ── BungeeCord NEU: serverName mitgeben ─────────────────────────── // ── BungeeCord + Multi-Rest: serverName und auto-slot ─────────────────
mysqlManager.setRestChest(playerUUID.toString(), mysqlManager.setRestChest(playerUUID.toString(),
location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ(), location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ(),
isPublic, serverName); isPublic, serverName);
mysqlManager.savePlayer(playerUUID.toString(), Bukkit.getOfflinePlayer(playerUUID).getName()); mysqlManager.savePlayer(playerUUID.toString(), Bukkit.getOfflinePlayer(playerUUID).getName());
} else { } else {
String path = "players." + playerUUID + ".rest-chest"; // YAML: mehrere Rest-Truhen unter rest-chests.<slot>
String basePath = "players." + playerUUID + ".rest-chests";
// Prüfen ob diese Location schon registriert ist (Update)
if (playerData.contains(basePath)) {
for (String slotKey : playerData.getConfigurationSection(basePath).getKeys(false)) {
String path = basePath + "." + slotKey;
if (location.getWorld().getName().equals(playerData.getString(path + ".world"))
&& location.getBlockX() == playerData.getInt(path + ".x")
&& location.getBlockY() == playerData.getInt(path + ".y")
&& location.getBlockZ() == playerData.getInt(path + ".z")) {
playerData.set(path + ".public", isPublic);
savePlayerData();
return;
}
}
}
// Neuen Slot anlegen
int nextSlot = 0;
if (playerData.contains(basePath)) {
nextSlot = playerData.getConfigurationSection(basePath).getKeys(false).size();
}
String path = basePath + "." + nextSlot;
playerData.set(path + ".world", location.getWorld().getName()); playerData.set(path + ".world", location.getWorld().getName());
playerData.set(path + ".x", location.getBlockX()); playerData.set(path + ".x", location.getBlockX());
playerData.set(path + ".y", location.getBlockY()); playerData.set(path + ".y", location.getBlockY());
@@ -1091,23 +1143,48 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
} }
private Location getRestChestLocation(UUID playerUUID) { /** Gibt alle Rest-Truhen-Locations zurück (für mehrere Rest-Truhen). */
private List<Location> getRestChestLocations(UUID playerUUID) {
List<Location> result = new ArrayList<>();
if (mysqlEnabled && mysqlManager != null) { if (mysqlEnabled && mysqlManager != null) {
Map<String, Object> map = mysqlManager.getRestChest(playerUUID.toString()); List<Map<String, Object>> maps = mysqlManager.getRestChests(playerUUID.toString());
if (map == null) return null; for (Map<String, Object> map : maps) {
// ── BungeeCord NEU: Remote-Truhen nicht lokal auflösen ───────────── if (isRemoteChest(map)) continue;
if (isRemoteChest(map)) return null; World w = Bukkit.getWorld((String) map.get("world"));
World w = Bukkit.getWorld((String) map.get("world")); if (w == null) continue;
if (w == null) return null; result.add(new Location(w, (int) map.get("x"), (int) map.get("y"), (int) map.get("z")));
return new Location(w, (int) map.get("x"), (int) map.get("y"), (int) map.get("z")); }
} else { } else {
String path = "players." + playerUUID + ".rest-chest"; // Neues Multi-Format
if (!playerData.contains(path)) return null; String basePath = "players." + playerUUID + ".rest-chests";
String worldName = playerData.getString(path + ".world"); if (playerData.contains(basePath)) {
World world = getServer().getWorld(worldName); for (String slotKey : playerData.getConfigurationSection(basePath).getKeys(false)) {
if (world == null) return null; String path = basePath + "." + slotKey;
return new Location(world, playerData.getInt(path + ".x"), playerData.getInt(path + ".y"), playerData.getInt(path + ".z")); String worldName = playerData.getString(path + ".world");
World world = getServer().getWorld(worldName);
if (world == null) continue;
result.add(new Location(world, playerData.getInt(path + ".x"),
playerData.getInt(path + ".y"), playerData.getInt(path + ".z")));
}
}
// Legacy-Fallback: altes single rest-chest Format
String legacyPath = "players." + playerUUID + ".rest-chest";
if (result.isEmpty() && playerData.contains(legacyPath)) {
String worldName = playerData.getString(legacyPath + ".world");
World world = getServer().getWorld(worldName);
if (world != null) {
result.add(new Location(world, playerData.getInt(legacyPath + ".x"),
playerData.getInt(legacyPath + ".y"), playerData.getInt(legacyPath + ".z")));
}
}
} }
return result;
}
/** Legacy: Gibt die erste Rest-Truhe zurück (oder null). */
private Location getRestChestLocation(UUID playerUUID) {
List<Location> locs = getRestChestLocations(playerUUID);
return locs.isEmpty() ? null : locs.get(0);
} }
private Location getTargetChestLocation(UUID playerUUID, Material itemType) { private Location getTargetChestLocation(UUID playerUUID, Material itemType) {
@@ -1171,15 +1248,25 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!command.getName().equalsIgnoreCase("asc")) return false; if (!command.getName().equalsIgnoreCase("asc")) return false;
if (!(sender instanceof Player)) {
sender.sendMessage(ChatColor.RED + "Dieser Befehl ist nur für Spieler!"); String lang = config != null ? config.getString("language", "de") : "de";
return true;
}
Player player = (Player) sender;
String lang = config.getString("language", "de");
if (lang == null) lang = "de"; if (lang == null) lang = "de";
// reload, import und export sind auch von der Konsole erlaubt
boolean isPlayer = sender instanceof Player;
if (!isPlayer) {
if (args.length == 0 || (!args[0].equalsIgnoreCase("reload")
&& !args[0].equalsIgnoreCase("import")
&& !args[0].equalsIgnoreCase("export"))) {
sender.sendMessage(ChatColor.RED + "Dieser Befehl ist nur für Spieler! (Konsole: reload, import, export)");
return true;
}
}
Player player = isPlayer ? (Player) sender : null;
if (args.length == 0 || args[0].equalsIgnoreCase("help")) { if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
if (player == null) { sender.sendMessage(ChatColor.RED + "Verwendung: /asc [reload|import|export]"); return true; }
String helpMessage = lang.equalsIgnoreCase("en") ? HELP_EN : HELP_DE; String helpMessage = lang.equalsIgnoreCase("en") ? HELP_EN : HELP_DE;
helpMessage = ChatColor.translateAlternateColorCodes('&', helpMessage); helpMessage = ChatColor.translateAlternateColorCodes('&', helpMessage);
player.sendMessage(helpMessage.split("\n")); player.sendMessage(helpMessage.split("\n"));
@@ -1194,13 +1281,13 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
.replace("%server_name%", serverName.isEmpty() ? "(nicht gesetzt)" : serverName) // BungeeCord NEU .replace("%server_name%", serverName.isEmpty() ? "(nicht gesetzt)" : serverName) // BungeeCord NEU
.replace("%author%", String.join(", ", getDescription().getAuthors())); .replace("%author%", String.join(", ", getDescription().getAuthors()));
infoMessage = ChatColor.translateAlternateColorCodes('&', infoMessage); infoMessage = ChatColor.translateAlternateColorCodes('&', infoMessage);
player.sendMessage(infoMessage.split("\n")); sender.sendMessage(infoMessage.split("\n"));
return true; return true;
} }
if (args[0].equalsIgnoreCase("reload")) { if (args[0].equalsIgnoreCase("reload")) {
if (!player.hasPermission("autosortchest.reload")) { if (!sender.hasPermission("autosortchest.reload")) {
player.sendMessage(getMessage("no-permission")); sender.sendMessage(getMessage("no-permission"));
return true; return true;
} }
@@ -1240,8 +1327,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
migrateInputChests(); migrateInputChests();
startTasks(); startTasks();
player.sendMessage(getMessage("reload-success")); sender.sendMessage(getMessage("reload-success"));
getLogger().info("Plugin erfolgreich neu geladen von " + player.getName() getLogger().info("Plugin erfolgreich neu geladen von " + sender.getName()
+ " (sort_interval_ticks=" + sortIntervalTicks + ", server_name=\"" + serverName + "\")"); + " (sort_interval_ticks=" + sortIntervalTicks + ", server_name=\"" + serverName + "\")");
return true; return true;
} }
@@ -1250,22 +1337,22 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
// /asc import YAML → MySQL // /asc import YAML → MySQL
// ------------------------------------------------------- // -------------------------------------------------------
if (args[0].equalsIgnoreCase("import")) { if (args[0].equalsIgnoreCase("import")) {
if (!player.hasPermission("autosortchest.import")) { if (!sender.hasPermission("autosortchest.import")) {
player.sendMessage(getMessage("no-permission")); sender.sendMessage(getMessage("no-permission"));
return true; return true;
} }
if (!mysqlEnabled || mysqlManager == null) { if (!mysqlEnabled || mysqlManager == null) {
player.sendMessage(ChatColor.RED + "MySQL ist nicht aktiviert! Aktiviere MySQL in der config.yml zuerst."); sender.sendMessage(ChatColor.RED + "MySQL ist nicht aktiviert! Aktiviere MySQL in der config.yml zuerst.");
return true; return true;
} }
if (playerData == null || playerData.getConfigurationSection("players") == null if (playerData == null || playerData.getConfigurationSection("players") == null
|| playerData.getConfigurationSection("players").getKeys(false).isEmpty()) { || playerData.getConfigurationSection("players").getKeys(false).isEmpty()) {
player.sendMessage(ChatColor.RED + "Die players.yml ist leer oder enthält keine Spielerdaten!"); sender.sendMessage(ChatColor.RED + "Die players.yml ist leer oder enthält keine Spielerdaten!");
return true; return true;
} }
player.sendMessage(ChatColor.YELLOW + "Importiere Daten aus players.yml nach MySQL..."); sender.sendMessage(ChatColor.YELLOW + "Importiere Daten aus players.yml nach MySQL...");
player.sendMessage(ChatColor.GRAY + "Bestehende MySQL-Daten werden nicht überschrieben (REPLACE INTO)."); sender.sendMessage(ChatColor.GRAY + "Bestehende MySQL-Daten werden nicht überschrieben (REPLACE INTO).");
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
@@ -1311,26 +1398,41 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
} }
String restPath = "players." + uuidString + ".rest-chest"; // BUG FIX: Mehrere Rest-Truhen importieren (neues Format + Legacy)
if (playerData.contains(restPath + ".world")) { String restBasePath2 = "players." + uuidString + ".rest-chests";
String world = playerData.getString(restPath + ".world"); if (playerData.contains(restBasePath2)) {
int x = playerData.getInt(restPath + ".x"); for (String slotKey : playerData.getConfigurationSection(restBasePath2).getKeys(false)) {
int y = playerData.getInt(restPath + ".y"); String rPath = restBasePath2 + "." + slotKey;
int z = playerData.getInt(restPath + ".z"); String world = playerData.getString(rPath + ".world");
boolean isPublic = playerData.getBoolean(restPath + ".public", false); int x = playerData.getInt(rPath + ".x");
mysqlManager.setRestChest(uuidString, world, x, y, z, isPublic); int y = playerData.getInt(rPath + ".y");
restCount++; int z = playerData.getInt(rPath + ".z");
boolean isPublic = playerData.getBoolean(rPath + ".public", false);
mysqlManager.setRestChest(uuidString, world, x, y, z, isPublic);
restCount++;
}
} else {
String restPath = "players." + uuidString + ".rest-chest";
if (playerData.contains(restPath + ".world")) {
String world = playerData.getString(restPath + ".world");
int x = playerData.getInt(restPath + ".x");
int y = playerData.getInt(restPath + ".y");
int z = playerData.getInt(restPath + ".z");
boolean isPublic = playerData.getBoolean(restPath + ".public", false);
mysqlManager.setRestChest(uuidString, world, x, y, z, isPublic);
restCount++;
}
} }
} }
final int fp = playerCount, fi = inputCount, ft = targetCount, fr = restCount; final int fp = playerCount, fi = inputCount, ft = targetCount, fr = restCount;
Bukkit.getScheduler().runTask(Main.this, () -> { Bukkit.getScheduler().runTask(Main.this, () -> {
player.sendMessage(ChatColor.GREEN + "Import erfolgreich abgeschlossen!"); sender.sendMessage(ChatColor.GREEN + "Import erfolgreich abgeschlossen!");
player.sendMessage(ChatColor.GRAY + " Spieler: " + ChatColor.WHITE + fp); sender.sendMessage(ChatColor.GRAY + " Spieler: " + ChatColor.WHITE + fp);
player.sendMessage(ChatColor.GRAY + " Eingangstruhen: " + ChatColor.WHITE + fi); sender.sendMessage(ChatColor.GRAY + " Eingangstruhen: " + ChatColor.WHITE + fi);
player.sendMessage(ChatColor.GRAY + " Zieltruhen: " + ChatColor.WHITE + ft); sender.sendMessage(ChatColor.GRAY + " Zieltruhen: " + ChatColor.WHITE + ft);
player.sendMessage(ChatColor.GRAY + " Rest-Truhen: " + ChatColor.WHITE + fr); sender.sendMessage(ChatColor.GRAY + " Rest-Truhen: " + ChatColor.WHITE + fr);
getLogger().info("Import durch " + player.getName() + " abgeschlossen: " getLogger().info("Import durch " + sender.getName() + " abgeschlossen: "
+ fp + " Spieler, " + fi + " Input, " + ft + " Target, " + fr + " Rest."); + fp + " Spieler, " + fi + " Input, " + ft + " Target, " + fr + " Rest.");
}); });
} }
@@ -1342,17 +1444,17 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
// /asc export MySQL → YAML // /asc export MySQL → YAML
// ------------------------------------------------------- // -------------------------------------------------------
if (args[0].equalsIgnoreCase("export")) { if (args[0].equalsIgnoreCase("export")) {
if (!player.hasPermission("autosortchest.export")) { if (!sender.hasPermission("autosortchest.export")) {
player.sendMessage(getMessage("no-permission")); sender.sendMessage(getMessage("no-permission"));
return true; return true;
} }
if (!mysqlEnabled || mysqlManager == null) { if (!mysqlEnabled || mysqlManager == null) {
player.sendMessage(ChatColor.RED + "MySQL ist nicht aktiviert! Der Export benötigt eine aktive MySQL-Verbindung."); sender.sendMessage(ChatColor.RED + "MySQL ist nicht aktiviert! Der Export benötigt eine aktive MySQL-Verbindung.");
return true; return true;
} }
player.sendMessage(ChatColor.YELLOW + "Exportiere Daten aus MySQL nach players.yml..."); sender.sendMessage(ChatColor.YELLOW + "Exportiere Daten aus MySQL nach players.yml...");
player.sendMessage(ChatColor.GRAY + "Ein Backup der aktuellen players.yml wird erstellt."); sender.sendMessage(ChatColor.GRAY + "Ein Backup der aktuellen players.yml wird erstellt.");
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
@@ -1370,7 +1472,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
java.nio.file.Files.copy(playerDataFile.toPath(), backupFile.toPath()); java.nio.file.Files.copy(playerDataFile.toPath(), backupFile.toPath());
} catch (IOException e) { } catch (IOException e) {
Bukkit.getScheduler().runTask(Main.this, () -> Bukkit.getScheduler().runTask(Main.this, () ->
player.sendMessage(ChatColor.RED + "Backup fehlgeschlagen: " + e.getMessage())); sender.sendMessage(ChatColor.RED + "Backup fehlgeschlagen: " + e.getMessage()));
return; return;
} }
} }
@@ -1410,9 +1512,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
targetCount++; targetCount++;
} }
Map<String, Object> restChest = mysqlManager.getRestChest(uuidString); // BUG FIX: Alle Rest-Truhen exportieren
if (restChest != null) { List<Map<String, Object>> restChests = mysqlManager.getRestChests(uuidString);
String path = "players." + uuidString + ".rest-chest"; for (Map<String, Object> restChest : restChests) {
int restSlot = (int) restChest.get("slot");
String path = "players." + uuidString + ".rest-chests." + restSlot;
exportData.set(path + ".world", restChest.get("world")); exportData.set(path + ".world", restChest.get("world"));
exportData.set(path + ".x", restChest.get("x")); exportData.set(path + ".x", restChest.get("x"));
exportData.set(path + ".y", restChest.get("y")); exportData.set(path + ".y", restChest.get("y"));
@@ -1430,24 +1534,24 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
Bukkit.getScheduler().runTask(Main.this, () -> { Bukkit.getScheduler().runTask(Main.this, () -> {
playerData = finalExport; playerData = finalExport;
player.sendMessage(ChatColor.GREEN + "Export erfolgreich abgeschlossen!"); sender.sendMessage(ChatColor.GREEN + "Export erfolgreich abgeschlossen!");
if (finalBackupName != null) { if (finalBackupName != null) {
player.sendMessage(ChatColor.GRAY + " Backup: " + ChatColor.WHITE + finalBackupName); sender.sendMessage(ChatColor.GRAY + " Backup: " + ChatColor.WHITE + finalBackupName);
} else { } else {
player.sendMessage(ChatColor.GRAY + " Backup: " + ChatColor.DARK_GRAY + "Übersprungen (players.yml war leer)"); sender.sendMessage(ChatColor.GRAY + " Backup: " + ChatColor.DARK_GRAY + "Übersprungen (players.yml war leer)");
} }
player.sendMessage(ChatColor.GRAY + " Spieler: " + ChatColor.WHITE + fp); sender.sendMessage(ChatColor.GRAY + " Spieler: " + ChatColor.WHITE + fp);
player.sendMessage(ChatColor.GRAY + " Eingangstruhen: " + ChatColor.WHITE + fi); sender.sendMessage(ChatColor.GRAY + " Eingangstruhen: " + ChatColor.WHITE + fi);
player.sendMessage(ChatColor.GRAY + " Zieltruhen: " + ChatColor.WHITE + ft); sender.sendMessage(ChatColor.GRAY + " Zieltruhen: " + ChatColor.WHITE + ft);
player.sendMessage(ChatColor.GRAY + " Rest-Truhen: " + ChatColor.WHITE + fr); sender.sendMessage(ChatColor.GRAY + " Rest-Truhen: " + ChatColor.WHITE + fr);
getLogger().info("Export durch " + player.getName() + " abgeschlossen: " getLogger().info("Export durch " + sender.getName() + " abgeschlossen: "
+ fp + " Spieler, " + fi + " Input, " + ft + " Target, " + fr + " Rest." + fp + " Spieler, " + fi + " Input, " + ft + " Target, " + fr + " Rest."
+ (finalBackupName != null ? " Backup: " + finalBackupName : " Kein Backup.")); + (finalBackupName != null ? " Backup: " + finalBackupName : " Kein Backup."));
}); });
} catch (Exception e) { } catch (Exception e) {
Bukkit.getScheduler().runTask(Main.this, () -> Bukkit.getScheduler().runTask(Main.this, () ->
player.sendMessage(ChatColor.RED + "Export fehlgeschlagen: " + e.getMessage())); sender.sendMessage(ChatColor.RED + "Export fehlgeschlagen: " + e.getMessage()));
getLogger().warning("Export fehlgeschlagen: " + e.getMessage()); getLogger().warning("Export fehlgeschlagen: " + e.getMessage());
} }
} }
@@ -1455,6 +1559,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
return true; return true;
} }
// Unbekannter Befehl → Hilfe (nur für Spieler)
if (player == null) {
sender.sendMessage(ChatColor.RED + "Verwendung: /asc [reload|import|export]");
return true;
}
String helpMessage = lang.equalsIgnoreCase("en") ? HELP_EN : HELP_DE; String helpMessage = lang.equalsIgnoreCase("en") ? HELP_EN : HELP_DE;
helpMessage = ChatColor.translateAlternateColorCodes('&', helpMessage); helpMessage = ChatColor.translateAlternateColorCodes('&', helpMessage);
player.sendMessage(helpMessage.split("\n")); player.sendMessage(helpMessage.split("\n"));
@@ -1486,14 +1595,40 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
// ── Limit-Pruefung: Rest-Truhe ───────────────────────────────────── // ── Limit-Pruefung: Rest-Truhe ─────────────────────────────────────
if (chestLimitsEnabled) { if (chestLimitsEnabled) {
int maxRest = getChestLimitForPlayer(player, "rest"); int maxRest = getChestLimitForPlayer(player, "rest");
boolean hasRestAlready = false; int currentRest = 0;
final Block finalChestBlockRest = chestBlock;
boolean alreadyRest = false;
if (mysqlEnabled && mysqlManager != null) { if (mysqlEnabled && mysqlManager != null) {
hasRestAlready = mysqlManager.getRestChest(playerUUID.toString()) != null; currentRest = mysqlManager.countRestChests(playerUUID.toString());
// Prüfen ob diese Truhe bereits als Rest-Truhe registriert ist (Update erlaubt)
alreadyRest = mysqlManager.getRestSlotForLocation(
playerUUID.toString(),
finalChestBlockRest.getWorld().getName(),
finalChestBlockRest.getX(),
finalChestBlockRest.getY(),
finalChestBlockRest.getZ()) >= 0;
} else { } else {
hasRestAlready = playerData.contains("players." + playerUUID + ".rest-chest"); String basePath = "players." + playerUUID + ".rest-chests";
if (playerData.contains(basePath)) {
for (String slotKey : playerData.getConfigurationSection(basePath).getKeys(false)) {
String rPath = basePath + "." + slotKey;
if (finalChestBlockRest.getWorld().getName().equals(playerData.getString(rPath + ".world"))
&& finalChestBlockRest.getX() == playerData.getInt(rPath + ".x")
&& finalChestBlockRest.getY() == playerData.getInt(rPath + ".y")
&& finalChestBlockRest.getZ() == playerData.getInt(rPath + ".z")) {
alreadyRest = true;
break;
}
}
if (!alreadyRest) currentRest = playerData.getConfigurationSection(basePath).getKeys(false).size();
}
// Legacy fallback
if (!alreadyRest && currentRest == 0 && playerData.contains("players." + playerUUID + ".rest-chest")) {
currentRest = 1;
}
} }
if (hasRestAlready && maxRest <= 1) { if (!alreadyRest && currentRest >= maxRest) {
player.sendMessage(ChatColor.RED + "Du hast bereits eine Rest-Truhe! (Limit: " + maxRest + ")"); player.sendMessage(ChatColor.RED + "Du hast das Limit deiner Rest-Truhen erreicht! (" + maxRest + ")");
event.setCancelled(true); event.setCancelled(true);
return; return;
} }
@@ -1530,11 +1665,14 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
final Block finalChestBlock = chestBlock; final Block finalChestBlock = chestBlock;
boolean alreadyInput = false; boolean alreadyInput = false;
if (mysqlEnabled && mysqlManager != null) { if (mysqlEnabled && mysqlManager != null) {
alreadyInput = mysqlManager.getInputChests(playerUUID.toString()).stream().anyMatch(c -> alreadyInput = mysqlManager.getInputChests(playerUUID.toString()).stream().anyMatch(c -> {
c.get("world").equals(finalChestBlock.getWorld().getName()) String cWorld = (String) c.get("world");
&& (int)c.get("x") == finalChestBlock.getX() return cWorld != null
&& (int)c.get("y") == finalChestBlock.getY() && cWorld.equals(finalChestBlock.getWorld().getName())
&& (int)c.get("z") == finalChestBlock.getZ()); && (int)c.get("x") == finalChestBlock.getX()
&& (int)c.get("y") == finalChestBlock.getY()
&& (int)c.get("z") == finalChestBlock.getZ();
});
} }
if (!alreadyInput && currentInput >= maxInput) { if (!alreadyInput && currentInput >= maxInput) {
player.sendMessage(ChatColor.RED + "Du hast das Limit deiner Eingangstruhen erreicht! (" + maxInput + ")"); player.sendMessage(ChatColor.RED + "Du hast das Limit deiner Eingangstruhen erreicht! (" + maxInput + ")");
@@ -1564,8 +1702,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
if (attachedBlock.getState() instanceof Chest) chestBlock = attachedBlock; if (attachedBlock.getState() instanceof Chest) chestBlock = attachedBlock;
} }
if (chestBlock == null) { player.sendMessage(getMessage("no-chest-near-sign")); return; } if (chestBlock == null) { player.sendMessage(getMessage("no-chest-near-sign")); return; }
event.setLine(0, "[asc]"); event.setLine(0, getSignColor("target", "line1") + "[asc]");
event.setLine(1, "ziel"); event.setLine(1, getSignColor("target", "line2") + "ziel");
event.setLine(2, ""); event.setLine(2, "");
event.setLine(3, ""); event.setLine(3, "");
} }
@@ -2017,7 +2155,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
event.setCancelled(true); event.setCancelled(true);
return; return;
} }
if (!player.isSneaking() && !isAdmin(player)) { // BUG FIX: autosortchest.bypass erlaubt Abbau ohne Shift-Taste
if (!player.isSneaking() && !isAdmin(player) && !player.hasPermission("autosortchest.bypass")) {
player.sendMessage(getMessage("sign-break-denied")); player.sendMessage(getMessage("sign-break-denied"));
event.setCancelled(true); event.setCancelled(true);
return; return;
@@ -2052,8 +2191,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
} }
if (ownerUUID == null) { if (ownerUUID == null) {
Location restLoc = getRestChestLocation(uuid); // BUG FIX: Alle Rest-Truhen des Spielers prüfen (nicht nur erste)
if (chestLoc.equals(restLoc)) ownerUUID = uuid; for (Location restLoc : getRestChestLocations(uuid)) {
if (chestLoc.equals(restLoc)) { ownerUUID = uuid; break; }
}
} }
if (ownerUUID != null) break; if (ownerUUID != null) break;
} }
@@ -2062,9 +2203,33 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
UUID uuidToDelete = (ownerUUID != null) ? ownerUUID : player.getUniqueId(); UUID uuidToDelete = (ownerUUID != null) ? ownerUUID : player.getUniqueId();
if (line1.equalsIgnoreCase("rest")) { if (line1.equalsIgnoreCase("rest")) {
playerData.set("players." + uuidToDelete + ".rest-chest", null); if (mysqlEnabled && mysqlManager != null) {
savePlayerData(); // BUG FIX: Nur die spezifische Location löschen, nicht alle Rest-Truhen
if (mysqlEnabled && mysqlManager != null) mysqlManager.removeRestChest(uuidToDelete.toString()); mysqlManager.removeRestChestByLocation(uuidToDelete.toString(),
chestLoc.getWorld().getName(), chestLoc.getBlockX(), chestLoc.getBlockY(), chestLoc.getBlockZ());
} else {
// YAML: Spezifische Location aus rest-chests entfernen
boolean removedFromNew = false;
String basePath = "players." + uuidToDelete + ".rest-chests";
if (playerData.contains(basePath)) {
for (String slotKey : new ArrayList<>(playerData.getConfigurationSection(basePath).getKeys(false))) {
String rPath = basePath + "." + slotKey;
if (chestLoc.getWorld().getName().equals(playerData.getString(rPath + ".world"))
&& chestLoc.getBlockX() == playerData.getInt(rPath + ".x")
&& chestLoc.getBlockY() == playerData.getInt(rPath + ".y")
&& chestLoc.getBlockZ() == playerData.getInt(rPath + ".z")) {
playerData.set(rPath, null);
removedFromNew = true;
break;
}
}
}
// Legacy-Fallback
if (!removedFromNew) {
playerData.set("players." + uuidToDelete + ".rest-chest", null);
}
savePlayerData();
}
} else if (line1.equalsIgnoreCase("input")) { } else if (line1.equalsIgnoreCase("input")) {
removeInputChestByLocation(uuidToDelete, chestLoc); removeInputChestByLocation(uuidToDelete, chestLoc);
} else if (line1.equalsIgnoreCase("ziel")) { } else if (line1.equalsIgnoreCase("ziel")) {
@@ -2072,11 +2237,24 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
if (!line2.isEmpty()) { if (!line2.isEmpty()) {
Material mat = Material.matchMaterial(line2); Material mat = Material.matchMaterial(line2);
if (mat != null) { if (mat != null) {
Location savedLoc = getTargetChestLocation(uuidToDelete, mat); if (mysqlEnabled && mysqlManager != null) {
if (savedLoc != null && savedLoc.equals(chestLoc)) { // BUG FIX: Nur bei MySQL → kein playerData-Zugriff nötig
playerData.set("players." + uuidToDelete + ".target-chests." + mat.name(), null); mysqlManager.removeTargetChest(uuidToDelete.toString(), mat.name());
savePlayerData(); } else {
if (mysqlEnabled && mysqlManager != null) mysqlManager.removeTargetChest(uuidToDelete.toString(), mat.name()); // YAML: Slot mit passender Location finden und löschen
String basePath = "players." + uuidToDelete + ".target-chests";
if (playerData.contains(basePath)) {
String path = basePath + "." + mat.name();
if (playerData.contains(path)) {
Location savedLoc = getLocationFromPath(path);
if (savedLoc != null && savedLoc.getBlockX() == chestLoc.getBlockX()
&& savedLoc.getBlockY() == chestLoc.getBlockY()
&& savedLoc.getBlockZ() == chestLoc.getBlockZ()) {
playerData.set(path, null);
savePlayerData();
}
}
}
} }
} }
} }
@@ -2144,16 +2322,36 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
private void removeOldTargetEntry(UUID uuid, Location loc, String newItemType) { private void removeOldTargetEntry(UUID uuid, Location loc, String newItemType) {
if (mysqlEnabled && mysqlManager != null) {
// MySQL: Alle Zieltruhen dieser Location löschen, die ein anderes Item haben
List<Map<String, Object>> existing = mysqlManager.getTargetChests(uuid.toString());
for (Map<String, Object> e : existing) {
String existingItem = (String) e.get("item");
if (existingItem.equalsIgnoreCase(newItemType)) continue;
String w = (String) e.get("world");
if (w.equals(loc.getWorld().getName())
&& (int) e.get("x") == loc.getBlockX()
&& (int) e.get("y") == loc.getBlockY()
&& (int) e.get("z") == loc.getBlockZ()) {
mysqlManager.removeTargetChest(uuid.toString(), existingItem);
}
}
return;
}
// YAML-Modus
String basePath = "players." + uuid + ".target-chests"; String basePath = "players." + uuid + ".target-chests";
if (!playerData.contains(basePath)) return; if (!playerData.contains(basePath)) return;
for (String existingType : playerData.getConfigurationSection(basePath).getKeys(false)) { for (String existingType : playerData.getConfigurationSection(basePath).getKeys(false)) {
if (existingType.equalsIgnoreCase(newItemType)) continue; if (existingType.equalsIgnoreCase(newItemType)) continue;
String path = basePath + "." + existingType; String path = basePath + "." + existingType;
if (playerData.getString(path + ".world").equals(loc.getWorld().getName()) String savedWorld = playerData.getString(path + ".world");
if (savedWorld == null) continue; // NPE-Schutz: korrupter Eintrag überspringen
if (savedWorld.equals(loc.getWorld().getName())
&& playerData.getInt(path + ".x") == loc.getBlockX() && playerData.getInt(path + ".x") == loc.getBlockX()
&& playerData.getInt(path + ".y") == loc.getBlockY() && playerData.getInt(path + ".y") == loc.getBlockY()
&& playerData.getInt(path + ".z") == loc.getBlockZ()) { && playerData.getInt(path + ".z") == loc.getBlockZ()) {
playerData.set(path, null); playerData.set(path, null);
savePlayerData(); // Änderung direkt auf Disk schreiben
break; break;
} }
} }
@@ -2352,7 +2550,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
}); });
// Preload Zieltruhen+Rest nur wenn es lokale Input-Truhen gibt // Preload Zieltruhen+Rest nur wenn es lokale Input-Truhen gibt
List<Map<String, Object>> preTargets = anyLocal ? mysqlManager.getTargetChests(uuidString) : new ArrayList<>(); List<Map<String, Object>> preTargets = anyLocal ? mysqlManager.getTargetChests(uuidString) : new ArrayList<>();
Map<String, Object> preRest = anyLocal ? mysqlManager.getRestChest(uuidString) : null; // BUG FIX: Alle Rest-Truhen vorladen (nicht nur eine)
List<Map<String, Object>> preRests = anyLocal ? mysqlManager.getRestChests(uuidString) : new ArrayList<>();
for (Map<String, Object> chest : chests) { for (Map<String, Object> chest : chests) {
if (crosslink) { if (crosslink) {
@@ -2361,7 +2560,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
String worldName = (String) chest.get("world"); String worldName = (String) chest.get("world");
boolean isLocal = Bukkit.getWorld(worldName) != null; boolean isLocal = Bukkit.getWorld(worldName) != null;
jobs.add(new Object[]{ ownerUUID, chest, isLocal, preTargets, preRest }); jobs.add(new Object[]{ ownerUUID, chest, isLocal, preTargets, preRests });
} }
} }
} catch (Exception e) { } catch (Exception e) {
@@ -2383,7 +2582,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Map<String, Object>> preTargets = (List<Map<String, Object>>) job[3]; List<Map<String, Object>> preTargets = (List<Map<String, Object>>) job[3];
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> preRest = (Map<String, Object>) job[4]; // BUG FIX: preRests ist jetzt eine Liste
List<Map<String, Object>> preRests = (List<Map<String, Object>>) job[4];
if (isLocal) { if (isLocal) {
World world = Bukkit.getWorld((String) chest.get("world")); World world = Bukkit.getWorld((String) chest.get("world"));
@@ -2400,14 +2600,17 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
targetLocs.computeIfAbsent((String) tc.get("item"), k -> new ArrayList<>()) targetLocs.computeIfAbsent((String) tc.get("item"), k -> new ArrayList<>())
.add(new Location(w, (int) tc.get("x"), (int) tc.get("y"), (int) tc.get("z"))); .add(new Location(w, (int) tc.get("x"), (int) tc.get("y"), (int) tc.get("z")));
} }
Location restLoc = null; // BUG FIX: Alle Rest-Truhen als Liste konvertieren
if (preRest != null && !isRemoteChest(preRest)) { List<Location> restLocs = new ArrayList<>();
World w = Bukkit.getWorld((String) preRest.get("world")); for (Map<String, Object> preRest : preRests) {
if (w != null) restLoc = new Location(w, if (preRest != null && !isRemoteChest(preRest)) {
(int) preRest.get("x"), (int) preRest.get("y"), (int) preRest.get("z")); World w = Bukkit.getWorld((String) preRest.get("world"));
if (w != null) restLocs.add(new Location(w,
(int) preRest.get("x"), (int) preRest.get("y"), (int) preRest.get("z")));
}
} }
checkSingleInputChest(ownerUUID, loc, (String) chest.get("chest_id"), false, targetLocs, restLoc); checkSingleInputChest(ownerUUID, loc, (String) chest.get("chest_id"), false, targetLocs, restLocs);
} else if (crosslink) { } else if (crosslink) {
checkRemoteInputChest(ownerUUID, chest); checkRemoteInputChest(ownerUUID, chest);
} }
@@ -2457,20 +2660,21 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
.add(new Location(world, (int) tc.get("x"), (int) tc.get("y"), (int) tc.get("z"))); .add(new Location(world, (int) tc.get("x"), (int) tc.get("y"), (int) tc.get("z")));
} }
Location localRestChest = getRestChestLocation(ownerUUID); // BUG FIX: Alle lokalen Rest-Truhen laden
List<Location> localRestChests = getRestChestLocations(ownerUUID);
if (localTargets.isEmpty() && localRestChest == null) return; if (localTargets.isEmpty() && localRestChests.isEmpty()) return;
OfflinePlayer op = Bukkit.getOfflinePlayer(ownerUUID); OfflinePlayer op = Bukkit.getOfflinePlayer(ownerUUID);
String ownerName = op.getName() != null ? op.getName() : ownerUUID.toString(); String ownerName = op.getName() != null ? op.getName() : ownerUUID.toString();
distributeFromRemoteInputChest(ownerUUID, ownerName, inputChestData, localTargets, localRestChest); distributeFromRemoteInputChest(ownerUUID, ownerName, inputChestData, localTargets, localRestChests);
} }
private void distributeFromRemoteInputChest(UUID ownerUUID, String ownerName, private void distributeFromRemoteInputChest(UUID ownerUUID, String ownerName,
Map<String, Object> inputChestData, Map<String, Object> inputChestData,
Map<String, List<Location>> localTargets, Map<String, List<Location>> localTargets,
Location localRestChest) { List<Location> localRestChests) {
if (!mysqlEnabled || mysqlManager == null) return; if (!mysqlEnabled || mysqlManager == null) return;
@@ -2495,7 +2699,12 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
for (Location l : slots) { for (Location l : slots) {
if (l != null && !isChestCachedFull(l)) { targetLoc = l; break; } if (l != null && !isChestCachedFull(l)) { targetLoc = l; break; }
} }
if (targetLoc == null) targetLoc = localRestChest; // BUG FIX: Alle lokalen Rest-Truhen als Fallback versuchen
if (targetLoc == null) {
for (Location restLoc : localRestChests) {
if (restLoc != null && !isChestCachedFull(restLoc)) { targetLoc = restLoc; break; }
}
}
if (targetLoc == null) continue; if (targetLoc == null) continue;
if (!(targetLoc.getBlock().getState() instanceof Chest)) continue; if (!(targetLoc.getBlock().getState() instanceof Chest)) continue;
@@ -2523,7 +2732,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
/** Legacy-Ueberladung: laedt Truhen-Locations synchron (nur fuer InventoryMoveItemEvent). */ /** Legacy-Ueberladung: laedt Truhen-Locations synchron (nur fuer InventoryMoveItemEvent). */
private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory, private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory,
String ownerNameOverride, Location sourceLocation) { String ownerNameOverride, Location sourceLocation) {
Location restLoc = getRestChestLocation(ownerUUID); List<Location> restLocs = getRestChestLocations(ownerUUID);
Map<String, List<Location>> targets = new HashMap<>(); Map<String, List<Location>> targets = new HashMap<>();
if (mysqlEnabled && mysqlManager != null) { if (mysqlEnabled && mysqlManager != null) {
for (Map<String, Object> tc : mysqlManager.getTargetChests(ownerUUID.toString())) { for (Map<String, Object> tc : mysqlManager.getTargetChests(ownerUUID.toString())) {
@@ -2546,7 +2755,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
} }
} }
distributeItemsForOwner(ownerUUID, ownerPlayer, sourceInventory, ownerNameOverride, sourceLocation, targets, restLoc); distributeItemsForOwner(ownerUUID, ownerPlayer, sourceInventory, ownerNameOverride, sourceLocation, targets, restLocs);
} }
private boolean checkSingleInputChest(UUID ownerUUID, Location location, String debugId, boolean crosslinkMode) { private boolean checkSingleInputChest(UUID ownerUUID, Location location, String debugId, boolean crosslinkMode) {
@@ -2554,7 +2763,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
private boolean checkSingleInputChest(UUID ownerUUID, Location location, String debugId, boolean crosslinkMode, private boolean checkSingleInputChest(UUID ownerUUID, Location location, String debugId, boolean crosslinkMode,
Map<String, List<Location>> preloadedTargets, Location preloadedRest) { Map<String, List<Location>> preloadedTargets, List<Location> preloadedRests) {
if (isWorldBlacklisted(location.getWorld())) return true; if (isWorldBlacklisted(location.getWorld())) return true;
if (!(location.getBlock().getState() instanceof Chest)) return false; if (!(location.getBlock().getState() instanceof Chest)) return false;
@@ -2599,7 +2808,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
// Wenn preloadedTargets null: synchron laden (Fallback) // Wenn preloadedTargets null: synchron laden (Fallback)
Map<String, List<Location>> effectiveTargets = preloadedTargets; Map<String, List<Location>> effectiveTargets = preloadedTargets;
Location effectiveRest = preloadedRest; List<Location> effectiveRests = preloadedRests;
if (effectiveTargets == null) { if (effectiveTargets == null) {
effectiveTargets = new HashMap<>(); effectiveTargets = new HashMap<>();
if (mysqlEnabled && mysqlManager != null) { if (mysqlEnabled && mysqlManager != null) {
@@ -2625,22 +2834,23 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
} }
} }
if (effectiveRest == null && preloadedTargets == null) { // BUG FIX: Rest-Truhen als Liste laden wenn nicht vorgeladen
effectiveRest = getRestChestLocation(ownerUUID); if (effectiveRests == null) {
effectiveRests = getRestChestLocations(ownerUUID);
} }
distributeItemsForOwner(ownerUUID, ownerPlayer, chest.getInventory(), ownerName, location, effectiveTargets, effectiveRest); distributeItemsForOwner(ownerUUID, ownerPlayer, chest.getInventory(), ownerName, location, effectiveTargets, effectiveRests);
return true; return true;
} }
/** /**
* Oeffentliche Variante mit vorgeladenen Truhen-Locations (kein DB-Hit im Main Thread). * Oeffentliche Variante mit vorgeladenen Truhen-Locations (kein DB-Hit im Main Thread).
* targetChestMap: Material-Name → Location (lokale Zieltruhen) * targetChestMap: Material-Name → Location (lokale Zieltruhen)
* restChestLoc: lokale Rest-Truhe oder null * restChestLocs: lokale Rest-Truhen oder leere Liste
*/ */
private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory, private void distributeItemsForOwner(UUID ownerUUID, Player ownerPlayer, Inventory sourceInventory,
String ownerNameOverride, Location sourceLocation, String ownerNameOverride, Location sourceLocation,
Map<String, List<Location>> targetChestMap, Location restChestLoc) { Map<String, List<Location>> targetChestMap, List<Location> restChestLocs) {
boolean hasItems = false; boolean hasItems = false;
for (ItemStack item : sourceInventory.getContents()) { for (ItemStack item : sourceInventory.getContents()) {
@@ -2658,8 +2868,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
if (offlinePlayer.hasPlayedBefore()) ownerName = offlinePlayer.getName(); if (offlinePlayer.hasPlayedBefore()) ownerName = offlinePlayer.getName();
} }
Location restChestLocation = restChestLoc; // BUG FIX: Rest-Truhen als Liste verwalten
boolean restChestKnownFull = (restChestLocation != null) && isChestCachedFull(restChestLocation); List<Location> restChestLocations = (restChestLocs != null) ? restChestLocs : new ArrayList<>();
// Alle vollen Rest-Truhen ermitteln
boolean allRestChestsFull = !restChestLocations.isEmpty()
&& restChestLocations.stream().allMatch(l -> l != null && isChestCachedFull(l));
for (int slot = 0; slot < sourceInventory.getSize(); slot++) { for (int slot = 0; slot < sourceInventory.getSize(); slot++) {
ItemStack item = sourceInventory.getItem(slot); ItemStack item = sourceInventory.getItem(slot);
@@ -2676,12 +2889,16 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
boolean isRestChest = false; boolean isRestChest = false;
boolean isCrosslink = false; boolean isCrosslink = false;
// Aktive Rest-Truhe für dieses Item (erste nicht-volle)
Location activeRestChestLocation = null;
for (Location loc : restChestLocations) {
if (loc != null && !isChestCachedFull(loc)) { activeRestChestLocation = loc; break; }
}
if (targetChestLocation == null) { if (targetChestLocation == null) {
if (serverCrosslink && mysqlEnabled && mysqlManager != null) { if (serverCrosslink && mysqlEnabled && mysqlManager != null) {
Map<String, Object> raw = mysqlManager.getTargetChest(ownerUUID.toString(), item.getType().name()); Map<String, Object> raw = mysqlManager.getTargetChest(ownerUUID.toString(), item.getType().name());
if (raw != null && isRemoteChest(raw)) { if (raw != null && isRemoteChest(raw)) {
// ── BungeeCord NEU: expliziten Ziel-Server mitgeben ───────────
String targetServerName = getChestServer(raw); String targetServerName = getChestServer(raw);
isCrosslink = true; isCrosslink = true;
mysqlManager.setupTransferTable(); mysqlManager.setupTransferTable();
@@ -2698,27 +2915,31 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
if (!isCrosslink) { if (!isCrosslink) {
if (restChestKnownFull) continue; // BUG FIX: Alle Rest-Truhen prüfen, nicht nur eine
if (allRestChestsFull) continue;
if (restChestLocation != null) { if (activeRestChestLocation != null) {
targetChestLocation = restChestLocation; targetChestLocation = activeRestChestLocation;
isRestChest = true; isRestChest = true;
} else if (serverCrosslink && mysqlEnabled && mysqlManager != null) { } else if (serverCrosslink && mysqlEnabled && mysqlManager != null) {
Map<String, Object> raw = mysqlManager.getRestChest(ownerUUID.toString()); // Crosslink: Remote Rest-Truhe suchen
if (raw != null && isRemoteChest(raw)) { for (Map<String, Object> raw : mysqlManager.getRestChests(ownerUUID.toString())) {
// ── BungeeCord NEU: expliziten Ziel-Server mitgeben ───── if (raw != null && isRemoteChest(raw)) {
String targetServerName = getChestServer(raw); String targetServerName = getChestServer(raw);
mysqlManager.setupTransferTable(); mysqlManager.setupTransferTable();
mysqlManager.addTransfer(ownerUUID.toString(), item.getType().name(), item.getAmount(), mysqlManager.addTransfer(ownerUUID.toString(), item.getType().name(), item.getAmount(),
(String) raw.get("world"), targetServerName, serverName); (String) raw.get("world"), targetServerName, serverName);
sourceInventory.setItem(slot, null); sourceInventory.setItem(slot, null);
if (isDebug()) { if (isDebug()) {
getLogger().info("[CrossLink] " + item.getAmount() + "x " + item.getType().name() getLogger().info("[CrossLink] " + item.getAmount() + "x " + item.getType().name()
+ " → Server:'" + (targetServerName.isEmpty() ? "?" : targetServerName) + " → Server:'" + (targetServerName.isEmpty() ? "?" : targetServerName)
+ "' Welt:'" + raw.get("world") + "' (Rest-Transfer-DB)"); + "' Welt:'" + raw.get("world") + "' (Rest-Transfer-DB)");
}
isCrosslink = true;
break;
} }
continue;
} }
if (isCrosslink) continue;
} }
} }
} else { } else {
@@ -2741,9 +2962,36 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) {
ownerPlayer.sendMessage(getMessage("target-chest-missing").replace("%item%", isRestChest ? "Rest-Truhe" : item.getType().name())); ownerPlayer.sendMessage(getMessage("target-chest-missing").replace("%item%", isRestChest ? "Rest-Truhe" : item.getType().name()));
} }
if (isRestChest) playerData.set("players." + ownerUUID + ".rest-chest", null); // BUG FIX: Beim MySQL-Modus playerData nicht anfassen; bei YAML korrekte Pfade
else playerData.set("players." + ownerUUID + ".target-chests." + item.getType().name(), null); if (isRestChest) {
savePlayerData(); if (mysqlEnabled && mysqlManager != null) {
mysqlManager.removeRestChestByLocation(ownerUUID.toString(),
targetChestLocation.getWorld().getName(),
targetChestLocation.getBlockX(), targetChestLocation.getBlockY(), targetChestLocation.getBlockZ());
} else {
String basePath = "players." + ownerUUID + ".rest-chests";
if (playerData.contains(basePath)) {
for (String slotKey : new ArrayList<>(playerData.getConfigurationSection(basePath).getKeys(false))) {
String rPath = basePath + "." + slotKey;
if (targetChestLocation.getWorld().getName().equals(playerData.getString(rPath + ".world"))
&& targetChestLocation.getBlockX() == playerData.getInt(rPath + ".x")
&& targetChestLocation.getBlockY() == playerData.getInt(rPath + ".y")
&& targetChestLocation.getBlockZ() == playerData.getInt(rPath + ".z")) {
playerData.set(rPath, null);
break;
}
}
}
// Legacy
playerData.set("players." + ownerUUID + ".rest-chest", null);
savePlayerData();
}
} else {
if (!mysqlEnabled) {
playerData.set("players." + ownerUUID + ".target-chests." + item.getType().name(), null);
savePlayerData();
}
}
continue; continue;
} }
@@ -2782,7 +3030,6 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
if (isFull) { if (isFull) {
markChestFull(targetChestLocation); markChestFull(targetChestLocation);
if (isRestChest) restChestKnownFull = true;
} }
if (signBlock != null) { if (signBlock != null) {
@@ -2829,19 +3076,41 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
} }
} }
if (isFull && isRestChest) { if (isFull && isRestChest) {
if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) { // BUG FIX: Nächste Rest-Truhe versuchen
String message = getMessage("target-chest-full") Location nextRestLoc = null;
.replace("%item%", item.getType().name()) for (Location loc : restChestLocations) {
.replace("%x%", String.valueOf(targetChestLocation.getBlockX())) if (loc != null && !isChestCachedFull(loc) && !loc.equals(targetChestLocation)) {
.replace("%y%", String.valueOf(targetChestLocation.getBlockY())) nextRestLoc = loc; break;
.replace("%z%", String.valueOf(targetChestLocation.getBlockZ())); }
ownerPlayer.sendMessage(message);
} }
for (ItemStack leftoverItem : leftover.values()) { if (nextRestLoc != null && nextRestLoc.getBlock().getState() instanceof Chest) {
if (leftoverItem != null && leftoverItem.getType() == item.getType()) { Chest nextRestChest = (Chest) nextRestLoc.getBlock().getState();
item.setAmount(leftoverItem.getAmount()); Map<Integer, ItemStack> 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 {
item.setAmount(leftover2.get(0).getAmount());
sourceInventory.setItem(slot, item); sourceInventory.setItem(slot, item);
break; }
} else {
// Alle Rest-Truhen voll: Nachricht senden
if (ownerPlayer != null && canSendFullChestMessage(ownerUUID, item.getType())) {
String message = getMessage("target-chest-full")
.replace("%item%", item.getType().name())
.replace("%x%", String.valueOf(targetChestLocation.getBlockX()))
.replace("%y%", String.valueOf(targetChestLocation.getBlockY()))
.replace("%z%", String.valueOf(targetChestLocation.getBlockZ()));
ownerPlayer.sendMessage(message);
}
for (ItemStack leftoverItem : leftover.values()) {
if (leftoverItem != null && leftoverItem.getType() == item.getType()) {
item.setAmount(leftoverItem.getAmount());
sourceInventory.setItem(slot, item);
break;
}
} }
} }
} }

View File

@@ -36,6 +36,39 @@ public class MySQLManager {
} }
} }
/**
* Migriert den PRIMARY KEY der asc_rest_chests von (uuid) auf (uuid, slot) falls nötig.
* Ermöglicht mehrere Rest-Truhen pro Spieler.
*/
private void migrateRestChestPrimaryKey(Statement st) {
try {
// Slot-Spalte zuerst sicherstellen
if (!columnExists("asc_rest_chests", "slot")) {
st.execute("ALTER TABLE asc_rest_chests ADD COLUMN slot INT NOT NULL DEFAULT 0;");
}
// Prüfen ob slot bereits im PRIMARY KEY ist
try (PreparedStatement ps = connection.prepareStatement(
"SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE " +
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'asc_rest_chests' " +
"AND CONSTRAINT_NAME = 'PRIMARY' AND COLUMN_NAME = 'slot';")) {
ResultSet rs = ps.executeQuery();
if (rs.next() && rs.getInt(1) > 0) {
rs.close();
return; // Bereits migriert
}
rs.close();
}
// Alten PK droppen und neuen mit slot anlegen
st.execute("ALTER TABLE asc_rest_chests DROP PRIMARY KEY, ADD PRIMARY KEY(uuid, slot);");
} catch (SQLException e) {
if (!e.getMessage().toLowerCase().contains("primary")) {
e.printStackTrace();
}
}
}
/** /**
* Migriert den PRIMARY KEY von (uuid, item) auf (uuid, item, slot) falls nötig. * Migriert den PRIMARY KEY von (uuid, item) auf (uuid, item, slot) falls nötig.
* Nutzt DATABASE() um nur die aktuelle DB zu prüfen (kein Cross-DB-Problem). * Nutzt DATABASE() um nur die aktuelle DB zu prüfen (kein Cross-DB-Problem).
@@ -88,7 +121,7 @@ public class MySQLManager {
*/ */
private void tryAlterColumn(Statement st, String table, String column, String definition) { private void tryAlterColumn(Statement st, String table, String column, String definition) {
try { try {
ResultSet rs = st.getConnection().getMetaData().getColumns(null, null, table, column); ResultSet rs = st.getConnection().getMetaData().getColumns(connection.getCatalog(), null, table, column);
if (!rs.next()) { if (!rs.next()) {
st.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition + ";"); st.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition + ";");
} }
@@ -105,7 +138,7 @@ public class MySQLManager {
*/ */
private boolean columnExists(String table, String column) { private boolean columnExists(String table, String column) {
try { try {
ResultSet rs = connection.getMetaData().getColumns(null, null, table, column); ResultSet rs = connection.getMetaData().getColumns(connection.getCatalog(), null, table, column);
boolean exists = rs.next(); boolean exists = rs.next();
rs.close(); rs.close();
return exists; return exists;
@@ -168,9 +201,10 @@ public class MySQLManager {
");"); ");");
st.execute("CREATE TABLE IF NOT EXISTS asc_rest_chests (" + st.execute("CREATE TABLE IF NOT EXISTS asc_rest_chests (" +
"uuid VARCHAR(36), world VARCHAR(32)," + "uuid VARCHAR(36), slot INT NOT NULL DEFAULT 0," +
"world VARCHAR(32)," +
"x INT, y INT, z INT, `public` BOOLEAN DEFAULT FALSE," + "x INT, y INT, z INT, `public` BOOLEAN DEFAULT FALSE," +
"PRIMARY KEY(uuid)" + "PRIMARY KEY(uuid, slot)" +
");"); ");");
// ── asc_transfers: immer beim Start sicherstellen ───────────────────── // ── asc_transfers: immer beim Start sicherstellen ─────────────────────
@@ -207,6 +241,9 @@ public class MySQLManager {
// v2 → v3 (Multi-Target): slot-Spalte + PRIMARY KEY Migration // v2 → v3 (Multi-Target): slot-Spalte + PRIMARY KEY Migration
migrateTargetChestPrimaryKey(st); migrateTargetChestPrimaryKey(st);
// v2 → v3 (Multi-Rest): slot-Spalte + PRIMARY KEY Migration fuer Rest-Truhen
migrateRestChestPrimaryKey(st);
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -593,32 +630,93 @@ public class MySQLManager {
setRestChest(uuid, world, x, y, z, isPublic, ""); setRestChest(uuid, world, x, y, z, isPublic, "");
} }
/** BungeeCord-Überladung mit serverName. */ /** BungeeCord-Überladung mit serverName ermittelt automatisch den nächsten freien Slot. */
public void setRestChest(String uuid, String world, int x, int y, int z, public void setRestChest(String uuid, String world, int x, int y, int z,
boolean isPublic, String serverName) { boolean isPublic, String serverName) {
// Prüfen ob diese exakte Location bereits als Rest-Truhe registriert ist (Update)
int slot = getRestSlotForLocation(uuid, world, x, y, z);
if (slot < 0) {
// Neue Truhe: nächsten freien Slot ermitteln
slot = getNextRestSlot(uuid);
}
setRestChest(uuid, slot, world, x, y, z, isPublic, serverName);
}
/** Vollständige Überladung mit explizitem slot. */
public void setRestChest(String uuid, int slot, String world, int x, int y, int z,
boolean isPublic, String serverName) {
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"REPLACE INTO asc_rest_chests (uuid, world, x, y, z, `public`, server) " + "REPLACE INTO asc_rest_chests (uuid, slot, world, x, y, z, `public`, server) " +
"VALUES (?, ?, ?, ?, ?, ?, ?);")) { "VALUES (?, ?, ?, ?, ?, ?, ?, ?);")) {
ps.setString(1, uuid); ps.setString(1, uuid);
ps.setString(2, world); ps.setInt(2, slot);
ps.setInt(3, x); ps.setString(3, world);
ps.setInt(4, y); ps.setInt(4, x);
ps.setInt(5, z); ps.setInt(5, y);
ps.setBoolean(6, isPublic); ps.setInt(6, z);
ps.setString(7, serverName != null ? serverName : ""); ps.setBoolean(7, isPublic);
ps.setString(8, serverName != null ? serverName : "");
ps.executeUpdate(); ps.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public Map<String, Object> getRestChest(String uuid) { /** Gibt den nächsten freien Slot für Rest-Truhen zurück. */
public int getNextRestSlot(String uuid) {
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"SELECT * FROM asc_rest_chests WHERE uuid=?;")) { "SELECT COALESCE(MAX(slot)+1, 0) AS next_slot FROM asc_rest_chests WHERE uuid=?;")) {
ps.setString(1, uuid); ps.setString(1, uuid);
ResultSet rs = ps.executeQuery(); ResultSet rs = ps.executeQuery();
if (rs.next()) { if (rs.next()) return rs.getInt("next_slot");
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
/**
* 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) {
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);
ps.setString(2, world);
ps.setInt(3, x);
ps.setInt(4, y);
ps.setInt(5, z);
ResultSet rs = ps.executeQuery();
if (rs.next()) return rs.getInt("slot");
} catch (SQLException e) {
e.printStackTrace();
}
return -1;
}
/** Zählt wie viele Rest-Truhen ein Spieler hat. */
public int countRestChests(String uuid) {
try (PreparedStatement ps = connection.prepareStatement(
"SELECT COUNT(*) FROM asc_rest_chests WHERE uuid=?;")) {
ps.setString(1, uuid);
ResultSet rs = ps.executeQuery();
if (rs.next()) return rs.getInt(1);
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
/** Gibt ALLE Rest-Truhen eines Spielers zurück, sortiert nach slot. */
public List<Map<String, Object>> getRestChests(String uuid) {
List<Map<String, Object>> list = new ArrayList<>();
try (PreparedStatement ps = connection.prepareStatement(
"SELECT * FROM asc_rest_chests WHERE uuid=? ORDER BY slot ASC;")) {
ps.setString(1, uuid);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("slot", rs.getInt("slot"));
map.put("world", rs.getString("world")); map.put("world", rs.getString("world"));
map.put("x", rs.getInt("x")); map.put("x", rs.getInt("x"));
map.put("y", rs.getInt("y")); map.put("y", rs.getInt("y"));
@@ -626,14 +724,72 @@ public class MySQLManager {
map.put("public", rs.getBoolean("public")); map.put("public", rs.getBoolean("public"));
try { map.put("server", rs.getString("server")); } try { map.put("server", rs.getString("server")); }
catch (SQLException ignored) { map.put("server", ""); } catch (SQLException ignored) { map.put("server", ""); }
return map; list.add(map);
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }
return null; return list;
} }
/** Gibt die erste Rest-Truhe (slot=0) zurück Legacy-Kompatibilität. */
public Map<String, Object> getRestChest(String uuid) {
List<Map<String, Object>> all = getRestChests(uuid);
return all.isEmpty() ? null : all.get(0);
}
/** Löscht eine spezifische Rest-Truhe anhand ihrer Location. */
public void removeRestChestByLocation(String uuid, String world, int x, int y, int z) {
try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_rest_chests WHERE uuid=? AND world=? AND x=? AND y=? AND z=?;")) {
ps.setString(1, uuid);
ps.setString(2, world);
ps.setInt(3, x);
ps.setInt(4, y);
ps.setInt(5, z);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
// Slots nach dem gelöschten nach unten schieben um Lücken zu vermeiden
compactRestSlots(uuid);
}
/** Löscht eine spezifische Rest-Truhe anhand des Slots. */
public void removeRestChestSlot(String uuid, int slot) {
try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_rest_chests WHERE uuid=? AND slot=?;")) {
ps.setString(1, uuid);
ps.setInt(2, slot);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
compactRestSlots(uuid);
}
/** Schiebt Rest-Slots nach dem gelöschten nach unten (keine Lücken). */
private void compactRestSlots(String uuid) {
try {
List<Map<String, Object>> remaining = getRestChests(uuid);
for (int i = 0; i < remaining.size(); i++) {
int currentSlot = (int) remaining.get(i).get("slot");
if (currentSlot != i) {
try (PreparedStatement ps = connection.prepareStatement(
"UPDATE asc_rest_chests SET slot=? WHERE uuid=? AND slot=?;")) {
ps.setInt(1, i);
ps.setString(2, uuid);
ps.setInt(3, currentSlot);
ps.executeUpdate();
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/** Löscht ALLE Rest-Truhen eines Spielers. */
public void removeRestChest(String uuid) { public void removeRestChest(String uuid) {
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_rest_chests WHERE uuid=?;")) { "DELETE FROM asc_rest_chests WHERE uuid=?;")) {
@@ -694,11 +850,12 @@ public class MySQLManager {
public Map<String, Object> getAnyRestChest() { public Map<String, Object> getAnyRestChest() {
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"SELECT * FROM asc_rest_chests WHERE public=1 LIMIT 1;")) { "SELECT * FROM asc_rest_chests WHERE `public`=1 ORDER BY uuid, slot LIMIT 1;")) {
ResultSet rs = ps.executeQuery(); ResultSet rs = ps.executeQuery();
if (rs.next()) { if (rs.next()) {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("uuid", rs.getString("uuid")); map.put("uuid", rs.getString("uuid"));
map.put("slot", rs.getInt("slot"));
map.put("world", rs.getString("world")); map.put("world", rs.getString("world"));
map.put("x", rs.getInt("x")); map.put("x", rs.getInt("x"));
map.put("y", rs.getInt("y")); map.put("y", rs.getInt("y"));

View File

@@ -10,7 +10,7 @@
# --- GRUNDLEGUNG --- # --- GRUNDLEGUNG ---
# Version der Konfigurationsdatei. Nicht ändern, um Fehler zu vermeiden! # Version der Konfigurationsdatei. Nicht ändern, um Fehler zu vermeiden!
version: "2.0" version: "2.2"
# Debug-Modus (true = Ausführliche Logs in der Server-Konsole, nur zum Entwickeln nutzen) # Debug-Modus (true = Ausführliche Logs in der Server-Konsole, nur zum Entwickeln nutzen)

View File

@@ -1,5 +1,5 @@
name: AutoSortChest name: AutoSortChest
version: 2.2 version: 2.3
main: com.viper.autosortchest.Main main: com.viper.autosortchest.Main
api-version: 1.21 api-version: 1.21
authors: [M_Viper] authors: [M_Viper]
@@ -25,3 +25,9 @@ permissions:
autosortchest.admin: autosortchest.admin:
description: Erlaubt OPs/Admins Zugriff auf fremde AutoSortChest-Truhen (Öffnen, Entnehmen, Abbauen) description: Erlaubt OPs/Admins Zugriff auf fremde AutoSortChest-Truhen (Öffnen, Entnehmen, Abbauen)
default: op default: op
autosortchest.limit.<gruppe>:
description: >
Limits fuer eine benutzerdefinierte Gruppe aus der config.yml.
Ersetze <gruppe> durch den Gruppennamen (z.B. autosortchest.limit.vip).
Die Gruppen und ihre Limits werden ausschliesslich in der config.yml definiert.
default: false