2 Commits
2.5 ... main

Author SHA1 Message Date
26aad1c54b Upload file pom.xml via GUI 2026-03-04 11:41:23 +01:00
4d72b64866 Update from Git Manager GUI 2026-03-04 11:41:13 +01:00
7 changed files with 3163 additions and 720 deletions

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.viper</groupId> <groupId>com.viper</groupId>
<artifactId>AutoSortChest</artifactId> <artifactId>AutoSortChest</artifactId>
<version>2.3</version> <version>2.5</version>
<name>AutoSortChest</name> <name>AutoSortChest</name>
<repositories> <repositories>

File diff suppressed because it is too large Load Diff

View File

@@ -111,6 +111,26 @@ public class MySQLManager {
} }
} }
/**
* Stellt sicher, dass die Verbindung offen ist.
* Reconnect-Logik für den Fall, dass die MySQL-Verbindung abgelaufen ist
* (z.B. nach wait_timeout, Netzwerk-Unterbrechung).
* Wird vor jeder DB-Operation aufgerufen.
*/
private void ensureConnected() {
try {
if (connection == null || connection.isClosed() || !connection.isValid(2)) {
connection = DriverManager.getConnection(
"jdbc:mysql://" + host + ":" + port + "/" + database
+ "?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC",
user, password
);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// ═══════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════
// BungeeCord-Erweiterung: Sanfte Schema-Migration // BungeeCord-Erweiterung: Sanfte Schema-Migration
// Fügt eine Spalte hinzu, falls sie noch nicht existiert. // Fügt eine Spalte hinzu, falls sie noch nicht existiert.
@@ -179,6 +199,7 @@ public class MySQLManager {
* asc_transfers (CrossLink Transfers) * asc_transfers (CrossLink Transfers)
*/ */
public void setupTables() { public void setupTables() {
ensureConnected();
try (Statement st = connection.createStatement()) { try (Statement st = connection.createStatement()) {
// ── Basis-Tabellen anlegen (falls nicht vorhanden) ──────────────────── // ── Basis-Tabellen anlegen (falls nicht vorhanden) ────────────────────
@@ -229,6 +250,17 @@ public class MySQLManager {
// Jede Migration ist idempotent mehrfaches Ausführen schadet nicht. // Jede Migration ist idempotent mehrfaches Ausführen schadet nicht.
// ══════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════
// ── Mülltruchen-Tabellen ──────────────────────────────────────────────
st.execute("CREATE TABLE IF NOT EXISTS asc_trash_chests (" +
"uuid VARCHAR(36) PRIMARY KEY, world VARCHAR(32)," +
"x INT, y INT, z INT, server VARCHAR(64) DEFAULT ''" +
");");
st.execute("CREATE TABLE IF NOT EXISTS asc_trash_items (" +
"uuid VARCHAR(36), item VARCHAR(64)," +
"PRIMARY KEY(uuid, item)" +
");");
// v1 → v2 (BungeeCord): server-Spalten // v1 → v2 (BungeeCord): server-Spalten
tryAlterColumn(st, "asc_input_chests", "server", "VARCHAR(64) DEFAULT ''"); tryAlterColumn(st, "asc_input_chests", "server", "VARCHAR(64) DEFAULT ''");
tryAlterColumn(st, "asc_target_chests", "server", "VARCHAR(64) DEFAULT ''"); tryAlterColumn(st, "asc_target_chests", "server", "VARCHAR(64) DEFAULT ''");
@@ -265,6 +297,7 @@ public class MySQLManager {
*/ */
public void heartbeat(String serverName) { public void heartbeat(String serverName) {
if (serverName == null || serverName.isEmpty()) return; if (serverName == null || serverName.isEmpty()) return;
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"REPLACE INTO asc_servers (server_name, last_seen) VALUES (?, NOW());")) { "REPLACE INTO asc_servers (server_name, last_seen) VALUES (?, NOW());")) {
ps.setString(1, serverName); ps.setString(1, serverName);
@@ -280,6 +313,7 @@ public class MySQLManager {
*/ */
public void addTransfer(String uuid, String item, int amount, public void addTransfer(String uuid, String item, int amount,
String targetWorld, String targetServer, String sourceServer) { String targetWorld, String targetServer, String sourceServer) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"INSERT INTO asc_transfers (uuid, item, amount, target_world, target_server, source_server) " + "INSERT INTO asc_transfers (uuid, item, amount, target_world, target_server, source_server) " +
"VALUES (?, ?, ?, ?, ?, ?);")) { "VALUES (?, ?, ?, ?, ?, ?);")) {
@@ -348,6 +382,7 @@ public class MySQLManager {
} }
public void deleteTransfer(long id) { public void deleteTransfer(long id) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_transfers WHERE id=?;")) { "DELETE FROM asc_transfers WHERE id=?;")) {
ps.setLong(1, id); ps.setLong(1, id);
@@ -358,6 +393,7 @@ public class MySQLManager {
} }
public void updateTransferAmount(long id, int newAmount) { public void updateTransferAmount(long id, int newAmount) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"UPDATE asc_transfers SET amount=? WHERE id=?;")) { "UPDATE asc_transfers SET amount=? WHERE id=?;")) {
ps.setInt(1, newAmount); ps.setInt(1, newAmount);
@@ -371,6 +407,7 @@ public class MySQLManager {
// --- Spieler --- // --- Spieler ---
public void savePlayer(String uuid, String name) { public void savePlayer(String uuid, String name) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"REPLACE INTO asc_players (uuid, name) VALUES (?, ?);")) { "REPLACE INTO asc_players (uuid, name) VALUES (?, ?);")) {
ps.setString(1, uuid); ps.setString(1, uuid);
@@ -382,6 +419,7 @@ public class MySQLManager {
} }
public List<Map<String, Object>> getAllPlayers() { public List<Map<String, Object>> getAllPlayers() {
ensureConnected();
List<Map<String, Object>> list = new ArrayList<>(); List<Map<String, Object>> list = new ArrayList<>();
try (Statement st = connection.createStatement(); try (Statement st = connection.createStatement();
ResultSet rs = st.executeQuery("SELECT uuid, name FROM asc_players;")) { ResultSet rs = st.executeQuery("SELECT uuid, name FROM asc_players;")) {
@@ -410,6 +448,7 @@ public class MySQLManager {
/** BungeeCord-Überladung mit serverName. */ /** BungeeCord-Überladung mit serverName. */
public void addInputChest(String uuid, String chestId, String world, public void addInputChest(String uuid, String chestId, String world,
int x, int y, int z, boolean isPublic, String serverName) { int x, int y, int z, boolean isPublic, String serverName) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"REPLACE INTO asc_input_chests (uuid, chest_id, world, x, y, z, `public`, server) " + "REPLACE INTO asc_input_chests (uuid, chest_id, world, x, y, z, `public`, server) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?);")) { "VALUES (?, ?, ?, ?, ?, ?, ?, ?);")) {
@@ -428,6 +467,7 @@ public class MySQLManager {
} }
public void removeInputChest(String uuid, String chestId) { public void removeInputChest(String uuid, String chestId) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_input_chests WHERE uuid=? AND chest_id=?;")) { "DELETE FROM asc_input_chests WHERE uuid=? AND chest_id=?;")) {
ps.setString(1, uuid); ps.setString(1, uuid);
@@ -439,6 +479,7 @@ public class MySQLManager {
} }
public List<Map<String, Object>> getInputChests(String uuid) { public List<Map<String, Object>> getInputChests(String uuid) {
ensureConnected();
List<Map<String, Object>> list = new ArrayList<>(); List<Map<String, Object>> list = new ArrayList<>();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"SELECT * FROM asc_input_chests WHERE uuid=?;")) { "SELECT * FROM asc_input_chests WHERE uuid=?;")) {
@@ -481,6 +522,7 @@ public class MySQLManager {
/** Vollständige Überladung mit slot für Multi-Target-Support. */ /** Vollständige Überladung mit slot für Multi-Target-Support. */
public void setTargetChest(String uuid, String item, int slot, String world, public void setTargetChest(String uuid, String item, int slot, String world,
int x, int y, int z, boolean isPublic, String serverName) { int x, int y, int z, boolean isPublic, String serverName) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"REPLACE INTO asc_target_chests (uuid, item, slot, world, x, y, z, `public`, server) " + "REPLACE INTO asc_target_chests (uuid, item, slot, world, x, y, z, `public`, server) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);")) { "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);")) {
@@ -558,6 +600,7 @@ public class MySQLManager {
/** 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 slot. */
public List<Map<String, Object>> getTargetChestsForItem(String uuid, String item) { public List<Map<String, Object>> getTargetChestsForItem(String uuid, String item) {
ensureConnected();
List<Map<String, Object>> list = new ArrayList<>(); List<Map<String, Object>> list = new ArrayList<>();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"SELECT * FROM asc_target_chests WHERE uuid=? AND item=? ORDER BY slot ASC;")) { "SELECT * FROM asc_target_chests WHERE uuid=? AND item=? ORDER BY slot ASC;")) {
@@ -584,6 +627,7 @@ public class MySQLManager {
} }
public void removeTargetChest(String uuid, String item) { public void removeTargetChest(String uuid, String item) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_target_chests WHERE uuid=? AND item=?;")) { "DELETE FROM asc_target_chests WHERE uuid=? AND item=?;")) {
ps.setString(1, uuid); ps.setString(1, uuid);
@@ -596,6 +640,7 @@ public class MySQLManager {
/** Gibt alle Zieltruhen eines Spielers zurück, sortiert nach item + slot. */ /** Gibt alle Zieltruhen eines Spielers zurück, sortiert nach item + slot. */
public List<Map<String, Object>> getTargetChests(String uuid) { public List<Map<String, Object>> getTargetChests(String uuid) {
ensureConnected();
List<Map<String, Object>> list = new ArrayList<>(); List<Map<String, Object>> list = new ArrayList<>();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"SELECT * FROM asc_target_chests WHERE uuid=? ORDER BY item ASC, slot ASC;")) { "SELECT * FROM asc_target_chests WHERE uuid=? ORDER BY item ASC, slot ASC;")) {
@@ -645,6 +690,7 @@ public class MySQLManager {
/** Vollständige Überladung mit explizitem slot. */ /** Vollständige Überladung mit explizitem slot. */
public void setRestChest(String uuid, int slot, String world, int x, int y, int z, public void setRestChest(String uuid, int slot, String world, int x, int y, int z,
boolean isPublic, String serverName) { boolean isPublic, String serverName) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"REPLACE INTO asc_rest_chests (uuid, slot, world, x, y, z, `public`, server) " + "REPLACE INTO asc_rest_chests (uuid, slot, world, x, y, z, `public`, server) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?);")) { "VALUES (?, ?, ?, ?, ?, ?, ?, ?);")) {
@@ -696,6 +742,7 @@ public class MySQLManager {
/** Zählt wie viele Rest-Truhen ein Spieler hat. */ /** Zählt wie viele Rest-Truhen ein Spieler hat. */
public int countRestChests(String uuid) { public int countRestChests(String uuid) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"SELECT COUNT(*) FROM asc_rest_chests WHERE uuid=?;")) { "SELECT COUNT(*) FROM asc_rest_chests WHERE uuid=?;")) {
ps.setString(1, uuid); ps.setString(1, uuid);
@@ -709,6 +756,7 @@ public class MySQLManager {
/** Gibt ALLE Rest-Truhen eines Spielers zurück, sortiert nach slot. */ /** Gibt ALLE Rest-Truhen eines Spielers zurück, sortiert nach slot. */
public List<Map<String, Object>> getRestChests(String uuid) { public List<Map<String, Object>> getRestChests(String uuid) {
ensureConnected();
List<Map<String, Object>> list = new ArrayList<>(); List<Map<String, Object>> list = new ArrayList<>();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"SELECT * FROM asc_rest_chests WHERE uuid=? ORDER BY slot ASC;")) { "SELECT * FROM asc_rest_chests WHERE uuid=? ORDER BY slot ASC;")) {
@@ -740,6 +788,7 @@ public class MySQLManager {
/** Löscht eine spezifische Rest-Truhe anhand ihrer Location. */ /** Löscht eine spezifische Rest-Truhe anhand ihrer Location. */
public void removeRestChestByLocation(String uuid, String world, int x, int y, int z) { public void removeRestChestByLocation(String uuid, String world, int x, int y, int z) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement( try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_rest_chests WHERE uuid=? AND world=? AND x=? AND y=? AND z=?;")) { "DELETE FROM asc_rest_chests WHERE uuid=? AND world=? AND x=? AND y=? AND z=?;")) {
ps.setString(1, uuid); ps.setString(1, uuid);
@@ -870,4 +919,198 @@ public class MySQLManager {
} }
return null; return null;
} }
// ══════════════════════════════════════════════════════════════════════════
// MÜLLTRUCHEN (asc_trash_chests + asc_trash_items)
// ══════════════════════════════════════════════════════════════════════════
/** Legt eine Mülltruche an oder aktualisiert sie. */
public void setTrashChest(String uuid, String world, int x, int y, int z, String server) {
try (PreparedStatement ps = connection.prepareStatement(
"INSERT INTO asc_trash_chests (uuid, world, x, y, z, server) VALUES (?,?,?,?,?,?) " +
"ON DUPLICATE KEY UPDATE world=VALUES(world), x=VALUES(x), y=VALUES(y), z=VALUES(z), server=VALUES(server)")) {
ps.setString(1, uuid);
ps.setString(2, world);
ps.setInt(3, x);
ps.setInt(4, y);
ps.setInt(5, z);
ps.setString(6, server == null ? "" : server);
ps.executeUpdate();
} catch (SQLException e) { e.printStackTrace(); }
}
/** Gibt die Mülltruche eines Spielers zurück (nur die des eigenen Servers). */
public Map<String, Object> getTrashChest(String uuid, String serverName) {
try (PreparedStatement ps = connection.prepareStatement(
"SELECT * FROM asc_trash_chests WHERE uuid=? AND (server=? OR server='') LIMIT 1")) {
ps.setString(1, uuid);
ps.setString(2, serverName == null ? "" : serverName);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
Map<String, Object> map = new HashMap<>();
map.put("world", rs.getString("world"));
map.put("x", rs.getInt("x"));
map.put("y", rs.getInt("y"));
map.put("z", rs.getInt("z"));
map.put("server", rs.getString("server"));
return map;
}
} catch (SQLException e) { e.printStackTrace(); }
return null;
}
/** Gibt ALLE Mülltruchen zurück (für sign-update oder cross-server). */
public List<Map<String, Object>> getAllTrashChests() {
List<Map<String, Object>> list = new ArrayList<>();
try (PreparedStatement ps = connection.prepareStatement("SELECT * FROM asc_trash_chests")) {
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> map = new HashMap<>();
map.put("uuid", rs.getString("uuid"));
map.put("world", rs.getString("world"));
map.put("x", rs.getInt("x"));
map.put("y", rs.getInt("y"));
map.put("z", rs.getInt("z"));
map.put("server", rs.getString("server"));
list.add(map);
}
} catch (SQLException e) { e.printStackTrace(); }
return list;
}
/** Entfernt die Mülltruche eines Spielers. */
public void removeTrashChest(String uuid) {
try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_trash_chests WHERE uuid=?")) {
ps.setString(1, uuid);
ps.executeUpdate();
} catch (SQLException e) { e.printStackTrace(); }
}
/** Speichert die komplette Filter-Liste (ersetzt alte Einträge). */
public void setTrashItems(String uuid, List<String> items) {
try (PreparedStatement del = connection.prepareStatement(
"DELETE FROM asc_trash_items WHERE uuid=?")) {
del.setString(1, uuid);
del.executeUpdate();
} catch (SQLException e) { e.printStackTrace(); }
if (items == null || items.isEmpty()) return;
try (PreparedStatement ps = connection.prepareStatement(
"INSERT IGNORE INTO asc_trash_items (uuid, item) VALUES (?,?)")) {
for (String item : items) {
ps.setString(1, uuid);
ps.setString(2, item);
ps.addBatch();
}
ps.executeBatch();
} catch (SQLException e) { e.printStackTrace(); }
}
/** Lädt die Filter-Liste eines Spielers. */
public List<String> getTrashItems(String uuid) {
List<String> list = new ArrayList<>();
try (PreparedStatement ps = connection.prepareStatement(
"SELECT item FROM asc_trash_items WHERE uuid=?")) {
ps.setString(1, uuid);
ResultSet rs = ps.executeQuery();
while (rs.next()) list.add(rs.getString("item"));
} catch (SQLException e) { e.printStackTrace(); }
return list;
}
/** Fügt ein einzelnes Item zur Filter-Liste hinzu. */
public void addTrashItem(String uuid, String item) {
try (PreparedStatement ps = connection.prepareStatement(
"INSERT IGNORE INTO asc_trash_items (uuid, item) VALUES (?,?)")) {
ps.setString(1, uuid);
ps.setString(2, item);
ps.executeUpdate();
} catch (SQLException e) { e.printStackTrace(); }
}
/** Entfernt ein einzelnes Item aus der Filter-Liste. */
public void removeTrashItem(String uuid, String item) {
try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_trash_items WHERE uuid=? AND item=?")) {
ps.setString(1, uuid);
ps.setString(2, item);
ps.executeUpdate();
} catch (SQLException e) { e.printStackTrace(); }
}
/** Entfernt alle Filter-Items eines Spielers. */
public void removeAllTrashItems(String uuid) {
try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM asc_trash_items WHERE uuid=?")) {
ps.setString(1, uuid);
ps.executeUpdate();
} catch (SQLException e) { e.printStackTrace(); }
}
// ═══════════════════════════════════════════════════════════════════
// FIX: Einzelne UNION-Abfrage statt 3 separater Queries für isChestPublic()
// Reduziert Main-Thread-Blockierung bei MySQL um ~66%.
// ═══════════════════════════════════════════════════════════════════
/**
* Prüft ob eine Truhen-Location in IRGENDEINER Tabelle als public markiert ist.
* Kombiniert Input-, Target- und Rest-Tabelle in einer einzigen UNION-Abfrage.
* @return true wenn mindestens eine Zeile `public=TRUE` enthält.
*/
public boolean isLocationPublic(String world, int x, int y, int z) {
ensureConnected();
// MySQL erfordert Klammern um einzelne SELECT-Statements wenn LIMIT verwendet wird
String sql =
"(SELECT 1 FROM asc_input_chests WHERE world=? AND x=? AND y=? AND z=? AND `public`=TRUE LIMIT 1) " +
"UNION ALL " +
"(SELECT 1 FROM asc_target_chests WHERE world=? AND x=? AND y=? AND z=? AND `public`=TRUE LIMIT 1) " +
"UNION ALL " +
"(SELECT 1 FROM asc_rest_chests WHERE world=? AND x=? AND y=? AND z=? AND `public`=TRUE LIMIT 1)";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
for (int i = 0; i < 3; 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();
boolean result = rs.next();
rs.close();
return result;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
// ═══════════════════════════════════════════════════════════════════
// FIX: MySQL-Implementierung für findItemForChestLocation()
// Wurde bisher übersprungen Clean-Sign-Modus + MySQL war dadurch broken.
// ═══════════════════════════════════════════════════════════════════
/**
* Findet den Item-Typ (z.B. "IRON_ORE") der einer Truhen-Location als Zieltruhe
* zugewiesen ist. Wird von findItemForChestLocation() im Clean-Sign-Modus benötigt.
* @return Material-Name oder null wenn nicht gefunden.
*/
public String getItemForLocation(String uuid, String world, int x, int y, int z) {
ensureConnected();
try (PreparedStatement ps = connection.prepareStatement(
"SELECT item FROM asc_target_chests WHERE uuid=? AND world=? AND x=? AND y=? AND z=? LIMIT 1;")) {
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()) {
String item = rs.getString("item");
rs.close();
return item;
}
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
} }

View File

@@ -0,0 +1,512 @@
package com.viper.autosortchest;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Chest;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.profile.PlayerProfile;
import org.bukkit.profile.PlayerTextures;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Verwaltet Mülltruhen für das AutoSortChest-Plugin.
*
* Speicherung:
* MySQL (wenn aktiv):
* asc_trash_chests : uuid, world, x, y, z, server
* asc_trash_items : uuid, item
* YAML (Fallback):
* players.<uuid>.trash-chest.world / x / y / z
* players.<uuid>.trash-items (StringList)
*/
public class TrashChestManager {
private static final String SKULL_TEXTURE =
"http://textures.minecraft.net/texture/942e7fb9b8eae22d55e32b8222f38eca7b2c41948b15d769b716d80f9d113611";
/** Gibt den sprachabhängigen GUI-Titel zurück (Farbe aus chest-titles.trash). */
private String getGuiTitle() {
boolean isEn = "en".equalsIgnoreCase(plugin.getConfig().getString("language", "de"));
String colorPrefix = getChestTitleColor();
String label = isEn ? "Configure Trash Chest" : "Mülltruhe konfigurieren";
return colorPrefix + ChatColor.BOLD + label;
}
private final Main plugin;
/** UUID → Truhen-Location (In-Memory-Cache für diesen Server) */
private final Map<UUID, Location> trashChestLocations = new HashMap<>();
/** UUID → Filter-Liste */
private final Map<UUID, List<String>> trashFilterLists = new HashMap<>();
/** Location-Key → Besitzer-UUID */
private final Map<String, UUID> locationToOwner = new HashMap<>();
/** Spieler-UUID → Truhen-Besitzer-UUID (offene GUIs) */
private final Map<UUID, UUID> openGuiOwners = new HashMap<>();
private BukkitTask autoTrashTask = null;
public TrashChestManager(Main plugin) {
this.plugin = plugin;
loadAllTrashChests();
plugin.getServer().getPluginManager().registerEvents(new GuiListener(), plugin);
}
// ══════════════════════════════════════════════════════════════════════════
// LADEN
// ══════════════════════════════════════════════════════════════════════════
private void loadAllTrashChests() {
if (plugin.isMysqlEnabled() && plugin.getMysqlManager() != null) {
loadMySQL();
} else {
loadYaml();
}
}
private void loadMySQL() {
MySQLManager db = plugin.getMysqlManager();
String myServer = plugin.getServerName();
for (Map<String, Object> row : db.getAllTrashChests()) {
String srv = (String) row.getOrDefault("server", "");
if (!srv.isEmpty() && !srv.equals(myServer)) continue;
try {
UUID uuid = UUID.fromString((String) row.get("uuid"));
World world = Bukkit.getWorld((String) row.get("world"));
if (world == null) continue;
Location loc = new Location(world, (int) row.get("x"), (int) row.get("y"), (int) row.get("z"));
trashChestLocations.put(uuid, loc);
locationToOwner.put(locKey(loc), uuid);
trashFilterLists.put(uuid, new ArrayList<>(db.getTrashItems(uuid.toString())));
} catch (IllegalArgumentException ignored) {}
}
}
private void loadYaml() {
FileConfiguration data = plugin.getPlayerData();
if (data == null || data.getConfigurationSection("players") == null) return;
for (String uuidStr : data.getConfigurationSection("players").getKeys(false)) {
try {
UUID uuid = UUID.fromString(uuidStr);
String base = "players." + uuidStr + ".trash-chest";
if (!data.contains(base + ".world")) continue;
World world = Bukkit.getWorld(data.getString(base + ".world"));
if (world == null) continue;
Location loc = new Location(world,
data.getInt(base + ".x"), data.getInt(base + ".y"), data.getInt(base + ".z"));
trashChestLocations.put(uuid, loc);
locationToOwner.put(locKey(loc), uuid);
trashFilterLists.put(uuid, new ArrayList<>(data.getStringList("players." + uuidStr + ".trash-items")));
} catch (IllegalArgumentException ignored) {}
}
}
// ══════════════════════════════════════════════════════════════════════════
// SPEICHERN
// ══════════════════════════════════════════════════════════════════════════
private void saveTrashChest(UUID uuid) {
if (plugin.isMysqlEnabled() && plugin.getMysqlManager() != null) {
saveMySQL(uuid);
} else {
saveYaml(uuid);
}
}
private void saveMySQL(UUID uuid) {
MySQLManager db = plugin.getMysqlManager();
String uuidStr = uuid.toString();
Location loc = trashChestLocations.get(uuid);
if (loc != null && loc.getWorld() != null) {
db.setTrashChest(uuidStr, loc.getWorld().getName(),
loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(),
plugin.getServerName());
db.savePlayer(uuidStr, Bukkit.getOfflinePlayer(uuid).getName());
} else {
db.removeTrashChest(uuidStr);
}
db.setTrashItems(uuidStr, trashFilterLists.getOrDefault(uuid, new ArrayList<>()));
}
private void saveYaml(UUID uuid) {
FileConfiguration data = plugin.getPlayerData();
String uuidStr = uuid.toString();
Location loc = trashChestLocations.get(uuid);
if (loc != null && loc.getWorld() != null) {
String path = "players." + uuidStr + ".trash-chest";
data.set(path + ".world", loc.getWorld().getName());
data.set(path + ".x", loc.getBlockX());
data.set(path + ".y", loc.getBlockY());
data.set(path + ".z", loc.getBlockZ());
} else {
data.set("players." + uuidStr + ".trash-chest", null);
}
data.set("players." + uuidStr + ".trash-items",
trashFilterLists.getOrDefault(uuid, new ArrayList<>()));
plugin.savePlayerDataPublic();
}
// ══════════════════════════════════════════════════════════════════════════
// TRUHE VERWALTEN
// ══════════════════════════════════════════════════════════════════════════
public void setTrashChestLocation(UUID uuid, Location loc) {
Location old = trashChestLocations.get(uuid);
if (old != null) locationToOwner.remove(locKey(old));
trashChestLocations.put(uuid, loc);
locationToOwner.put(locKey(loc), uuid);
trashFilterLists.putIfAbsent(uuid, new ArrayList<>());
saveTrashChest(uuid);
}
public void removeTrashChest(UUID uuid) {
Location loc = trashChestLocations.remove(uuid);
if (loc != null) 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);
plugin.savePlayerDataPublic();
}
}
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);
}
public boolean isTrashItem(UUID uuid, Material mat) {
if (!trashChestLocations.containsKey(uuid)) return false;
List<String> filter = trashFilterLists.getOrDefault(uuid, new ArrayList<>());
// Leerer Filter = keine Items werden weitergeleitet (Mülltruhe deaktiviert)
return !filter.isEmpty() && filter.contains(mat.name());
}
// ══════════════════════════════════════════════════════════════════════════
// ITEM-VERARBEITUNG
// ══════════════════════════════════════════════════════════════════════════
public void processTrashChestInventory(UUID ownerUUID, Inventory inv) {
List<String> filter = trashFilterLists.getOrDefault(ownerUUID, new ArrayList<>());
// Leerer Filter = keine Items löschen (Mülltruhe deaktiviert bis Items konfiguriert sind)
if (filter.isEmpty()) return;
for (int i = 0; i < inv.getSize(); i++) {
ItemStack item = inv.getItem(i);
if (item == null || item.getType() == Material.AIR) continue;
if (filter.contains(item.getType().name())) inv.setItem(i, null);
}
}
public void clearTrashChest(UUID ownerUUID) {
Location loc = trashChestLocations.get(ownerUUID);
if (loc == null || loc.getWorld() == null) return;
if (loc.getBlock().getState() instanceof Chest chest) chest.getInventory().clear();
}
// ══════════════════════════════════════════════════════════════════════════
// FILTER-LISTE
// ══════════════════════════════════════════════════════════════════════════
public boolean addToFilter(UUID uuid, Material mat) {
List<String> filter = trashFilterLists.computeIfAbsent(uuid, k -> new ArrayList<>());
if (filter.contains(mat.name())) return false;
filter.add(mat.name());
if (plugin.isMysqlEnabled() && plugin.getMysqlManager() != null) {
plugin.getMysqlManager().addTrashItem(uuid.toString(), mat.name());
} else {
saveTrashChest(uuid);
}
return true;
}
public boolean removeFromFilter(UUID uuid, Material mat) {
List<String> filter = trashFilterLists.get(uuid);
if (filter == null) return false;
boolean removed = filter.remove(mat.name());
if (removed) {
if (plugin.isMysqlEnabled() && plugin.getMysqlManager() != null) {
plugin.getMysqlManager().removeTrashItem(uuid.toString(), mat.name());
} else {
saveTrashChest(uuid);
}
}
return removed;
}
public List<String> getFilter(UUID uuid) {
return trashFilterLists.getOrDefault(uuid, new ArrayList<>());
}
// ══════════════════════════════════════════════════════════════════════════
// AUTO-CLEAR-TASK
// ══════════════════════════════════════════════════════════════════════════
public void startAutoTrashTask() {
stopAutoTrashTask();
int intervalSeconds = plugin.getConfig().getInt("trash.auto_clear_interval_seconds", 0);
if (intervalSeconds <= 0) return;
long ticks = intervalSeconds * 20L;
autoTrashTask = new BukkitRunnable() {
@Override
public void run() {
for (Map.Entry<UUID, Location> entry : new HashMap<>(trashChestLocations).entrySet()) {
Location loc = entry.getValue();
if (loc == null || loc.getWorld() == null) continue;
if (loc.getBlock().getState() instanceof Chest chest) {
processTrashChestInventory(entry.getKey(), chest.getInventory());
}
}
}
}.runTaskTimer(plugin, ticks, ticks);
}
public void stopAutoTrashTask() {
if (autoTrashTask != null) { autoTrashTask.cancel(); autoTrashTask = null; }
}
// ══════════════════════════════════════════════════════════════════════════
// KONFIGURATIONS-GUI
// ══════════════════════════════════════════════════════════════════════════
public void openConfigGui(Player player, UUID ownerUUID) {
openGuiOwners.put(player.getUniqueId(), ownerUUID);
Inventory gui = Bukkit.createInventory(null, 54, getGuiTitle());
List<String> filter = trashFilterLists.getOrDefault(ownerUUID, new ArrayList<>());
int displaySlot = 0;
for (String matName : filter) {
if (displaySlot >= 45) break;
Material mat = Material.matchMaterial(matName);
if (mat == null || mat == Material.AIR) continue;
ItemStack display = new ItemStack(mat, 1);
ItemMeta meta = display.getItemMeta();
if (meta != null) {
meta.setDisplayName(getSignColor("trash", "line1") + formatMaterialName(matName));
meta.setLore(Arrays.asList(getSignColor("trash", "line4") + "Rechtsklick: Entfernen"));
display.setItemMeta(meta);
}
gui.setItem(displaySlot++, display);
}
ItemStack filler = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
ItemMeta fillerMeta = filler.getItemMeta();
if (fillerMeta != null) { fillerMeta.setDisplayName(" "); filler.setItemMeta(fillerMeta); }
for (int i = 45; i <= 53; i++) gui.setItem(i, filler.clone());
ItemStack modeInfo = new ItemStack(filter.isEmpty() ? Material.BARRIER : Material.WATER_BUCKET);
ItemMeta modeMeta = modeInfo.getItemMeta();
if (modeMeta != null) {
if (filter.isEmpty()) {
modeMeta.setDisplayName(getSignColor("trash", "line2") + "" + ChatColor.BOLD + "Modus: Deaktiviert");
modeMeta.setLore(Arrays.asList(
ChatColor.GRAY + "Kein Filter gesetzt ",
ChatColor.GRAY + "Items werden NICHT gelöscht.",
getSignColor("trash", "line1") + "Füge Items hinzu um die Mülltruhe",
getSignColor("trash", "line1") + "zu aktivieren."));
} else {
modeMeta.setDisplayName(getSignColor("trash", "line4") + "" + ChatColor.BOLD + "Modus: Filter aktiv");
modeMeta.setLore(Arrays.asList(
ChatColor.GRAY + "Nur gefilterte Items",
ChatColor.GRAY + "werden gelöscht."));
}
modeInfo.setItemMeta(modeMeta);
}
gui.setItem(48, modeInfo);
ItemStack addBtn = new ItemStack(Material.LIME_STAINED_GLASS_PANE);
ItemMeta addMeta = addBtn.getItemMeta();
if (addMeta != null) {
addMeta.setDisplayName(getSignColor("trash", "line1") + "" + ChatColor.BOLD + "Item hinzufügen");
addMeta.setLore(Arrays.asList(
ChatColor.GRAY + "Item in die Hand nehmen",
ChatColor.GRAY + "und diesen Button klicken."));
addBtn.setItemMeta(addMeta);
}
gui.setItem(49, addBtn);
gui.setItem(53, buildSkullButton());
player.openInventory(gui);
}
public UUID getGuiOwner(UUID playerUUID) {
return openGuiOwners.get(playerUUID);
}
private ItemStack buildSkullButton() {
ItemStack skull;
try {
skull = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) skull.getItemMeta();
if (meta != null) {
PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID(), "TrashBtn");
PlayerTextures textures = profile.getTextures();
textures.setSkin(new URL(SKULL_TEXTURE));
profile.setTextures(textures);
meta.setOwnerProfile(profile);
meta.setDisplayName(getChestTitleColor() + "" + ChatColor.BOLD + "Mülltruhe leeren");
meta.setLore(Arrays.asList(
ChatColor.GRAY + "Klicken um alle Items",
ChatColor.GRAY + "sofort zu löschen."));
skull.setItemMeta(meta);
}
} catch (Exception e) {
skull = new ItemStack(Material.RED_DYE);
ItemMeta meta = skull.getItemMeta();
if (meta != null) {
meta.setDisplayName(getChestTitleColor() + "" + ChatColor.BOLD + "Mülltruhe leeren");
meta.setLore(Arrays.asList(
ChatColor.GRAY + "Klicken um alle Items",
ChatColor.GRAY + "sofort zu löschen."));
skull.setItemMeta(meta);
}
}
return skull;
}
// ══════════════════════════════════════════════════════════════════════════
// GUI-LISTENER
// ══════════════════════════════════════════════════════════════════════════
private class GuiListener implements Listener {
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) return;
if (!getGuiTitle().equals(event.getView().getTitle())) return;
event.setCancelled(true);
int clickedSlot = event.getRawSlot();
UUID ownerUUID = openGuiOwners.get(player.getUniqueId());
if (ownerUUID == null) return;
if (clickedSlot == 53) {
clearTrashChest(ownerUUID);
player.sendMessage(getMessage("trash-cleared"));
return;
}
if (clickedSlot == 49) {
ItemStack inHand = player.getInventory().getItemInMainHand();
if (inHand.getType() == Material.AIR) {
player.sendMessage(getMessage("no-item-in-hand"));
return;
}
Material mat = inHand.getType();
if (addToFilter(ownerUUID, mat)) {
player.sendMessage(getMessage("trash-item-added").replace("%item%", formatMaterialName(mat.name())));
} else {
player.sendMessage(getMessage("trash-item-already"));
}
openConfigGui(player, ownerUUID);
return;
}
if (clickedSlot >= 45) return;
if (event.isRightClick()) {
ItemStack clicked = event.getCurrentItem();
if (clicked == null || clicked.getType() == Material.AIR) return;
Material mat = clicked.getType();
if (removeFromFilter(ownerUUID, mat)) {
player.sendMessage(getMessage("trash-item-removed").replace("%item%", formatMaterialName(mat.name())));
openConfigGui(player, ownerUUID);
}
}
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
if (event.getPlayer() instanceof Player player) {
openGuiOwners.remove(player.getUniqueId());
}
}
}
// ══════════════════════════════════════════════════════════════════════════
// HILFSMETHODEN
// ══════════════════════════════════════════════════════════════════════════
private String getMessage(String key) {
String msg = plugin.getConfig().getString("messages." + key, "messages." + key + " fehlt");
return org.bukkit.ChatColor.translateAlternateColorCodes('&', msg);
}
/**
* Liest eine Schildfarbe aus sign-colors.&lt;type&gt;.&lt;line&gt; in der Config.
* Gibt übersetzten §-Code zurück (z.B. §6 für &6).
*/
private String getSignColor(String type, String line) {
String raw = plugin.getConfig().getString("sign-colors." + type + "." + line, "&f");
return ChatColor.translateAlternateColorCodes('&', raw);
}
/**
* Liest nur den Farb-Präfix aus chest-titles.trash (ohne Titeltext).
* Gibt übersetzten §-Code zurück.
*/
private String getChestTitleColor() {
boolean isEn = "en".equalsIgnoreCase(plugin.getConfig().getString("language", "de"));
String lang = isEn ? "en" : "de";
String full = plugin.getConfig().getString("chest-titles.trash." + lang,
isEn ? "&4Trash Chest" : "&4Mülltruhe");
// Nur die führenden &X / &l Codes extrahieren
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++; // Zeichen überspringen
} else {
break; // erster Nicht-Code-Zeichenblock → abbrechen
}
}
return ChatColor.translateAlternateColorCodes('&', codes.length() > 0 ? codes.toString() : "&4");
}
private String locKey(Location loc) {
return loc.getWorld().getName() + ":" + loc.getBlockX() + ":" + loc.getBlockY() + ":" + loc.getBlockZ();
}
public static String formatMaterialName(String name) {
if (name == null || name.isEmpty()) return "";
String[] parts = name.split("_");
StringBuilder sb = new StringBuilder();
for (String part : parts) {
if (sb.length() > 0) sb.append(' ');
if (!part.isEmpty()) {
sb.append(Character.toUpperCase(part.charAt(0)));
if (part.length() > 1) sb.append(part.substring(1).toLowerCase());
}
}
return sb.toString();
}
}

View File

@@ -21,7 +21,7 @@ public class UpdateChecker {
public void getVersion(final Consumer<String> consumer) { public void getVersion(final Consumer<String> consumer) {
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
try (InputStream is = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId + "/~").openStream(); try (InputStream is = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId).openStream();
Scanner scann = new Scanner(is)) { Scanner scann = new Scanner(is)) {
if (scann.hasNext()) { if (scann.hasNext()) {
consumer.accept(scann.next()); consumer.accept(scann.next());

View File

@@ -7,19 +7,24 @@
# #
# ============================================================ # ============================================================
# --- GRUNDLEGUNG --- # ============================================================
# Version der Konfigurationsdatei. Nicht ändern, um Fehler zu vermeiden! # ALLGEMEIN
# ============================================================
version: "2.2" # Version der Konfigurationsdatei bitte nicht ändern!
version: "2.3"
# Debug-Modus (true = Ausführliche Logs in der Server-Konsole, nur zum Entwickeln nutzen)
# Debug-Modus: true = Ausführliche Logs in der Konsole (nur zum Entwickeln)
debug: false debug: false
# --------------------------------------------------- # Sprache der Benutzeroberfläche
# DATENBANK (MySQL/MariaDB) - Optional # Mögliche Werte: 'de' (Deutsch) oder 'en' (Englisch)
# --------------------------------------------------- # Betrifft: /asc help, /asc info und die Truhen-Fenstertitel
# Aktiviere MySQL/MariaDB Speicherung (true/false) language: "de"
# ============================================================
# DATENBANK (MySQL / MariaDB) Optional
# ============================================================
mysql: mysql:
enabled: false enabled: false
@@ -29,111 +34,80 @@ mysql:
user: "autosortchest" user: "autosortchest"
password: "autosortchest" password: "autosortchest"
# Soll serverübergreifendes Sortieren (mit MySQL) erlaubt sein? # ============================================================
server_crosslink: true
# ---------------------------------------------------
# BUNGEE CORD / MULTI-SERVER SETUP # BUNGEE CORD / MULTI-SERVER SETUP
# --------------------------------------------------- # ============================================================
# Soll serverübergreifendes Sortieren (über MySQL) erlaubt sein?
server_crosslink: false
# Eindeutiger Name dieses Servers im BungeeCord-Netzwerk. # Eindeutiger Name dieses Servers im BungeeCord-Netzwerk.
# WICHTIG: Jeder Server braucht einen anderen Namen! # WICHTIG: Jeder Server braucht einen anderen Namen!
# #
# Beispiele: # Beispiele: lobby | survival | creative
# server_name: "lobby"
# server_name: "survival"
# server_name: "creative"
# #
# Leer lassen = Legacy-Modus (welt-basierte Erkennung, kein BungeeCord). # Leer lassen = Legacy-Modus (welt-basierte Erkennung, kein BungeeCord).
# Fuer BungeeCord MUSS mysql.enabled: true gesetzt sein! # Für BungeeCord MUSS mysql.enabled: true gesetzt sein!
# #
# Setup-Schritte: # Setup:
# 1. Gleiche MySQL-Datenbank auf allen Servern eintragen. # 1. Gleiche MySQL-Datenbank auf allen Servern eintragen.
# 2. Auf jedem Server einen einzigartigen server_name setzen. # 2. Auf jedem Server einen einzigartigen server_name setzen.
# 3. mysql.enabled: true und server_crosslink: true setzen. # 3. mysql.enabled: true und server_crosslink: true setzen.
# 4. Alle Server neu starten - Schema wird automatisch migriert. # 4. Alle Server neu starten Schema wird automatisch migriert.
server_name: "" server_name: ""
# --------------------------------------------------- # ============================================================
# SPRACHE (Language) # WELTEN-BLACKLIST
# --------------------------------------------------- # ============================================================
# Mögliche Werte: 'de' für Deutsch oder 'en' für Englisch
# Ändert den Text von /asc help und /asc info
language: "de"
# ---------------------------------------------------
# BLACKLIST FÜR WELTEN (Optional)
# ---------------------------------------------------
# Welten, in denen AutoSortChest NICHT funktioniert # Welten, in denen AutoSortChest NICHT funktioniert
world_blacklist: world_blacklist:
- "world_nether" - "world_nether"
- "world_the_end" - "world_the_end"
# --------------------------------------------------- # ============================================================
# VISUELLE EFFEKTE (PARTIKEL & TÖNE)
# ---------------------------------------------------
# Einstellungen für den Regenbogen-Effekt beim Sortieren
effects:
# Sollen Effekte angezeigt werden?
enabled: false
# Soll ein Ton gespielt werden, wenn Items ankommen?
sound: false
# Der Partikel-Typ.
# 'DUST' ist zwingend für den bunten Regenbogen-Effekt im aktuellen Code.
type: "DUST"
# ---------------------------------------------------
# SORTIER-INTERVALL (Ticks) # SORTIER-INTERVALL (Ticks)
# --------------------------------------------------- # ============================================================
# Wie oft soll sortiert werden? (1 Tick = 0,05s) # Wie oft soll sortiert werden? (1 Tick = 0,05 Sekunden)
#
# Wähle hier je nach Server-Leistung:
#
# 1 = SEHR SCHNELL (Items verschwinden sofort)
# WARNUNG: Kann bei vielen Truhen Lagg verursachen!
#
# 5 = SCHNELL (Sehr flüssig, gute Balance)
# Empfohlen für schnelle Server.
#
# 10 = FLÜSSIG (0,5s Verzögerung)
# Spart Ressourcen, fühlt sich noch schnell an.
#
# 20 = STANDARD (1 Sekunde)
# Standard-Wert, minimale Last.
#
# 30+ = SPARSAM (>1,5 Sekunden)
# Für sehr große Server mit schwacher Hardware.
# #
# 1 = Sehr schnell Items verschwinden sofort
# WARNUNG: Kann bei vielen Truhen Lag verursachen!
# 5 = Schnell Sehr flüssig, gute Balance
# 10 = Flüssig 0,5s Verzögerung, spart Ressourcen <- Empfohlen
# 20 = Standard 1 Sekunde, minimale Last
# 30+ = Sparsam Für große Server mit schwacher Hardware
sort_interval_ticks: 5 sort_interval_ticks: 10
# --------------------------------------------------- # Wie oft werden Rest-Truhen neu einsortiert? (in Ticks)
# LIMITS FÜR SORTIERKISTEN (Optional) rest_resort_interval_ticks: 400 # 400 Ticks = 20 Sekunden
# ---------------------------------------------------
# Maximale Anzahl an Sortierkisten pro Spielergruppe # ============================================================
# TRUHEN-LIMITS
# ============================================================
# Maximale Anzahl an Sortiertruhen pro Spielergruppe.
# Sollen Limits aktiv sein? (true = ja, false = keine Beschränkung)
chest_limits: chest_limits:
# Sollen Truhen-Limits aktiv sein? (true = ja, false = keine Beschraenkung) enabled: false
enabled: true
# Jede Gruppe hat eigene Limits fuer input, rest und target.
# Spieler benoetigen die Permission: autosortchest.limit.<gruppe>
# Spieler benötigen die Permission: autosortchest.limit.<gruppe>
# WICHTIG: Spieler OHNE jegliche autosortchest.limit.*-Permission können bei
# aktivierten Limits KEINE Truhen erstellen (Limit = 0).
# Vergib autosortchest.limit.default an alle normalen Spieler (z.B. in LuckPerms).
default: default:
input: 1 # Eingangstruhen (Input) input: 1 # Eingangstruhen
rest: 1 # Rest-Truhen (Fallback) rest: 1 # Rest-Truhen (Fallback)
target: 50 # Zieltruhen gesamt target: 50 # Zieltruhen gesamt
target_per_item: 1 # Wie viele Zieltruhen pro Item-Typ erlaubt sind target_per_item: 1 # Zieltruhen pro Item-Typ
vip: vip:
input: 2 input: 2
rest: 2 rest: 2
target: 100 target: 100
target_per_item: 3 target_per_item: 3
# Weitere Gruppen: # Weitere Gruppen (auskommentiert):
# supporter: # supporter:
# input: 3 # input: 3
# rest: 2 # rest: 2
@@ -145,61 +119,197 @@ chest_limits:
# target: 200 # target: 200
# target_per_item: 10 # target_per_item: 10
# --------------------------------------------------- # ============================================================
# SCHILDFARBEN (Farbcodes wie im Chat) # MÜLLTRUHE
# &c = Rot, &a = Grün, &e = Gelb, &6 = Gold, &f = Weiß, &0 = Schwarz # ============================================================
# --------------------------------------------------- # auto_clear_interval_seconds:
# 0 = Deaktiviert (Truhe wird nur beim Schließen geleert)
# 300 = Alle 5 Minuten
# 3600 = Stündlich
trash:
auto_clear_interval_seconds: 0
# ============================================================
# VISUELLE EFFEKTE (Partikel & Töne)
# ============================================================
effects:
# Sollen Partikel-Effekte angezeigt werden?
enabled: false
# Soll ein Ton gespielt werden, wenn Items ankommen?
sound: false
# Partikel-Typ ('DUST' = bunter Regenbogen-Effekt)
type: "DUST"
# ============================================================
# SCHILD-STIL
# ============================================================
sign-style:
# Saubere Zieltruhen-Schilder (ohne Item-Namen auf dem Schild)
# true = Clean-Modus aktiviert
clean-target: false
# ============================================================
# SCHILDFARBEN
# ============================================================
# Farbcodes: &0-&9, &a-&f | &l = Fett, &o = Kursiv, &r = Reset
#
# &0 Schwarz &1 Dunkelblau &2 Dunkelgrün &3 Cyan
# &4 Dunkelrot &5 Lila &6 Gold &7 Grau
# &8 Dunkelgrau &9 Blau &a Grün &b Aqua
# &c Rot &d Pink &e Gelb &f Weiß
sign-colors: sign-colors:
# Farben für die Eingangstruhe ([asc] / input)
input:
line1: "&6" # Zeile 1: [asc]
line2: "&0" # Zeile 2: input
line4: "&1" # Zeile 4: Spielername
# Farben für die Zieltruhe ([asc] / ziel)
target:
line1: "&6" # Zeile 1: [asc]
line2: "&0" # Zeile 2: ziel
line3: "&f" # Zeile 3: Item-Name
line4: "&1" # Zeile 4: Spielername
# Farben für volle Truhen (Automatische Erkennung)
full:
line1: "&c" # Zeile 1: [asc]
line2: "&4" # Zeile 2: ziel / rest (Rot)
line3: "&e" # Zeile 3: Item-Name (Gelb)
line4: "&1" # Zeile 4: Spielername
# Farben für die Rest-Truhe ([asc] / rest)
rest:
line1: "&6" # Zeile 1: [asc]
line2: "&0" # Zeile 2: rest
line3: "&f" # Zeile 3: (Leer)
line4: "&1" # Zeile 4: Spielername
# --------------------------------------------------- # Eingangstruhe ([asc] / input)
# SYSTEM NACHRICHTEN (Spieler-Feedback) input:
line1: "&6" # Zeile 1: [asc]
line2: "&0" # Zeile 2: input
line4: "&1" # Zeile 4: Spielername
# Zieltruhe ([asc] / ziel)
target:
line1: "&6" # Zeile 1: [asc]
line2: "&0" # Zeile 2: ziel
line3: "&f" # Zeile 3: Item-Name
line4: "&1" # Zeile 4: Spielername
# Volle Zieltruhe (automatische Erkennung)
full:
line1: "&c" # Zeile 1: [asc]
line2: "&4" # Zeile 2: ziel / rest
line3: "&e" # Zeile 3: Item-Name
line4: "&1" # Zeile 4: Spielername
# Rest-Truhe ([asc] / rest)
rest:
line1: "&6" # Zeile 1: [asc]
line2: "&0" # Zeile 2: rest
line3: "&f" # Zeile 3: (leer)
line4: "&1" # Zeile 4: Spielername
# Mülltruhe ([asc] / trash)
trash:
line1: "&6" # Zeile 1: [asc]
line2: "&0" # Zeile 2: trash
line4: "&1" # Zeile 4: Spielername
# ============================================================
# CLEAN-MODUS SCHILDFARBEN (sign-style.clean-target: true)
# ============================================================
# Eigene Farben für den sauberen Schild-Stil.
# Farbcodes: &0-&9, &a-&f | &l = Fett, &o = Kursiv, &r = Reset
#
# Layout im Clean-Modus (Zeile 4 = versteckter Typ-Marker, nicht konfigurierbar):
#
# input → Z1: Spielername | Z2: "Eingang/Input" | Z3: Öffentlich/Privat
# target → Z1: Item-Name | Z2: Spielername | Z3: Öffentlich/Privat
# full → Z1: Item-Name | Z2: Spielername | Z3: Öffentlich/Privat (volle Truhe)
# rest → Z1: Spielername | Z2: "Rest" | Z3: Öffentlich/Privat
# trash → Z1: Spielername | Z2: "Müll/Trash" | Z3: (leer)
sign-colors-clean:
# Eingangstruhe (Clean)
input:
line1: "&1" # Zeile 1: Spielername
line2: "&0" # Zeile 2: "Eingang" / "Input"
line3: "&a" # Zeile 3: Öffentlich / Privat
# Zieltruhe (Clean)
target:
line1: "&f" # Zeile 1: Item-Name
line2: "&1" # Zeile 2: Spielername
line3: "&a" # Zeile 3: Öffentlich / Privat
# Volle Zieltruhe (Clean)
full:
line1: "&c" # Zeile 1: Item-Name (volle Truhe) → Rot
line2: "&1" # Zeile 2: Spielername
line3: "&a" # Zeile 3: Öffentlich / Privat
# Rest-Truhe (Clean)
rest:
line1: "&1" # Zeile 1: Spielername
line2: "&0" # Zeile 2: "Rest"
line3: "&a" # Zeile 3: Öffentlich / Privat
# Mülltruhe (Clean)
trash:
line1: "&1" # Zeile 1: Spielername
line2: "&0" # Zeile 2: "Müll" / "Trash"
# ============================================================
# TRUHEN-FENSTERTITEL
# ============================================================
# Farbe & Text des Titels, wenn ein Spieler eine ASC-Truhe öffnet.
# Farbcodes wie oben.
#
# Platzhalter für Zieltruhen:
# %item% -> wird durch den Item-Namen ersetzt (z.B. "Iron Ore")
# Beispiel: "&6%item%" zeigt "Iron Ore" in Gold
chest-titles:
input:
de: "&6Eingangstruhe"
en: "&6Input Chest"
target:
de: "&6%item%"
en: "&6%item%"
rest:
de: "&6Rest-Truhe"
en: "&6Rest Chest"
trash:
de: "&4Mülltruhe"
en: "&4Trash Chest"
# ============================================================
# SYSTEM-NACHRICHTEN (Spieler-Feedback)
# ============================================================
# Platzhalter: %player%, %item%, %x%, %y%, %z%, %mode% # Platzhalter: %player%, %item%, %x%, %y%, %z%, %mode%
# ---------------------------------------------------
messages: messages:
# --- FEHLERMELDUNGEN ---
no-chest-near-sign: "&cKeine Truhe in der Nähe des Schildes!" # --- Fehlermeldungen ---
no-item-in-hand: "&cDu musst ein Item in der Hand halten!" no-chest-near-sign: "&cKeine Truhe in der Nähe des Schildes!"
not-your-chest: "&cDiese Truhe gehört dir nicht!" no-item-in-hand: "&cDu musst ein Item in der Hand halten!"
not-your-chest: "&cDiese Truhe gehört dir nicht!"
target-chest-missing: "&cZieltruhe für %item% fehlt!" target-chest-missing: "&cZieltruhe für %item% fehlt!"
sign-break-denied: "&cDu musst Shift gedrückt halten, um dieses Schild oder die Truhe abzubauen!" sign-break-denied: "&cDu musst Shift gedrückt halten, um dieses Schild oder die Truhe abzubauen!"
no-permission: "&cDu hast keine Berechtigung für diesen Befehl!" no-permission: "&cDu hast keine Berechtigung für diesen Befehl!"
world-blacklisted: "&cIn dieser Welt kannst du keine AutoSortChest erstellen!"
# --- ERFOLGSMELDUNGEN --- # --- Limit-Fehlermeldungen ---
input-chest-set: "&aEingangstruhe erfolgreich gesetzt!" # Platzhalter: %max% = erlaubtes Limit, %item% = Item-Name
target-chest-set: "&aZieltruhe erfolgreich für %item% eingerichtet!" limit-input-reached: "&cDu hast das Limit deiner Eingangstruhen erreicht! &7(%max%)"
rest-chest-set: "&aRest-Truhe (Fallback) erfolgreich gesetzt!" limit-rest-reached: "&cDu hast das Limit deiner Rest-Truhen erreicht! &7(%max%)"
reload-success: "&aKonfiguration erfolgreich neu geladen!" limit-target-reached: "&cDu hast das Limit deiner Zieltruhen erreicht! &7(%max%)"
limit-target-per-item: "&cDu hast das Limit für %item%-Truhen erreicht! &7(%max%)"
limit-no-permission: "&cDu hast keine Berechtigung um Truhen zu erstellen!"
# --- HINWEIS MELDUNGEN --- # --- Erfolgsmeldungen ---
input-chest-set: "&aEingangstruhe erfolgreich gesetzt!"
target-chest-set: "&aZieltruhe erfolgreich für %item% eingerichtet!"
rest-chest-set: "&aRest-Truhe (Fallback) erfolgreich gesetzt!"
trash-chest-set: "&aMülltruhe erfolgreich eingerichtet!"
trash-chest-hint: "&7Rechtsklicke das Schild um Items zu konfigurieren."
reload-success: "&aKonfiguration erfolgreich neu geladen!"
# --- Mülltruhe GUI ---
trash-cleared: "&a✔ Mülltruhe wurde geleert!"
trash-item-added: "&a✔ &e%item% &azur Müll-Liste hinzugefügt."
trash-item-already: "&eDiseses Item ist bereits in der Müll-Liste."
trash-item-removed: "&c✖ &e%item% &caus der Müll-Liste entfernt."
# --- Mülltruhe Info (erscheint im Chat beim Öffnen der Truhe) ---
# Platzhalter: %items% = kommagetrennte Item-Liste
trash-info-empty: "&4Mülltruhe &8(Deaktiviert) &7 Rechtsklick Schild zum Konfigurieren"
trash-info-filter: "&4Müll: &f%items% &8| Schild-Rechtsklick: Konfigurieren"
# --- Hinweise ---
target-chest-full: "&cZieltruhe für %item% ist voll! Koordinaten: (%x%, %y%, %z%)" target-chest-full: "&cZieltruhe für %item% ist voll! Koordinaten: (%x%, %y%, %z%)"
mode-changed: "&aModus gewechselt: &e%mode%" mode-changed: "&aModus gewechselt: &e%mode%"
mode-public: "&aÖffentlich" mode-public: "&aÖffentlich"
mode-private: "&cPrivat" mode-private: "&cPrivat"

View File

@@ -1,5 +1,5 @@
name: AutoSortChest name: AutoSortChest
version: 2.3 version: 2.5
main: com.viper.autosortchest.Main main: com.viper.autosortchest.Main
api-version: 1.21 api-version: 1.21
authors: [M_Viper] authors: [M_Viper]
@@ -10,6 +10,9 @@ commands:
usage: /<command> [help|info|reload|import|export] usage: /<command> [help|info|reload|import|export]
aliases: [autosortchest] aliases: [autosortchest]
permissions: permissions:
autosortchest.use:
description: Erlaubt das Erstellen von AutoSortChest-Schildern (Eingang, Ziel, Rest, Muelltruhe)
default: true
autosortchest.reload: autosortchest.reload:
description: Erlaubt das Neuladen der Konfiguration mit /asc reload description: Erlaubt das Neuladen der Konfiguration mit /asc reload
default: op default: op
@@ -20,14 +23,20 @@ permissions:
description: Erlaubt den Export von MySQL nach players.yml mit /asc export description: Erlaubt den Export von MySQL nach players.yml mit /asc export
default: op default: op
autosortchest.bypass: autosortchest.bypass:
description: Erlaubt das Abbauen von ASC-Schildern ohne Shift-Taste und unabhängig vom Besitzer description: Erlaubt das Abbauen von ASC-Schildern ohne Shift-Taste und unabhaengig vom Besitzer
default: op
autosortchest.limit.bypass:
description: Umgeht alle Truhen-Limits (input, rest, target) OPs haben dies automatisch
default: op default: op
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
default: op default: op
autosortchest.limit.<gruppe>: autosortchest.limit.<gruppe>:
description: > description: >
Limits fuer eine benutzerdefinierte Gruppe aus der config.yml. Limits fuer eine benutzerdefinierte Gruppe aus der config.yml.
Ersetze <gruppe> durch den Gruppennamen (z.B. autosortchest.limit.vip). Ersetze <gruppe> durch den Gruppennamen (z.B. autosortchest.limit.vip).
Die Gruppen und ihre Limits werden ausschliesslich in der config.yml definiert. Die Gruppen und ihre Limits werden ausschliesslich in der config.yml definiert.
WICHTIG: Spieler ohne jegliche autosortchest.limit.*-Permission koennen bei
aktivierten Limits (chest_limits.enabled: true) KEINE Truhen erstellen.
Vergib autosortchest.limit.default fuer den Standard-Rang.
default: false default: false