Update from Git Manager GUI
This commit is contained in:
@@ -47,7 +47,7 @@ import java.util.stream.Collectors;
|
||||
import com.viper.autosortchest.MySQLManager;
|
||||
|
||||
|
||||
public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
public class Main extends JavaPlugin implements Listener, CommandExecutor, org.bukkit.command.TabCompleter {
|
||||
|
||||
private boolean serverCrosslink = true;
|
||||
|
||||
@@ -138,9 +138,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
@Override
|
||||
public void run() {
|
||||
cleanMessageTracker();
|
||||
flushPlayerData(); // FIX: Async-Flush alle 60s statt synchronem Save bei jeder Änderung
|
||||
flushPlayerData(); // Async-Flush alle 30s
|
||||
}
|
||||
}.runTaskTimer(this, 20L * 60, 20L * 60);
|
||||
}.runTaskTimer(this, 20L * 30, 20L * 30);
|
||||
|
||||
// ── BungeeCord NEU: Heartbeat alle 30 Sekunden (async) ────────────────
|
||||
if (mysqlEnabled && mysqlManager != null && !serverName.isEmpty()) {
|
||||
@@ -226,6 +226,10 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
// group → { "input", "rest", "target" } → limit
|
||||
private Map<String, Map<String, Integer>> chestLimits;
|
||||
private boolean chestLimitsEnabled = true;
|
||||
// FIX: Cache für bereits migrierte Target-Einträge – verhindert wiederholte YAML-Lookups
|
||||
// im heißen Sort-Loop (isOldTargetFormat wird nach einmaliger Migration nie wieder true).
|
||||
private final java.util.Set<String> migratedTargetItems = new java.util.HashSet<>();
|
||||
|
||||
private final Map<UUID, Map<Material, Long>> fullChestMessageTracker = new HashMap<>();
|
||||
private static final long MESSAGE_COOLDOWN = 5 * 60 * 1000;
|
||||
|
||||
@@ -330,7 +334,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
private volatile boolean playerDataDirty = false;
|
||||
private volatile boolean saveInProgress = false;
|
||||
|
||||
private static final String CONFIG_VERSION = "2.4";
|
||||
private static final String CONFIG_VERSION = "2.5";
|
||||
|
||||
private boolean updateAvailable = false;
|
||||
private String latestVersion = "";
|
||||
@@ -369,6 +373,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
"&f- &b/asc import &f- Importiert Daten aus players.yml in MySQL (OP).\n" +
|
||||
"&f- &b/asc export &f- Exportiert Daten aus MySQL in players.yml (OP).\n" +
|
||||
"&f- &b/asc list <Spieler> &f- Zeigt Truhen-Übersicht eines Spielers (Admin).\n" +
|
||||
"&f- &b/asc autosign <input|ziel|rest|trash> [item|hand]\n" +
|
||||
" &7Setzt automatisch ein ASC-Schild an die angeschaute Truhe.\n" +
|
||||
" &7Beispiele: &b/asc autosign ziel IRON_ORE &7| &b/asc autosign ziel hand\n" +
|
||||
"&6&l========================";
|
||||
|
||||
private static final String HELP_EN =
|
||||
@@ -405,6 +412,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
"&f- &b/asc import &f- Imports data from players.yml into MySQL (OP only).\n" +
|
||||
"&f- &b/asc export &f- Exports data from MySQL into players.yml (OP only).\n" +
|
||||
"&f- &b/asc list <player> &f- Shows chest overview of a player (Admin).\n" +
|
||||
"&f- &b/asc autosign <input|target|rest|trash> [item|hand]\n" +
|
||||
" &7Automatically places an ASC sign on the chest you are looking at.\n" +
|
||||
" &7Examples: &b/asc autosign target IRON_ORE &7| &b/asc autosign target hand\n" +
|
||||
"&6&l========================";
|
||||
|
||||
private static final String INFO_DE =
|
||||
@@ -663,6 +673,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
|
||||
getServer().getPluginManager().registerEvents(this, this);
|
||||
this.getCommand("asc").setExecutor(this);
|
||||
this.getCommand("asc").setTabCompleter(this);
|
||||
|
||||
// Mülltruchen-Manager initialisieren
|
||||
trashChestManager = new TrashChestManager(this);
|
||||
@@ -734,6 +745,12 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
getLogger().info("AutoSortChest Plugin deaktiviert!");
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(org.bukkit.event.player.PlayerQuitEvent event) {
|
||||
// Verhindert dauerhaftes Wachsen der openCustomInventories Map
|
||||
openCustomInventories.remove(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent event) {
|
||||
if (!updateAvailable) return;
|
||||
@@ -991,6 +1008,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
}
|
||||
|
||||
private void loadPlayerData() {
|
||||
migratedTargetItems.clear(); // Cache leeren damit Migrationen korrekt erkannt werden
|
||||
if (playerDataFile == null) {
|
||||
playerDataFile = new File(getDataFolder(), "players.yml");
|
||||
}
|
||||
@@ -1837,6 +1855,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
|
||||
/** Prüft ob ein Target-Eintrag noch im alten Flat-Format gespeichert ist (direkt world/x/y/z unter item). */
|
||||
private boolean isOldTargetFormat(UUID playerUUID, String itemName) {
|
||||
// Cache-Hit: bereits migriert → sofort false zurückgeben ohne YAML-Zugriff
|
||||
if (migratedTargetItems.contains(playerUUID + ":" + itemName)) return false;
|
||||
String path = "players." + playerUUID + ".target-chests." + itemName;
|
||||
return playerData.contains(path + ".world");
|
||||
}
|
||||
@@ -1862,6 +1882,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
playerData.set(base + ".0.y", y);
|
||||
playerData.set(base + ".0.z", z);
|
||||
playerData.set(base + ".0.public", pub);
|
||||
// Im Cache markieren – kein erneuter YAML-Lookup mehr nötig
|
||||
migratedTargetItems.add(playerUUID + ":" + itemName);
|
||||
savePlayerData();
|
||||
if (isDebug()) getLogger().info("[YAML-Migration] target-chests." + itemName + " → Slot 0 für " + playerUUID);
|
||||
}
|
||||
@@ -1875,8 +1897,24 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
// Auto-Migrierung falls altes Format
|
||||
if (isOldTargetFormat(playerUUID, itemName)) migrateOldTargetChestEntry(playerUUID, itemName);
|
||||
if (!playerData.isConfigurationSection(base)) return result;
|
||||
for (String slotKey : playerData.getConfigurationSection(base).getKeys(false)) {
|
||||
String p = base + "." + slotKey;
|
||||
// Slots mit Prio sammeln und nach prio aufsteigend sortieren (Prio 1 zuerst, 0 = nicht gesetzt → ganz hinten)
|
||||
List<int[]> slotPrios = new ArrayList<>(); // [slot-index, prio]
|
||||
List<String> slotKeys = new ArrayList<>(playerData.getConfigurationSection(base).getKeys(false));
|
||||
for (String slotKey : slotKeys) {
|
||||
int prio = playerData.getInt(base + "." + slotKey + ".prio", 0);
|
||||
int slotIdx = 0;
|
||||
try { slotIdx = Integer.parseInt(slotKey); } catch (NumberFormatException ignored) {}
|
||||
slotPrios.add(new int[]{slotIdx, prio});
|
||||
}
|
||||
// Prio 1 zuerst, Prio 2 danach usw. — Prio 0 (nicht gesetzt) kommt ans Ende.
|
||||
// Bei gleicher Prio: niedrigster Slot zuerst.
|
||||
slotPrios.sort((a, b) -> {
|
||||
int pa = a[1] == 0 ? Integer.MAX_VALUE : a[1];
|
||||
int pb = b[1] == 0 ? Integer.MAX_VALUE : b[1];
|
||||
return pa != pb ? Integer.compare(pa, pb) : Integer.compare(a[0], b[0]);
|
||||
});
|
||||
for (int[] sp : slotPrios) {
|
||||
String p = base + "." + sp[0];
|
||||
World w = Bukkit.getWorld(playerData.getString(p + ".world", ""));
|
||||
if (w == null) continue;
|
||||
result.add(new Location(w, playerData.getInt(p + ".x"), playerData.getInt(p + ".y"), playerData.getInt(p + ".z")));
|
||||
@@ -2074,7 +2112,131 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!command.getName().equalsIgnoreCase("asc")) return false;
|
||||
|
||||
if (args.length == 0) {
|
||||
// Keine Argumente → Hilfe anzeigen (kein Crash durch args[0])
|
||||
String lang = config != null ? config.getString("language", "de") : "de";
|
||||
if (sender instanceof Player p) {
|
||||
String helpMessage = "en".equalsIgnoreCase(lang) ? HELP_EN : HELP_DE;
|
||||
p.sendMessage(ChatColor.translateAlternateColorCodes('&', helpMessage).split("\n"));
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Verwendung: /asc [reload|import|export|list]");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// -------------------------------------------------------
|
||||
// /asc priority <Nummer> – Priorität für Zieltruhe setzen
|
||||
// -------------------------------------------------------
|
||||
if (args[0].equalsIgnoreCase("priority")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage(getMessage("priority-player-only"));
|
||||
return true;
|
||||
}
|
||||
Player player = (Player) sender;
|
||||
if (!player.hasPermission("autosortchest.use")) {
|
||||
player.sendMessage(getMessage("no-permission"));
|
||||
return true;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(getMessage("priority-usage"));
|
||||
return true;
|
||||
}
|
||||
int prio = 0;
|
||||
try {
|
||||
prio = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
player.sendMessage(getMessage("priority-invalid-number").replace("%input%", args[1]));
|
||||
return true;
|
||||
}
|
||||
if (prio < 1 || prio > 20) {
|
||||
player.sendMessage(getMessage("priority-out-of-range"));
|
||||
return true;
|
||||
}
|
||||
// Block, den der Spieler anschaut (max. 5 Blöcke)
|
||||
Block targetBlock = player.getTargetBlockExact(5);
|
||||
if (targetBlock == null || !(targetBlock.getState() instanceof Sign)) {
|
||||
player.sendMessage(getMessage("priority-no-sign"));
|
||||
return true;
|
||||
}
|
||||
Sign sign = (Sign) targetBlock.getState();
|
||||
// Prüfen, ob es ein ASC-Ziel-Schild ist
|
||||
if (!isAscSign(sign, "target")) {
|
||||
player.sendMessage(getMessage("priority-wrong-sign"));
|
||||
return true;
|
||||
}
|
||||
// Zieltruhe zu diesem Schild finden (WallSign-API, nicht deprecated getData())
|
||||
Block attached = null;
|
||||
if (targetBlock.getBlockData() instanceof WallSign wallSignBD) {
|
||||
attached = targetBlock.getRelative(wallSignBD.getFacing().getOppositeFace());
|
||||
}
|
||||
if (attached == null || !(attached.getState() instanceof Chest)) {
|
||||
player.sendMessage(getMessage("priority-not-attached"));
|
||||
return true;
|
||||
}
|
||||
Chest chest = (Chest) attached.getState();
|
||||
Location chestLoc = chest.getLocation();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
// Item-Typ ermitteln:
|
||||
// Normales Schild → Index 2 (Zeile 3) enthält den Item-Namen (z.B. "IRON_ORE")
|
||||
// Clean-Schild → Item steht nicht auf dem Schild, per DB/YAML ermitteln
|
||||
String itemType = null;
|
||||
if (isCleanTargetSign(sign)) {
|
||||
itemType = findItemForChestLocation(playerUUID, chestLoc);
|
||||
if (itemType == null || itemType.isEmpty()) {
|
||||
player.sendMessage(getMessage("priority-item-not-found"));
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
String line2 = sign.getLine(2); // Index 2 = dritte Zeile = Item-Name
|
||||
if (line2 != null && !line2.trim().isEmpty()) {
|
||||
itemType = ChatColor.stripColor(line2).trim().toUpperCase().replace(' ', '_');
|
||||
}
|
||||
}
|
||||
if (itemType == null || itemType.isEmpty()) {
|
||||
player.sendMessage(getMessage("priority-item-unknown"));
|
||||
return true;
|
||||
}
|
||||
boolean found = false;
|
||||
if (mysqlEnabled && mysqlManager != null) {
|
||||
// MySQL: Zieltruhe suchen und Prio setzen
|
||||
List<Map<String, Object>> targets = mysqlManager.getTargetChestsForItem(playerUUID.toString(), itemType);
|
||||
for (Map<String, Object> t : targets) {
|
||||
String w = (String) t.get("world");
|
||||
int x = (int) t.get("x");
|
||||
int y = (int) t.get("y");
|
||||
int z = (int) t.get("z");
|
||||
int slot = (int) t.get("slot");
|
||||
if (w.equals(chestLoc.getWorld().getName()) && x == chestLoc.getBlockX() && y == chestLoc.getBlockY() && z == chestLoc.getBlockZ()) {
|
||||
mysqlManager.setTargetChestPrio(playerUUID.toString(), itemType, slot, prio);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// YAML: Slot der Zieltruhe finden und Prio setzen
|
||||
String base = "players." + playerUUID + ".target-chests." + itemType;
|
||||
if (playerData.contains(base) && playerData.isConfigurationSection(base)) {
|
||||
for (String slotKey : playerData.getConfigurationSection(base).getKeys(false)) {
|
||||
String p = base + "." + slotKey;
|
||||
if (playerData.getString(p + ".world", "").equals(chestLoc.getWorld().getName()) &&
|
||||
playerData.getInt(p + ".x") == chestLoc.getBlockX() &&
|
||||
playerData.getInt(p + ".y") == chestLoc.getBlockY() &&
|
||||
playerData.getInt(p + ".z") == chestLoc.getBlockZ()) {
|
||||
playerData.set(p + ".prio", prio);
|
||||
savePlayerData();
|
||||
flushPlayerData(); // sofort schreiben, nicht erst nach 30s
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
player.sendMessage(getMessage("priority-success").replace("%prio%", String.valueOf(prio)));
|
||||
} else {
|
||||
player.sendMessage(getMessage("priority-not-found"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
String lang = config != null ? config.getString("language", "de") : "de";
|
||||
if (lang == null) lang = "de";
|
||||
|
||||
@@ -2084,7 +2246,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
&& !args[0].equalsIgnoreCase("import")
|
||||
&& !args[0].equalsIgnoreCase("export")
|
||||
&& !args[0].equalsIgnoreCase("list"))) {
|
||||
sender.sendMessage(ChatColor.RED + "Dieser Befehl ist nur für Spieler! (Konsole: reload, import, export, list)");
|
||||
sender.sendMessage(getMessage("console-only"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2192,12 +2354,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isEn = lang.equalsIgnoreCase("en");
|
||||
|
||||
if (args.length < 2) {
|
||||
sender.sendMessage(ChatColor.RED + (isEn
|
||||
? "Usage: /asc list <player>"
|
||||
: "Verwendung: /asc list <Spieler>"));
|
||||
sender.sendMessage(getMessage("list-usage"));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2219,9 +2377,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
}
|
||||
}
|
||||
if (target == null) {
|
||||
sender.sendMessage(ChatColor.RED + (isEn
|
||||
? "Player '" + targetName + "' was not found!"
|
||||
: "Spieler '" + targetName + "' wurde nicht gefunden!"));
|
||||
sender.sendMessage(getMessage("list-player-not-found").replace("%name%", targetName));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2229,6 +2385,18 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
final String uuidStr = finalTarget.getUniqueId().toString();
|
||||
final String displayName = finalTarget.getName() != null ? finalTarget.getName() : targetName;
|
||||
|
||||
// Labels vorab aus config lesen (thread-sicher, da Main-Thread)
|
||||
final String cfgHeader = getMessage("list-header");
|
||||
final String cfgTitle = getMessage("list-title");
|
||||
final String cfgPlayerLabel = getMessage("list-player-label");
|
||||
final String cfgOffline = getMessage("list-offline");
|
||||
final String cfgInputLabel = getMessage("list-input-label");
|
||||
final String cfgTargetLabel = getMessage("list-target-label");
|
||||
final String cfgRestLabel = getMessage("list-rest-label");
|
||||
final String cfgTrashLabel = getMessage("list-trash-label");
|
||||
final String cfgUnlimited = getMessage("list-unlimited");
|
||||
final String cfgFooter = getMessage("list-footer");
|
||||
|
||||
// Zählen (async, da ggf. DB-Abfragen)
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
@@ -2266,59 +2434,49 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
trashCount = playerData.contains("players." + uuidStr + ".trash-chest.world") ? 1 : 0;
|
||||
}
|
||||
|
||||
// Limits bestimmen
|
||||
// Spieler ist offline → Limits können nicht per Permission geprüft werden
|
||||
// → nur die Anzahl anzeigen, kein " / X"
|
||||
final boolean isOffline = !finalTarget.isOnline();
|
||||
|
||||
String inputMax, targetMax, restMax;
|
||||
final String trashMax = "1";
|
||||
|
||||
if (isOffline || !chestLimitsEnabled) {
|
||||
// Offline: kein Limit anzeigen
|
||||
// Limits deaktiviert: unbegrenzt
|
||||
inputMax = isOffline ? null : "*";
|
||||
targetMax = isOffline ? null : "*";
|
||||
restMax = isOffline ? null : "*";
|
||||
inputMax = isOffline ? null : cfgUnlimited;
|
||||
targetMax = isOffline ? null : cfgUnlimited;
|
||||
restMax = isOffline ? null : cfgUnlimited;
|
||||
} else {
|
||||
Player onlineTarget = (Player) finalTarget;
|
||||
if (onlineTarget.isOp() || onlineTarget.hasPermission("autosortchest.limit.bypass")) {
|
||||
inputMax = "*";
|
||||
targetMax = "*";
|
||||
restMax = "*";
|
||||
inputMax = cfgUnlimited;
|
||||
targetMax = cfgUnlimited;
|
||||
restMax = cfgUnlimited;
|
||||
} else {
|
||||
int iMax = getChestLimitForPlayer(onlineTarget, "input");
|
||||
int tMax = getChestLimitForPlayer(onlineTarget, "target");
|
||||
int rMax = getChestLimitForPlayer(onlineTarget, "rest");
|
||||
inputMax = (iMax == Integer.MAX_VALUE) ? "*" : String.valueOf(iMax);
|
||||
targetMax = (tMax == Integer.MAX_VALUE) ? "*" : String.valueOf(tMax);
|
||||
restMax = (rMax == Integer.MAX_VALUE) ? "*" : String.valueOf(rMax);
|
||||
inputMax = (iMax == Integer.MAX_VALUE) ? cfgUnlimited : String.valueOf(iMax);
|
||||
targetMax = (tMax == Integer.MAX_VALUE) ? cfgUnlimited : String.valueOf(tMax);
|
||||
restMax = (rMax == Integer.MAX_VALUE) ? cfgUnlimited : String.valueOf(rMax);
|
||||
}
|
||||
}
|
||||
|
||||
// Sprachabhängige Label
|
||||
final String labelPlayer = isEn ? "Player: " : "Spieler: ";
|
||||
final String labelTarget = isEn ? "Target: " : "Ziel: ";
|
||||
final String labelTrash = isEn ? "Trash: " : "Müll: ";
|
||||
|
||||
final String fInputMax = inputMax, fTargetMax = targetMax, fRestMax = restMax;
|
||||
final int fIn = inputCount, fTa = targetCount, fRe = restCount, fTr = trashCount;
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sender.sendMessage(ChatColor.GOLD + "================================");
|
||||
sender.sendMessage(ChatColor.GOLD + "" + ChatColor.BOLD + "==== AutoSortChest Info ====");
|
||||
sender.sendMessage(ChatColor.YELLOW + labelPlayer + ChatColor.WHITE + displayName
|
||||
+ (isOffline ? ChatColor.GRAY + " (offline)" : ""));
|
||||
sender.sendMessage(ChatColor.YELLOW + "Input: " + ChatColor.WHITE
|
||||
sender.sendMessage(cfgHeader);
|
||||
sender.sendMessage(cfgTitle);
|
||||
sender.sendMessage(cfgPlayerLabel + ChatColor.WHITE + displayName
|
||||
+ (isOffline ? cfgOffline : ""));
|
||||
sender.sendMessage(cfgInputLabel + ChatColor.WHITE
|
||||
+ (fInputMax != null ? fIn + " / " + fInputMax : String.valueOf(fIn)));
|
||||
sender.sendMessage(ChatColor.YELLOW + labelTarget + ChatColor.WHITE
|
||||
sender.sendMessage(cfgTargetLabel + ChatColor.WHITE
|
||||
+ (fTargetMax != null ? fTa + " / " + fTargetMax : String.valueOf(fTa)));
|
||||
sender.sendMessage(ChatColor.YELLOW + "Rest: " + ChatColor.WHITE
|
||||
sender.sendMessage(cfgRestLabel + ChatColor.WHITE
|
||||
+ (fRestMax != null ? fRe + " / " + fRestMax : String.valueOf(fRe)));
|
||||
sender.sendMessage(ChatColor.YELLOW + labelTrash + ChatColor.WHITE
|
||||
sender.sendMessage(cfgTrashLabel + ChatColor.WHITE
|
||||
+ (isOffline ? String.valueOf(fTr) : fTr + " / " + trashMax));
|
||||
sender.sendMessage(ChatColor.GOLD + "================================");
|
||||
sender.sendMessage(cfgFooter);
|
||||
}
|
||||
}.runTask(Main.this);
|
||||
}
|
||||
@@ -2335,17 +2493,17 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
if (!mysqlEnabled || mysqlManager == null) {
|
||||
sender.sendMessage(ChatColor.RED + "MySQL ist nicht aktiviert! Aktiviere MySQL in der config.yml zuerst.");
|
||||
sender.sendMessage(getMessage("mysql-not-enabled-import"));
|
||||
return true;
|
||||
}
|
||||
if (playerData == null || playerData.getConfigurationSection("players") == null
|
||||
|| playerData.getConfigurationSection("players").getKeys(false).isEmpty()) {
|
||||
sender.sendMessage(ChatColor.RED + "Die players.yml ist leer oder enthält keine Spielerdaten!");
|
||||
sender.sendMessage(getMessage("yaml-empty"));
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.YELLOW + "Importiere Daten aus players.yml nach MySQL...");
|
||||
sender.sendMessage(ChatColor.GRAY + "Bestehende MySQL-Daten werden nicht überschrieben (REPLACE INTO).");
|
||||
sender.sendMessage(getMessage("import-start"));
|
||||
sender.sendMessage(getMessage("import-info"));
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
@@ -2439,11 +2597,11 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
|
||||
final int fp = playerCount, fi = inputCount, ft = targetCount, fr = restCount;
|
||||
Bukkit.getScheduler().runTask(Main.this, () -> {
|
||||
sender.sendMessage(ChatColor.GREEN + "Import erfolgreich abgeschlossen!");
|
||||
sender.sendMessage(ChatColor.GRAY + " Spieler: " + ChatColor.WHITE + fp);
|
||||
sender.sendMessage(ChatColor.GRAY + " Eingangstruhen: " + ChatColor.WHITE + fi);
|
||||
sender.sendMessage(ChatColor.GRAY + " Zieltruhen: " + ChatColor.WHITE + ft);
|
||||
sender.sendMessage(ChatColor.GRAY + " Rest-Truhen: " + ChatColor.WHITE + fr);
|
||||
sender.sendMessage(getMessage("import-success"));
|
||||
sender.sendMessage(getMessage("import-stats-players").replace("%players%", String.valueOf(fp)));
|
||||
sender.sendMessage(getMessage("import-stats-input") .replace("%input%", String.valueOf(fi)));
|
||||
sender.sendMessage(getMessage("import-stats-target").replace("%target%", String.valueOf(ft)));
|
||||
sender.sendMessage(getMessage("import-stats-rest") .replace("%rest%", String.valueOf(fr)));
|
||||
getLogger().info("Import durch " + sender.getName() + " abgeschlossen: "
|
||||
+ fp + " Spieler, " + fi + " Input, " + ft + " Target, " + fr + " Rest.");
|
||||
});
|
||||
@@ -2461,12 +2619,12 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
if (!mysqlEnabled || mysqlManager == null) {
|
||||
sender.sendMessage(ChatColor.RED + "MySQL ist nicht aktiviert! Der Export benötigt eine aktive MySQL-Verbindung.");
|
||||
sender.sendMessage(getMessage("mysql-not-enabled-export"));
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.YELLOW + "Exportiere Daten aus MySQL nach players.yml...");
|
||||
sender.sendMessage(ChatColor.GRAY + "Ein Backup der aktuellen players.yml wird erstellt.");
|
||||
sender.sendMessage(getMessage("export-start"));
|
||||
sender.sendMessage(getMessage("export-info"));
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
@@ -2483,8 +2641,9 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
try {
|
||||
java.nio.file.Files.copy(playerDataFile.toPath(), backupFile.toPath());
|
||||
} catch (IOException e) {
|
||||
final String errMsg = e.getMessage();
|
||||
Bukkit.getScheduler().runTask(Main.this, () ->
|
||||
sender.sendMessage(ChatColor.RED + "Backup fehlgeschlagen: " + e.getMessage()));
|
||||
sender.sendMessage(getMessage("backup-failed").replace("%error%", errMsg)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2515,7 +2674,6 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
List<Map<String, Object>> targetChests = mysqlManager.getTargetChests(uuidString);
|
||||
for (Map<String, Object> chest : targetChests) {
|
||||
String item = (String) chest.get("item");
|
||||
// FIX: export in new slotted format (slot 0)
|
||||
String path = "players." + uuidString + ".target-chests." + item + ".0";
|
||||
exportData.set(path + ".world", chest.get("world"));
|
||||
exportData.set(path + ".x", chest.get("x"));
|
||||
@@ -2540,30 +2698,31 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
|
||||
exportData.save(playerDataFile);
|
||||
|
||||
final FileConfiguration finalExport = exportData;
|
||||
final FileConfiguration finalExport = exportData;
|
||||
final int fp = playerCount, fi = inputCount, ft = targetCount, fr = restCount;
|
||||
final String finalBackupName = backupName;
|
||||
|
||||
Bukkit.getScheduler().runTask(Main.this, () -> {
|
||||
playerData = finalExport;
|
||||
sender.sendMessage(ChatColor.GREEN + "Export erfolgreich abgeschlossen!");
|
||||
sender.sendMessage(getMessage("export-success"));
|
||||
if (finalBackupName != null) {
|
||||
sender.sendMessage(ChatColor.GRAY + " Backup: " + ChatColor.WHITE + finalBackupName);
|
||||
sender.sendMessage(getMessage("export-backup").replace("%file%", finalBackupName));
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.GRAY + " Backup: " + ChatColor.DARK_GRAY + "Übersprungen (players.yml war leer)");
|
||||
sender.sendMessage(getMessage("export-backup-skipped"));
|
||||
}
|
||||
sender.sendMessage(ChatColor.GRAY + " Spieler: " + ChatColor.WHITE + fp);
|
||||
sender.sendMessage(ChatColor.GRAY + " Eingangstruhen: " + ChatColor.WHITE + fi);
|
||||
sender.sendMessage(ChatColor.GRAY + " Zieltruhen: " + ChatColor.WHITE + ft);
|
||||
sender.sendMessage(ChatColor.GRAY + " Rest-Truhen: " + ChatColor.WHITE + fr);
|
||||
sender.sendMessage(getMessage("import-stats-players").replace("%players%", String.valueOf(fp)));
|
||||
sender.sendMessage(getMessage("import-stats-input") .replace("%input%", String.valueOf(fi)));
|
||||
sender.sendMessage(getMessage("import-stats-target").replace("%target%", String.valueOf(ft)));
|
||||
sender.sendMessage(getMessage("import-stats-rest") .replace("%rest%", String.valueOf(fr)));
|
||||
getLogger().info("Export durch " + sender.getName() + " abgeschlossen: "
|
||||
+ fp + " Spieler, " + fi + " Input, " + ft + " Target, " + fr + " Rest."
|
||||
+ (finalBackupName != null ? " Backup: " + finalBackupName : " Kein Backup."));
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
final String errMsg = e.getMessage();
|
||||
Bukkit.getScheduler().runTask(Main.this, () ->
|
||||
sender.sendMessage(ChatColor.RED + "Export fehlgeschlagen: " + e.getMessage()));
|
||||
sender.sendMessage(getMessage("export-error").replace("%error%", errMsg)));
|
||||
getLogger().warning("Export fehlgeschlagen: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -2571,6 +2730,25 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// /asc autosign – Schild automatisch an Truhe setzen
|
||||
// -------------------------------------------------------
|
||||
if (args[0].equalsIgnoreCase("autosign")) {
|
||||
if (player == null) {
|
||||
sender.sendMessage(getMessage("autosign-player-only"));
|
||||
return true;
|
||||
}
|
||||
if (!player.hasPermission("autosortchest.use")) {
|
||||
player.sendMessage(getMessage("no-permission"));
|
||||
return true;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(getMessage("autosign-usage"));
|
||||
return true;
|
||||
}
|
||||
return handleAutoSign(player, args);
|
||||
}
|
||||
|
||||
// Unbekannter Befehl → Hilfe (nur für Spieler)
|
||||
if (player == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Verwendung: /asc [reload|import|export]");
|
||||
@@ -2582,6 +2760,416 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// TAB COMPLETER
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
if (!command.getName().equalsIgnoreCase("asc")) return Collections.emptyList();
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
if (args.length == 1) {
|
||||
// /asc <subcommand>
|
||||
List<String> subs = new ArrayList<>(Arrays.asList(
|
||||
"help", "info", "autosign", "priority"));
|
||||
if (sender.hasPermission("autosortchest.reload")) subs.add("reload");
|
||||
if (sender.hasPermission("autosortchest.import")) subs.add("import");
|
||||
if (sender.hasPermission("autosortchest.export")) subs.add("export");
|
||||
if (sender.hasPermission("autosortchest.list")) subs.add("list");
|
||||
String partial = args[0].toLowerCase();
|
||||
for (String s : subs) {
|
||||
if (s.toLowerCase().startsWith(partial)) result.add(s);
|
||||
}
|
||||
Collections.sort(result);
|
||||
|
||||
} else if (args.length == 2) {
|
||||
|
||||
if (args[0].equalsIgnoreCase("autosign")) {
|
||||
// /asc autosign <typ>
|
||||
String lang = config != null ? config.getString("language", "de") : "de";
|
||||
List<String> types;
|
||||
if ("en".equalsIgnoreCase(lang)) {
|
||||
types = Arrays.asList("input", "target", "rest", "trash");
|
||||
} else {
|
||||
types = Arrays.asList("input", "ziel", "rest", "trash");
|
||||
}
|
||||
String partial = args[1].toLowerCase();
|
||||
for (String t : types) {
|
||||
if (t.startsWith(partial)) result.add(t);
|
||||
}
|
||||
|
||||
} else if (args[0].equalsIgnoreCase("list")) {
|
||||
// /asc list <Spieler>
|
||||
String partial = args[1].toLowerCase();
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (p.getName().toLowerCase().startsWith(partial)) result.add(p.getName());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("priority")) {
|
||||
// /asc priority <Nummer>
|
||||
String partial = args[1];
|
||||
for (int i = 1; i <= 20; i++) {
|
||||
String s = String.valueOf(i);
|
||||
if (s.startsWith(partial)) result.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (args.length == 3
|
||||
&& args[0].equalsIgnoreCase("autosign")
|
||||
&& (args[1].equalsIgnoreCase("ziel") || args[1].equalsIgnoreCase("target"))) {
|
||||
// /asc autosign ziel <item|hand>
|
||||
String partial = args[2].toLowerCase();
|
||||
result.add("hand");
|
||||
for (Material mat : Material.values()) {
|
||||
if (mat.isAir() || !mat.isItem()) continue;
|
||||
String name = mat.name().toLowerCase();
|
||||
if (name.startsWith(partial)) {
|
||||
result.add(name);
|
||||
if (result.size() >= 60) break; // Lag-Schutz
|
||||
}
|
||||
}
|
||||
result = result.stream()
|
||||
.filter(s -> s.startsWith(partial))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// /asc autosign – Implementierung
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Implementiert /asc autosign – platziert automatisch ein ASC-Schild
|
||||
* an der Truhe, auf die der Spieler gerade schaut (max. 5 Blöcke).
|
||||
*
|
||||
* Syntax: /asc autosign <input|ziel|rest|trash> [item|hand]
|
||||
* – input / rest / trash : kein weiteres Argument nötig
|
||||
* – ziel / target : [item] = Material-Name (z.B. IRON_ORE)
|
||||
* [hand] = nimmt das Item in der Haupthand
|
||||
* (kein Argument) = wie "hand"
|
||||
*/
|
||||
private boolean handleAutoSign(Player player, String[] args) {
|
||||
|
||||
// ── 1. Ziel-Block ermitteln ──────────────────────────────────────────
|
||||
Block targetBlock = player.getTargetBlockExact(5);
|
||||
if (targetBlock == null || !(targetBlock.getState() instanceof Chest)) {
|
||||
player.sendMessage(getMessage("autosign-no-chest"));
|
||||
return true;
|
||||
}
|
||||
if (isWorldBlacklisted(targetBlock.getWorld())) {
|
||||
player.sendMessage(getMessage("world-blacklisted"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── 2. Typ normalisieren ─────────────────────────────────────────────
|
||||
String typRaw = args[1].toLowerCase();
|
||||
switch (typRaw) {
|
||||
case "target": typRaw = "ziel"; break;
|
||||
case "müll": case "muell": typRaw = "trash"; break;
|
||||
default: break;
|
||||
}
|
||||
if (!typRaw.equals("input") && !typRaw.equals("ziel")
|
||||
&& !typRaw.equals("rest") && !typRaw.equals("trash")) {
|
||||
player.sendMessage(getMessage("autosign-invalid-type"));
|
||||
return true;
|
||||
}
|
||||
final String typ = typRaw;
|
||||
|
||||
// ── 3. Material für Zieltruhe ermitteln ──────────────────────────────
|
||||
Material targetMaterial = null;
|
||||
if (typ.equals("ziel")) {
|
||||
if (args.length >= 3 && !args[2].equalsIgnoreCase("hand")) {
|
||||
// Expliziter Material-Name angegeben
|
||||
targetMaterial = Material.matchMaterial(args[2].toUpperCase());
|
||||
if (targetMaterial == null || targetMaterial == Material.AIR) {
|
||||
player.sendMessage(getMessage("autosign-unknown-item").replace("%item%", args[2]));
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Item in Haupthand verwenden
|
||||
ItemStack handItem = player.getInventory().getItemInMainHand();
|
||||
if (handItem == null || handItem.getType() == Material.AIR) {
|
||||
player.sendMessage(getMessage("no-item-in-hand"));
|
||||
return true;
|
||||
}
|
||||
targetMaterial = handItem.getType();
|
||||
}
|
||||
}
|
||||
final Material finalMaterial = targetMaterial;
|
||||
|
||||
|
||||
// ── 4. Freie Schildfläche an der Truhe suchen (Blickrichtung bevorzugen) ──
|
||||
Chest chestState = (Chest) targetBlock.getState();
|
||||
List<Block> chestBlocks = getChestBlocks(chestState);
|
||||
|
||||
org.bukkit.block.BlockFace[] faces = {
|
||||
org.bukkit.block.BlockFace.NORTH, org.bukkit.block.BlockFace.SOUTH,
|
||||
org.bukkit.block.BlockFace.EAST, org.bukkit.block.BlockFace.WEST
|
||||
};
|
||||
|
||||
Block signBlock = null;
|
||||
org.bukkit.block.BlockFace signFacing = null;
|
||||
Block chestBlock = null;
|
||||
|
||||
// Blickrichtung des Spielers zur Truhe bestimmen (von Truhe zum Spieler!)
|
||||
org.bukkit.util.Vector eye = player.getEyeLocation().toVector();
|
||||
org.bukkit.util.Vector chest = targetBlock.getLocation().add(0.5, 0.5, 0.5).toVector();
|
||||
org.bukkit.util.Vector dir = eye.clone().subtract(chest).normalize();
|
||||
double maxDot = -2.0;
|
||||
org.bukkit.block.BlockFace facingFace = null;
|
||||
for (org.bukkit.block.BlockFace face : faces) {
|
||||
org.bukkit.util.Vector faceVec = new org.bukkit.util.Vector(face.getModX(), face.getModY(), face.getModZ());
|
||||
double dot = dir.dot(faceVec);
|
||||
if (dot > maxDot) {
|
||||
maxDot = dot;
|
||||
facingFace = face;
|
||||
}
|
||||
}
|
||||
|
||||
// Zuerst versuchen, auf die Seite zu setzen, die der Spieler anschaut
|
||||
outer:
|
||||
for (Block cb : chestBlocks) {
|
||||
Block adj = cb.getRelative(facingFace);
|
||||
if (!(adj.getState() instanceof Sign existSign && isSignAttachedToChest(adj, cb)) &&
|
||||
(adj.getType() == Material.AIR || adj.getType() == Material.CAVE_AIR || adj.getType() == Material.VOID_AIR)) {
|
||||
signBlock = adj;
|
||||
signFacing = facingFace;
|
||||
chestBlock = cb;
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
|
||||
// Falls dort kein Platz ist, wie bisher freie Seite suchen
|
||||
if (signBlock == null) {
|
||||
outer2:
|
||||
for (Block cb : chestBlocks) {
|
||||
for (org.bukkit.block.BlockFace face : faces) {
|
||||
Block adj = cb.getRelative(face);
|
||||
if (adj.getState() instanceof Sign existSign && isSignAttachedToChest(adj, cb)) {
|
||||
continue;
|
||||
}
|
||||
if (adj.getType() == Material.AIR || adj.getType() == Material.CAVE_AIR || adj.getType() == Material.VOID_AIR) {
|
||||
signBlock = adj;
|
||||
signFacing = face;
|
||||
chestBlock = cb;
|
||||
break outer2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (signBlock == null) {
|
||||
player.sendMessage(getMessage("autosign-no-space"));
|
||||
return true;
|
||||
}
|
||||
|
||||
final Block finalChestBlock = chestBlock;
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
// ── 5. Limit-Prüfung ────────────────────────────────────────────────
|
||||
if (chestLimitsEnabled
|
||||
&& !isAdmin(player)
|
||||
&& !player.hasPermission("autosortchest.limit.bypass")) {
|
||||
|
||||
switch (typ) {
|
||||
case "input": {
|
||||
int maxInput = getChestLimitForPlayer(player, "input");
|
||||
if (maxInput == 0) {
|
||||
player.sendMessage(getMessage("limit-no-permission"));
|
||||
return true;
|
||||
}
|
||||
int currentInput = autoSignCountInputChests(playerUUID);
|
||||
boolean alreadyInput = autoSignIsAlreadyInputChest(playerUUID, finalChestBlock);
|
||||
if (!alreadyInput && currentInput >= maxInput) {
|
||||
player.sendMessage(getMessage("limit-input-reached")
|
||||
.replace("%max%", String.valueOf(maxInput)));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "rest": {
|
||||
int maxRest = getChestLimitForPlayer(player, "rest");
|
||||
if (maxRest == 0) {
|
||||
player.sendMessage(getMessage("limit-no-permission"));
|
||||
return true;
|
||||
}
|
||||
int currentRest = autoSignCountRestChests(playerUUID);
|
||||
if (currentRest >= maxRest) {
|
||||
player.sendMessage(getMessage("limit-rest-reached")
|
||||
.replace("%max%", String.valueOf(maxRest)));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ziel": {
|
||||
int maxChests = getChestLimitForPlayer(player, "target");
|
||||
int maxPerItem = getChestLimitForPlayer(player, "target_per_item");
|
||||
if (maxChests == 0) {
|
||||
player.sendMessage(getMessage("limit-no-permission"));
|
||||
return true;
|
||||
}
|
||||
Set<String> uniqueLocs = new HashSet<>();
|
||||
int countForThisItem = 0;
|
||||
String thisLocKey = finalChestBlock.getWorld().getName() + ":"
|
||||
+ finalChestBlock.getX() + ":" + finalChestBlock.getY()
|
||||
+ ":" + finalChestBlock.getZ();
|
||||
|
||||
if (mysqlEnabled && mysqlManager != null) {
|
||||
for (Map<String, Object> 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 && bw.getBlockAt(tx, ty, tz).getState() instanceof Chest) {
|
||||
uniqueLocs.add(w + ":" + tx + ":" + ty + ":" + tz);
|
||||
if (finalMaterial.name().equals(map.get("item"))) countForThisItem++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String basePath = "players." + playerUUID + ".target-chests";
|
||||
if (playerData.contains(basePath)) {
|
||||
for (String item : playerData.getConfigurationSection(basePath).getKeys(false)) {
|
||||
for (Location slotLoc : getTargetChestSlotsYaml(playerUUID, item)) {
|
||||
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) {
|
||||
uniqueLocs.add(bw.getName() + ":" + tx + ":" + ty + ":" + tz);
|
||||
if (finalMaterial.name().equals(item)) countForThisItem++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean alreadyTarget = uniqueLocs.contains(thisLocKey);
|
||||
if (!alreadyTarget && uniqueLocs.size() >= maxChests) {
|
||||
player.sendMessage(getMessage("limit-target-reached")
|
||||
.replace("%max%", String.valueOf(maxChests)));
|
||||
return true;
|
||||
}
|
||||
if (!alreadyTarget && countForThisItem >= maxPerItem) {
|
||||
player.sendMessage(getMessage("limit-target-per-item")
|
||||
.replace("%max%", String.valueOf(maxPerItem))
|
||||
.replace("%item%", finalMaterial.name()));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "trash": {
|
||||
// Trash-Limit: kein eigenes Limit-System, nur Permission-Check
|
||||
if (chestLimitsEnabled
|
||||
&& getChestLimitForPlayer(player, "input") == 0
|
||||
&& getChestLimitForPlayer(player, "target") == 0) {
|
||||
player.sendMessage(getMessage("limit-no-permission"));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 6. Schild-Block setzen ───────────────────────────────────────────
|
||||
signBlock.setType(Material.OAK_WALL_SIGN);
|
||||
org.bukkit.block.data.type.WallSign wallSignData =
|
||||
(org.bukkit.block.data.type.WallSign) signBlock.getBlockData();
|
||||
wallSignData.setFacing(signFacing);
|
||||
signBlock.setBlockData(wallSignData);
|
||||
|
||||
BlockState rawState = signBlock.getState();
|
||||
if (!(rawState instanceof Sign)) {
|
||||
player.sendMessage(getMessage("autosign-place-error"));
|
||||
signBlock.setType(Material.AIR);
|
||||
return true;
|
||||
}
|
||||
Sign sign = (Sign) rawState;
|
||||
|
||||
// ── 7. Schildtext + Registrierung ────────────────────────────────────
|
||||
Location chestLoc = finalChestBlock.getLocation();
|
||||
switch (typ) {
|
||||
case "input": {
|
||||
updateSignToCurrentStyle(sign, "input", null, player.getName(), false, false);
|
||||
sign.update();
|
||||
setInputChestLocation(playerUUID, chestLoc);
|
||||
player.sendMessage(getMessage("input-chest-set"));
|
||||
break;
|
||||
}
|
||||
case "ziel": {
|
||||
String itemDisplay = TrashChestManager.formatMaterialName(finalMaterial.name());
|
||||
if (itemDisplay.length() > 15) itemDisplay = itemDisplay.substring(0, 15);
|
||||
updateSignToCurrentStyle(sign, "target", itemDisplay, player.getName(), false, false);
|
||||
sign.update();
|
||||
setTargetChestLocation(playerUUID, chestLoc, finalMaterial);
|
||||
player.sendMessage(getMessage("target-chest-set").replace("%item%", finalMaterial.name()));
|
||||
break;
|
||||
}
|
||||
case "rest": {
|
||||
updateSignToCurrentStyle(sign, "rest", null, player.getName(), false, false);
|
||||
sign.update();
|
||||
setRestChestLocation(playerUUID, chestLoc);
|
||||
player.sendMessage(getMessage("rest-chest-set"));
|
||||
break;
|
||||
}
|
||||
case "trash": {
|
||||
updateSignToCurrentStyle(sign, "trash", null, player.getName(), false, false);
|
||||
sign.update();
|
||||
trashChestManager.setTrashChestLocation(playerUUID, chestLoc);
|
||||
player.sendMessage(getMessage("trash-chest-set"));
|
||||
player.sendMessage(getMessage("trash-chest-hint"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Hilfs-Methoden für Limit-Prüfung in handleAutoSign ───────────────────
|
||||
|
||||
private int autoSignCountInputChests(UUID playerUUID) {
|
||||
if (mysqlEnabled && mysqlManager != null) {
|
||||
return mysqlManager.getInputChests(playerUUID.toString()).size();
|
||||
}
|
||||
String basePath = "players." + playerUUID + ".input-chests";
|
||||
return playerData.contains(basePath)
|
||||
? playerData.getConfigurationSection(basePath).getKeys(false).size() : 0;
|
||||
}
|
||||
|
||||
private boolean autoSignIsAlreadyInputChest(UUID playerUUID, Block chestBlock) {
|
||||
if (mysqlEnabled && mysqlManager != null) {
|
||||
return mysqlManager.getInputChests(playerUUID.toString()).stream().anyMatch(c -> {
|
||||
String w = (String) c.get("world");
|
||||
return w != null && w.equals(chestBlock.getWorld().getName())
|
||||
&& (int) c.get("x") == chestBlock.getX()
|
||||
&& (int) c.get("y") == chestBlock.getY()
|
||||
&& (int) c.get("z") == chestBlock.getZ();
|
||||
});
|
||||
}
|
||||
String basePath = "players." + playerUUID + ".input-chests";
|
||||
if (playerData.contains(basePath)) {
|
||||
for (String id : playerData.getConfigurationSection(basePath).getKeys(false)) {
|
||||
String p = basePath + "." + id;
|
||||
if (chestBlock.getWorld().getName().equals(playerData.getString(p + ".world"))
|
||||
&& chestBlock.getX() == playerData.getInt(p + ".x")
|
||||
&& chestBlock.getY() == playerData.getInt(p + ".y")
|
||||
&& chestBlock.getZ() == playerData.getInt(p + ".z")) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int autoSignCountRestChests(UUID playerUUID) {
|
||||
if (mysqlEnabled && mysqlManager != null) {
|
||||
return mysqlManager.getRestChests(playerUUID.toString()).size();
|
||||
}
|
||||
String basePath = "players." + playerUUID + ".rest-chests";
|
||||
if (playerData.contains(basePath) && playerData.isConfigurationSection(basePath)) {
|
||||
return playerData.getConfigurationSection(basePath).getKeys(false).size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onSignChange(SignChangeEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
@@ -3574,8 +4162,20 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
// MySQL-Fallback: Besitzer anhand der Location direkt in der DB suchen.
|
||||
// Notwendig wenn players.yml leer ist (MySQL-Modus) oder der Name-Lookup fehlschlug.
|
||||
if (ownerUUID == null && mysqlEnabled && mysqlManager != null) {
|
||||
ownerUUID = mysqlManager.findOwnerByLocation(
|
||||
chestLoc.getWorld().getName(),
|
||||
chestLoc.getBlockX(), chestLoc.getBlockY(), chestLoc.getBlockZ());
|
||||
}
|
||||
|
||||
UUID uuidToDelete = (ownerUUID != null) ? ownerUUID : player.getUniqueId();
|
||||
|
||||
// YAML-Public-Cache immer leeren, unabhängig vom Typ (kein Leak nach Abbau)
|
||||
removeFromYamlPublicCache(chestLoc.getWorld().getName(),
|
||||
chestLoc.getBlockX(), chestLoc.getBlockY(), chestLoc.getBlockZ());
|
||||
|
||||
if (line1.equalsIgnoreCase("rest")) {
|
||||
if (mysqlEnabled && mysqlManager != null) {
|
||||
mysqlManager.removeRestChestByLocation(uuidToDelete.toString(),
|
||||
|
||||
@@ -4,6 +4,36 @@ 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();
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
@@ -598,12 +628,24 @@ public class MySQLManager {
|
||||
return all.isEmpty() ? null : all.get(0);
|
||||
}
|
||||
|
||||
/** Gibt ALLE Zieltruhen für ein bestimmtes Item zurück, sortiert nach slot. */
|
||||
/** Gibt ALLE Zieltruhen für ein bestimmtes Item zurück, sortiert nach prio absteigend, dann slot aufsteigend. */
|
||||
public List<Map<String, Object>> getTargetChestsForItem(String uuid, String item) {
|
||||
ensureConnected();
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
try (PreparedStatement ps = connection.prepareStatement(
|
||||
"SELECT * FROM asc_target_chests WHERE uuid=? AND item=? ORDER BY slot ASC;")) {
|
||||
// Prio 1 zuerst, Prio 2 danach usw. — Prio 0 (nicht gesetzt) kommt ans Ende.
|
||||
// CASE WHEN vermeidet eine separate Abfrage ob die Spalte existiert.
|
||||
String sql;
|
||||
try {
|
||||
if (columnExists("asc_target_chests", "prio")) {
|
||||
sql = "SELECT * FROM asc_target_chests WHERE uuid=? AND item=? " +
|
||||
"ORDER BY CASE WHEN COALESCE(prio,0)=0 THEN 999 ELSE COALESCE(prio,0) END ASC, slot ASC;";
|
||||
} else {
|
||||
sql = "SELECT * FROM asc_target_chests WHERE uuid=? AND item=? ORDER BY slot ASC;";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sql = "SELECT * FROM asc_target_chests WHERE uuid=? AND item=? ORDER BY slot ASC;";
|
||||
}
|
||||
try (PreparedStatement ps = connection.prepareStatement(sql)) {
|
||||
ps.setString(1, uuid);
|
||||
ps.setString(2, item);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
@@ -616,6 +658,7 @@ public class MySQLManager {
|
||||
map.put("y", rs.getInt("y"));
|
||||
map.put("z", rs.getInt("z"));
|
||||
map.put("public", rs.getBoolean("public"));
|
||||
try { map.put("prio", rs.getInt("prio")); } catch (SQLException ignored) { map.put("prio", 0); }
|
||||
try { map.put("server", rs.getString("server")); }
|
||||
catch (SQLException ignored) { map.put("server", ""); }
|
||||
list.add(map);
|
||||
@@ -1051,6 +1094,45 @@ public class MySQLManager {
|
||||
// FIX: Einzelne UNION-Abfrage statt 3 separater Queries für isChestPublic()
|
||||
// Reduziert Main-Thread-Blockierung bei MySQL um ~66%.
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
/**
|
||||
* Sucht den Besitzer einer Truhen-Location in ALLEN ASC-Tabellen (UNION).
|
||||
* Wird im onBlockBreak-Fallback verwendet wenn der Name-Lookup keine UUID liefert
|
||||
* (z.B. MySQL-Modus mit leerer players.yml).
|
||||
*
|
||||
* @return UUID des Besitzers oder null wenn nicht gefunden.
|
||||
*/
|
||||
public UUID findOwnerByLocation(String world, int x, int y, int z) {
|
||||
ensureConnected();
|
||||
String sql =
|
||||
"(SELECT uuid FROM asc_input_chests WHERE world=? AND x=? AND y=? AND z=? LIMIT 1) " +
|
||||
"UNION ALL " +
|
||||
"(SELECT uuid FROM asc_target_chests WHERE world=? AND x=? AND y=? AND z=? LIMIT 1) " +
|
||||
"UNION ALL " +
|
||||
"(SELECT uuid FROM asc_rest_chests WHERE world=? AND x=? AND y=? AND z=? LIMIT 1) " +
|
||||
"UNION ALL " +
|
||||
"(SELECT uuid FROM asc_trash_chests WHERE world=? AND x=? AND y=? AND z=? LIMIT 1) " +
|
||||
"LIMIT 1";
|
||||
try (PreparedStatement ps = connection.prepareStatement(sql)) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int base = i * 4;
|
||||
ps.setString(base + 1, world);
|
||||
ps.setInt (base + 2, x);
|
||||
ps.setInt (base + 3, y);
|
||||
ps.setInt (base + 4, z);
|
||||
}
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
String uuidStr = rs.getString("uuid");
|
||||
rs.close();
|
||||
try { return UUID.fromString(uuidStr); } catch (Exception ignored) { return null; }
|
||||
}
|
||||
rs.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob eine Truhen-Location in IRGENDEINER Tabelle als public markiert ist.
|
||||
* Kombiniert Input-, Target- und Rest-Tabelle in einer einzigen UNION-Abfrage.
|
||||
|
||||
@@ -62,9 +62,32 @@ public class TrashChestManager {
|
||||
"http://textures.minecraft.net/texture/32518d04f9c06c95dd0edad617abb93d3d8657f01e659079d330cca6f65bccf7";
|
||||
|
||||
private String getGuiTitle() {
|
||||
String colorPrefix = getChestTitleColor();
|
||||
String label = isEnglish() ? "Configure Trash Chest" : "Mülltruhe konfigurieren";
|
||||
return colorPrefix + ChatColor.BOLD + label;
|
||||
return getGuiText("title");
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest einen einzelnen Text aus dem trash-gui-Abschnitt der config.yml.
|
||||
* Wählt automatisch die richtige Sprache (de/en). Farbcodes werden übersetzt.
|
||||
*/
|
||||
private String getGuiText(String key) {
|
||||
String lang = isEnglish() ? "en" : "de";
|
||||
String val = plugin.getConfig().getString("trash-gui." + key + "." + lang);
|
||||
if (val == null) val = plugin.getConfig().getString("trash-gui." + key + ".de", key);
|
||||
return ChatColor.translateAlternateColorCodes('&', val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest eine mehrzeilige Lore-Liste aus dem trash-gui-Abschnitt der config.yml.
|
||||
* Wählt automatisch die richtige Sprache (de/en). Farbcodes werden übersetzt.
|
||||
*/
|
||||
private List<String> getGuiLore(String key) {
|
||||
String lang = isEnglish() ? "en" : "de";
|
||||
List<String> list = plugin.getConfig().getStringList("trash-gui." + key + "." + lang);
|
||||
if (list.isEmpty()) list = plugin.getConfig().getStringList("trash-gui." + key + ".de");
|
||||
List<String> result = new ArrayList<>(list.size());
|
||||
for (String line : list)
|
||||
result.add(ChatColor.translateAlternateColorCodes('&', line));
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isEnglish() {
|
||||
@@ -273,21 +296,44 @@ public class TrashChestManager {
|
||||
saveTrashChest(uuid);
|
||||
}
|
||||
|
||||
public void removeTrashChest(UUID uuid) {
|
||||
Location loc = trashChestLocations.remove(uuid);
|
||||
if (loc != null) locationToOwner.remove(locKey(loc));
|
||||
/**
|
||||
* Entfernt Mülltruhen-Eintrag für eine bestimmte Location (z.B. beim Abbau der Kiste).
|
||||
* Falls mehrere Mülltruhen pro Spieler möglich sind, wird nur der passende Eintrag entfernt.
|
||||
* Falls keine Location angegeben, wird wie bisher nach UUID gelöscht.
|
||||
*/
|
||||
public void removeTrashChest(UUID uuid, Location loc) {
|
||||
trashChestLocations.remove(uuid);
|
||||
locationToOwner.remove(locKey(loc));
|
||||
trashFilterLists.remove(uuid);
|
||||
if (plugin.isMysqlEnabled() && plugin.getMysqlManager() != null) {
|
||||
plugin.getMysqlManager().removeTrashChest(uuid.toString());
|
||||
plugin.getMysqlManager().removeAllTrashItems(uuid.toString());
|
||||
} else {
|
||||
FileConfiguration data = plugin.getPlayerData();
|
||||
data.set("players." + uuid + ".trash-chest", null);
|
||||
data.set("players." + uuid + ".trash-items", null);
|
||||
String uuidStr = uuid.toString();
|
||||
String base = "players." + uuidStr + ".trash-chest";
|
||||
// Prüfe, ob die gespeicherte Location mit der zu entfernenden übereinstimmt
|
||||
if (data.contains(base + ".world")) {
|
||||
String w = data.getString(base + ".world");
|
||||
int x = data.getInt(base + ".x");
|
||||
int y = data.getInt(base + ".y");
|
||||
int z = data.getInt(base + ".z");
|
||||
if (w != null && loc != null &&
|
||||
w.equals(loc.getWorld().getName()) &&
|
||||
x == loc.getBlockX() && y == loc.getBlockY() && z == loc.getBlockZ()) {
|
||||
data.set(base, null);
|
||||
data.set("players." + uuidStr + ".trash-items", null);
|
||||
}
|
||||
}
|
||||
plugin.savePlayerDataPublic();
|
||||
}
|
||||
}
|
||||
|
||||
// Für Kompatibilität: alter Aufruf ohne Location löscht wie bisher alles für die UUID
|
||||
public void removeTrashChest(UUID uuid) {
|
||||
removeTrashChest(uuid, trashChestLocations.get(uuid));
|
||||
}
|
||||
|
||||
public UUID getTrashChestOwner(Location loc) { return locationToOwner.get(locKey(loc)); }
|
||||
public Location getTrashChestLocation(UUID uuid) { return trashChestLocations.get(uuid); }
|
||||
public Map<UUID, Location> getAllTrashChests() { return new HashMap<>(trashChestLocations); }
|
||||
@@ -449,7 +495,6 @@ public class TrashChestManager {
|
||||
|
||||
public void openConfigGui(Player player, UUID ownerUUID, int page) {
|
||||
openGuiOwners.put(player.getUniqueId(), ownerUUID);
|
||||
boolean isEn = isEnglish();
|
||||
|
||||
List<ItemStack> filter = trashFilterLists.getOrDefault(ownerUUID, new ArrayList<>());
|
||||
|
||||
@@ -469,15 +514,14 @@ public class TrashChestManager {
|
||||
// ── Items der aktuellen Seite ────────────────────────────────────────
|
||||
int startIndex = page * ITEMS_PER_PAGE;
|
||||
int endIndex = Math.min(startIndex + ITEMS_PER_PAGE, validItems.size());
|
||||
String removeHint = getGuiText("item-remove-hint");
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
ItemStack filterItem = validItems.get(i);
|
||||
// Clone mit Menge 1
|
||||
ItemStack display = filterItem.clone();
|
||||
display.setAmount(1);
|
||||
|
||||
ItemMeta meta = display.getItemMeta();
|
||||
if (meta != null) {
|
||||
// Anzeige-Namen setzen wenn kein eigener vorhanden
|
||||
if (!meta.hasDisplayName()) {
|
||||
meta.setDisplayName(getSignColor("trash", "line1") + "" + ChatColor.BOLD
|
||||
+ formatMaterialName(filterItem.getType().name()));
|
||||
@@ -507,7 +551,7 @@ public class TrashChestManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tränkeffekte (Trank, Wurftrank, Pfeil: PotionMeta) ──────────
|
||||
// ── Tränkeffekte (PotionMeta) ────────────────────────────────────
|
||||
if (meta instanceof PotionMeta pm) {
|
||||
List<PotionEffect> effects = pm.getCustomEffects();
|
||||
if (!effects.isEmpty()) {
|
||||
@@ -539,7 +583,7 @@ public class TrashChestManager {
|
||||
lore.add("");
|
||||
}
|
||||
|
||||
lore.add(ChatColor.RED + (isEn ? "▶ Right-click: Remove" : "▶ Rechtsklick: Entfernen"));
|
||||
lore.add(removeHint);
|
||||
meta.setLore(lore);
|
||||
display.setItemMeta(meta);
|
||||
}
|
||||
@@ -557,10 +601,10 @@ public class TrashChestManager {
|
||||
ItemStack prev = new ItemStack(Material.ARROW);
|
||||
ItemMeta prevMeta = prev.getItemMeta();
|
||||
if (prevMeta != null) {
|
||||
prevMeta.setDisplayName(ChatColor.YELLOW + "" + ChatColor.BOLD
|
||||
+ (isEn ? "◀ Previous Page" : "◀ Vorherige Seite"));
|
||||
prevMeta.setLore(Arrays.asList(ChatColor.GRAY
|
||||
+ (isEn ? "Page " : "Seite ") + page + (isEn ? " of " : " von ") + totalPages));
|
||||
prevMeta.setDisplayName(getGuiText("btn-prev-title"));
|
||||
prevMeta.setLore(Arrays.asList(getGuiText("page-nav-lore")
|
||||
.replace("%page%", String.valueOf(page))
|
||||
.replace("%total%", String.valueOf(totalPages))));
|
||||
prev.setItemMeta(prevMeta);
|
||||
}
|
||||
gui.setItem(45, prev);
|
||||
@@ -570,10 +614,11 @@ public class TrashChestManager {
|
||||
ItemStack pageInfo = new ItemStack(Material.PAPER);
|
||||
ItemMeta pageMeta = pageInfo.getItemMeta();
|
||||
if (pageMeta != null) {
|
||||
pageMeta.setDisplayName(ChatColor.WHITE + "" + ChatColor.BOLD
|
||||
+ (isEn ? "Page " : "Seite ") + (page + 1) + " / " + totalPages);
|
||||
pageMeta.setLore(Arrays.asList(ChatColor.GRAY + "" + validItems.size()
|
||||
+ (isEn ? " items in filter" : " Items im Filter")));
|
||||
pageMeta.setDisplayName(getGuiText("page-info-title")
|
||||
.replace("%page%", String.valueOf(page + 1))
|
||||
.replace("%total%", String.valueOf(totalPages)));
|
||||
pageMeta.setLore(Arrays.asList(getGuiText("page-info-lore")
|
||||
.replace("%count%", String.valueOf(validItems.size()))));
|
||||
pageInfo.setItemMeta(pageMeta);
|
||||
}
|
||||
gui.setItem(46, pageInfo);
|
||||
@@ -583,10 +628,10 @@ public class TrashChestManager {
|
||||
ItemStack next = new ItemStack(Material.ARROW);
|
||||
ItemMeta nextMeta = next.getItemMeta();
|
||||
if (nextMeta != null) {
|
||||
nextMeta.setDisplayName(ChatColor.YELLOW + "" + ChatColor.BOLD
|
||||
+ (isEn ? "Next Page ▶" : "Nächste Seite ▶"));
|
||||
nextMeta.setLore(Arrays.asList(ChatColor.GRAY
|
||||
+ (isEn ? "Page " : "Seite ") + (page + 2) + (isEn ? " of " : " von ") + totalPages));
|
||||
nextMeta.setDisplayName(getGuiText("btn-next-title"));
|
||||
nextMeta.setLore(Arrays.asList(getGuiText("page-nav-lore")
|
||||
.replace("%page%", String.valueOf(page + 2))
|
||||
.replace("%total%", String.valueOf(totalPages))));
|
||||
next.setItemMeta(nextMeta);
|
||||
}
|
||||
gui.setItem(47, next);
|
||||
@@ -597,24 +642,11 @@ public class TrashChestManager {
|
||||
ItemMeta modeMeta = modeInfo.getItemMeta();
|
||||
if (modeMeta != null) {
|
||||
if (filter.isEmpty()) {
|
||||
modeMeta.setDisplayName(ChatColor.RED + "" + ChatColor.BOLD
|
||||
+ (isEn ? "✗ Status: Disabled" : "✗ Status: Deaktiviert"));
|
||||
modeMeta.setLore(Arrays.asList(
|
||||
ChatColor.GRAY + (isEn ? "No filter set –" : "Kein Filter gesetzt –"),
|
||||
ChatColor.GRAY + (isEn ? "items will NOT be deleted." : "Items werden NICHT gelöscht."),
|
||||
ChatColor.YELLOW + (isEn
|
||||
? "Add items to activate the trash chest."
|
||||
: "Füge Items hinzu um die Mülltruhe zu aktivieren.")));
|
||||
modeMeta.setDisplayName(getGuiText("status-disabled-title"));
|
||||
modeMeta.setLore(getGuiLore("status-disabled-lore"));
|
||||
} else {
|
||||
modeMeta.setDisplayName(ChatColor.GREEN + "" + ChatColor.BOLD
|
||||
+ (isEn ? "✔ Status: Active" : "✔ Status: Aktiv"));
|
||||
modeMeta.setLore(Arrays.asList(
|
||||
ChatColor.GRAY + (isEn
|
||||
? "Items are matched exactly:"
|
||||
: "Items werden exakt verglichen:"),
|
||||
ChatColor.GRAY + (isEn
|
||||
? "Type + enchantments + name must match."
|
||||
: "Typ + Verzauberungen + Name müssen übereinstimmen.")));
|
||||
modeMeta.setDisplayName(getGuiText("status-active-title"));
|
||||
modeMeta.setLore(getGuiLore("status-active-lore"));
|
||||
}
|
||||
modeInfo.setItemMeta(modeMeta);
|
||||
}
|
||||
@@ -624,18 +656,8 @@ public class TrashChestManager {
|
||||
ItemStack addBtn = new ItemStack(Material.LIME_STAINED_GLASS_PANE);
|
||||
ItemMeta addMeta = addBtn.getItemMeta();
|
||||
if (addMeta != null) {
|
||||
addMeta.setDisplayName(ChatColor.GREEN + "" + ChatColor.BOLD
|
||||
+ (isEn ? "✚ Add Item" : "✚ Item hinzufügen"));
|
||||
addMeta.setLore(Arrays.asList(
|
||||
ChatColor.GRAY + (isEn
|
||||
? "Hold the exact item in your main hand"
|
||||
: "Genau das Item in die Haupthand nehmen"),
|
||||
ChatColor.GRAY + (isEn
|
||||
? "and click this button."
|
||||
: "und diesen Button klicken."),
|
||||
ChatColor.YELLOW + (isEn
|
||||
? "Enchantments & name are saved exactly."
|
||||
: "Verzauberungen & Name werden exakt gespeichert.")));
|
||||
addMeta.setDisplayName(getGuiText("btn-add-title"));
|
||||
addMeta.setLore(getGuiLore("btn-add-lore"));
|
||||
addBtn.setItemMeta(addMeta);
|
||||
}
|
||||
gui.setItem(49, addBtn);
|
||||
@@ -648,10 +670,8 @@ public class TrashChestManager {
|
||||
}
|
||||
|
||||
private ItemStack buildSkullButton() {
|
||||
boolean isEn = isEnglish();
|
||||
String label = isEn ? "Empty Trash Chest" : "Mülltruhe leeren";
|
||||
String lore1 = isEn ? "Click to immediately" : "Klicken um alle Items";
|
||||
String lore2 = isEn ? "delete all items." : "sofort zu löschen.";
|
||||
String label = getGuiText("btn-clear-title");
|
||||
List<String> loreLines = getGuiLore("btn-clear-lore");
|
||||
|
||||
ItemStack skull;
|
||||
try {
|
||||
@@ -663,16 +683,16 @@ public class TrashChestManager {
|
||||
textures.setSkin(new URL(SKULL_TEXTURE));
|
||||
profile.setTextures(textures);
|
||||
meta.setOwnerProfile(profile);
|
||||
meta.setDisplayName(getChestTitleColor() + "" + ChatColor.BOLD + label);
|
||||
meta.setLore(Arrays.asList(ChatColor.GRAY + lore1, ChatColor.GRAY + lore2));
|
||||
meta.setDisplayName(label);
|
||||
meta.setLore(loreLines);
|
||||
skull.setItemMeta(meta);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
skull = new ItemStack(Material.RED_DYE);
|
||||
ItemMeta meta = skull.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(getChestTitleColor() + "" + ChatColor.BOLD + label);
|
||||
meta.setLore(Arrays.asList(ChatColor.GRAY + lore1, ChatColor.GRAY + lore2));
|
||||
meta.setDisplayName(label);
|
||||
meta.setLore(loreLines);
|
||||
skull.setItemMeta(meta);
|
||||
}
|
||||
}
|
||||
@@ -790,23 +810,6 @@ public class TrashChestManager {
|
||||
return ChatColor.translateAlternateColorCodes('&', raw);
|
||||
}
|
||||
|
||||
private String getChestTitleColor() {
|
||||
boolean isEn = isEnglish();
|
||||
String lang = isEn ? "en" : "de";
|
||||
String full = plugin.getConfig().getString("chest-titles.trash." + lang,
|
||||
isEn ? "&4Trash Chest" : "&4Mülltruhe");
|
||||
StringBuilder codes = new StringBuilder();
|
||||
for (int i = 0; i + 1 < full.length(); i++) {
|
||||
if (full.charAt(i) == '&' && "0123456789abcdefklmnor".indexOf(full.charAt(i + 1)) >= 0) {
|
||||
codes.append(full, i, i + 2);
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ChatColor.translateAlternateColorCodes('&', codes.length() > 0 ? codes.toString() : "&4");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lesbarer Anzeige-Name für Chat-Nachrichten.
|
||||
* Nutzt Custom Display Name falls vorhanden, sonst formatierten Material-Namen.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# ============================================================
|
||||
|
||||
# Version der Konfigurationsdatei – bitte nicht ändern!
|
||||
version: "2.3"
|
||||
version: "2.5"
|
||||
|
||||
# Debug-Modus: true = Ausführliche Logs in der Konsole (nur zum Entwickeln)
|
||||
debug: false
|
||||
@@ -313,3 +313,157 @@ messages:
|
||||
mode-changed: "&aModus gewechselt: &e%mode%"
|
||||
mode-public: "&aÖffentlich"
|
||||
mode-private: "&cPrivat"
|
||||
# --- Priorität-Befehl (/asc priority) ---
|
||||
# Platzhalter: %input% = eingegebene Zahl, %prio% = gesetzte Priorität
|
||||
priority-player-only: "&cDieser Befehl ist nur für Spieler!"
|
||||
priority-usage: "&cVerwendung: /asc priority <1-20>"
|
||||
priority-invalid-number: "&cUngültige Zahl: &e%input%"
|
||||
priority-out-of-range: "&cPriorität muss zwischen 1 und 20 liegen!"
|
||||
priority-no-sign: "&cDu schaust auf kein Schild!"
|
||||
priority-wrong-sign: "&cDas ist kein Ziel-Schild!"
|
||||
priority-not-attached: "&cDas Schild ist nicht an einer Truhe befestigt!"
|
||||
priority-item-unknown: "&cKonnte Item-Typ auf dem Schild nicht erkennen!"
|
||||
priority-item-not-found: "&cKonnte Item-Typ für dieses Schild nicht finden!"
|
||||
priority-success: "&aPriorität für Zieltruhe gesetzt: &e%prio%"
|
||||
priority-not-found: "&cKonnte die Zieltruhe zu diesem Schild nicht finden!"
|
||||
|
||||
# --- AutoSign-Befehl (/asc autosign) ---
|
||||
# Platzhalter: %item% = Item-Name
|
||||
autosign-no-chest: "&cDu schaust auf keine Truhe! &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!"
|
||||
autosign-place-error: "&cFehler beim Platzieren – bitte manuell versuchen."
|
||||
|
||||
# --- /asc list Ausgabe ---
|
||||
# Platzhalter: %name% = Spielername
|
||||
list-usage: "&cVerwendung: /asc list <Spieler>"
|
||||
list-player-not-found: "&cSpieler &e'%name%' &cwurde nicht gefunden!"
|
||||
list-header: "&6================================"
|
||||
list-title: "&6&l==== AutoSortChest Info ===="
|
||||
list-player-label: "&eSpieler: "
|
||||
list-offline: "&7 (offline)"
|
||||
list-input-label: "&eInput: "
|
||||
list-target-label: "&eZiel: "
|
||||
list-rest-label: "&eRest: "
|
||||
list-trash-label: "&eMüll: "
|
||||
list-unlimited: "*"
|
||||
list-footer: "&6================================"
|
||||
|
||||
# --- Konsolen-Hinweis (wenn Spieler-Befehl per Konsole aufgerufen) ---
|
||||
console-only: "&cDieser Befehl ist nur für Spieler! (Konsole: reload, import, export, list)"
|
||||
|
||||
# ============================================================
|
||||
# MÜLLTRUHEN-GUI
|
||||
# ============================================================
|
||||
# Alle Texte des Konfigurations-GUIs der Mülltruhe.
|
||||
# Farbcodes: &0-&9, &a-&f | &l = Fett, &o = Kursiv
|
||||
# Platzhalter: %page% = Seitennummer, %total% = Gesamtseiten, %count% = Anzahl Items
|
||||
|
||||
trash-gui:
|
||||
|
||||
# Fenstertitel
|
||||
title:
|
||||
de: "&4Mülltruhe konfigurieren"
|
||||
en: "&4Configure Trash Chest"
|
||||
|
||||
# Hinweis unter jedem Filter-Item (Rechtsklick zum Entfernen)
|
||||
item-remove-hint:
|
||||
de: "&c▶ Rechtsklick: Entfernen"
|
||||
en: "&c▶ Right-click: Remove"
|
||||
|
||||
# Navigations-Pfeile
|
||||
btn-prev-title:
|
||||
de: "&e&l◀ Vorherige Seite"
|
||||
en: "&e&l◀ Previous Page"
|
||||
btn-next-title:
|
||||
de: "&e&lNächste Seite ▶"
|
||||
en: "&e&lNext Page ▶"
|
||||
|
||||
# Seitenanzeige (Slot 46)
|
||||
page-info-title:
|
||||
de: "&f&lSeite %page% / %total%"
|
||||
en: "&f&lPage %page% / %total%"
|
||||
page-info-lore:
|
||||
de: "&7%count% Items im Filter"
|
||||
en: "&7%count% items in filter"
|
||||
page-nav-lore:
|
||||
de: "Seite %page% von %total%"
|
||||
en: "Page %page% of %total%"
|
||||
|
||||
# Status-Anzeige: Deaktiviert (kein Filter gesetzt)
|
||||
status-disabled-title:
|
||||
de: "&c&l✗ Status: Deaktiviert"
|
||||
en: "&c&l✗ Status: Disabled"
|
||||
status-disabled-lore:
|
||||
de:
|
||||
- "&7Kein Filter gesetzt –"
|
||||
- "&7Items werden NICHT gelöscht."
|
||||
- "&eItems hinzufügen um zu aktivieren."
|
||||
en:
|
||||
- "&7No filter set –"
|
||||
- "&7items will NOT be deleted."
|
||||
- "&eAdd items to activate."
|
||||
|
||||
# Status-Anzeige: Aktiv (Filter gesetzt)
|
||||
status-active-title:
|
||||
de: "&a&l✔ Status: Aktiv"
|
||||
en: "&a&l✔ Status: Active"
|
||||
status-active-lore:
|
||||
de:
|
||||
- "&7Items werden exakt verglichen:"
|
||||
- "&7Typ + Verzauberungen + Name."
|
||||
en:
|
||||
- "&7Items are matched exactly:"
|
||||
- "&7Type + enchantments + name."
|
||||
|
||||
# Item hinzufügen (Slot 49)
|
||||
btn-add-title:
|
||||
de: "&a&l✚ Item hinzufügen"
|
||||
en: "&a&l✚ Add Item"
|
||||
btn-add-lore:
|
||||
de:
|
||||
- "&7Gewünschtes Item in die Haupthand nehmen"
|
||||
- "&7und diesen Knopf klicken."
|
||||
- "&eVerzauberungen & Name werden gespeichert."
|
||||
en:
|
||||
- "&7Hold the exact item in your main hand"
|
||||
- "&7and click this button."
|
||||
- "&eEnchantments & name are saved exactly."
|
||||
|
||||
# Mülltruhe leeren (Slot 53 – Schädel-Button)
|
||||
btn-clear-title:
|
||||
de: "&4&lMülltruhe leeren"
|
||||
en: "&4&lEmpty Trash Chest"
|
||||
btn-clear-lore:
|
||||
de:
|
||||
- "&7Klicken um alle Items"
|
||||
- "&7sofort zu löschen."
|
||||
en:
|
||||
- "&7Click to immediately"
|
||||
- "&7delete all items."
|
||||
|
||||
# --- /asc import / export (Admin-Befehle) ---
|
||||
mysql-not-enabled-import: "&cMySQL ist nicht aktiviert! Aktiviere MySQL in der config.yml zuerst."
|
||||
mysql-not-enabled-export: "&cMySQL ist nicht aktiviert! Der Export benötigt eine aktive MySQL-Verbindung."
|
||||
yaml-empty: "&cDie players.yml ist leer oder enthält keine Spielerdaten!"
|
||||
import-start: "&eImportiere Daten aus players.yml nach MySQL..."
|
||||
import-info: "&7Bestehende MySQL-Daten werden nicht überschrieben (REPLACE INTO)."
|
||||
import-success: "&aImport erfolgreich abgeschlossen!"
|
||||
# Platzhalter: %players%, %input%, %target%, %rest%
|
||||
import-stats-players: "&7 Spieler: &f%players%"
|
||||
import-stats-input: "&7 Eingangstruhen: &f%input%"
|
||||
import-stats-target: "&7 Zieltruhen: &f%target%"
|
||||
import-stats-rest: "&7 Rest-Truhen: &f%rest%"
|
||||
export-start: "&eExportiere Daten aus MySQL nach players.yml..."
|
||||
export-info: "&7Ein Backup der aktuellen players.yml wird erstellt."
|
||||
export-success: "&aExport erfolgreich abgeschlossen!"
|
||||
# Platzhalter: %file% = Backup-Dateiname
|
||||
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 <input|ziel|rest|trash> [item|hand]"
|
||||
@@ -1,5 +1,5 @@
|
||||
name: AutoSortChest
|
||||
version: 2.6
|
||||
version: 2.7
|
||||
main: com.viper.autosortchest.Main
|
||||
api-version: 1.21
|
||||
authors: [M_Viper]
|
||||
@@ -7,11 +7,11 @@ description: Ein Plugin zum automatischen Sortieren von Items in Truhen
|
||||
commands:
|
||||
asc:
|
||||
description: AutoSortChest Befehle
|
||||
usage: /<command> [help|info|reload|import|export|list]
|
||||
usage: /<command> [help|info|reload|import|export|list|autosign|priority]
|
||||
aliases: [autosortchest]
|
||||
permissions:
|
||||
autosortchest.use:
|
||||
description: Erlaubt das Erstellen von AutoSortChest-Schildern (Eingang, Ziel, Rest, Muelltruhe)
|
||||
description: Erlaubt das Erstellen von AutoSortChest-Schildern (Eingang, Ziel, Rest, Muelltruhe) sowie die Verwendung von /asc autosign
|
||||
default: true
|
||||
autosortchest.reload:
|
||||
description: Erlaubt das Neuladen der Konfiguration mit /asc reload
|
||||
|
||||
Reference in New Issue
Block a user