commit a8524ccc4756b68a1b6f94c5e5cae874feaad525 Author: Tronax Date: Sat Jun 13 10:47:19 2026 +0200 Minecraft Storage Plugin for PaperMC (works like a backpack) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b788884 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# Build output +*.jar +*.class + +# IntelliJ IDEA +.idea/ +*.iml +*.ipr +*.iws +out/ + +# Eclipse +.classpath +.project +.settings/ +bin/ + +# VS Code +.vscode/ + +# NetBeans +nbproject/ +nbbuild/ +nbdist/ +.nb-gradle/ + +# Logs & databases (lokale Test-Daten) +*.log +*.db +storage.db + +# OS +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a5e278 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +# ZalmikStorage + +Ein mehrseitiges, datenbankgestütztes Lagersystem (ähnlich einer erweiterten Enderchest) für Paper/Spigot-Server ab **Minecraft 1.20**. + +Spieler öffnen über `/storage` ein 54-Slot-Inventar mit 45 nutzbaren Slots pro Seite und können per Pfeil-Buttons zwischen Seiten blättern. Die Anzahl der verfügbaren Seiten wird über Permissions gesteuert. Items werden serialisiert in einer Datenbank gespeichert. + +- **Author:** Tronax +- **Version:** 1.0 +- **API:** Paper 1.20 + +## Features + +- Mehrseitiges Lager mit Navigation (Vorherige / Nächste Seite) +- Pro-Spieler-Speicherung anhand der UUID +- Seitenlimit konfigurierbar über Permissions +- Unterstützt **SQLite**, **MySQL/MariaDB** und **PostgreSQL** (HikariCP Connection-Pool) +- Asynchrones Laden/Speichern, um den Main-Thread zu entlasten + +## Befehle + +| Befehl | Beschreibung | Permission | +|------------|---------------------------|----------------------| +| `/storage` | Öffnet das eigene Lager | `zalmikstorage.use` | + +## Permissions + +| Permission | Beschreibung | Default | +|----------------------------------|-----------------------------------------------------|---------| +| `zalmikstorage.use` | Erlaubt das Öffnen des Lagers | `false` | +| `zalmikstorage.pages.` | Setzt das Seitenlimit auf `` (z. B. `.5` = 5) | – | +| `zalmikstorage.pages.unlimited` | Hebt das Seitenlimit komplett auf | – | + +> Das höchste zutreffende `zalmikstorage.pages.` gewinnt. Ohne eine solche Permission steht **1 Seite** zur Verfügung. + +## Konfiguration (`config.yml`) + +```yaml +database: + type: sqlite # sqlite | mysql | mariadb | postgresql + host: "127.0.0.1" + port: 3306 + database: "minecraft" + username: "root" + password: "password" + pool_size: 10 + timeout: 30000 +``` + +Bei `type: sqlite` wird eine lokale Datei `storage.db` im Plugin-Ordner verwendet (Host/Port/Zugangsdaten werden ignoriert). + +## Build + +Voraussetzungen: **JDK 21** und **Maven**. + +```bash +mvn clean package +``` + +Das fertige Plugin liegt anschließend unter `target/ZalmikStorage-1.0-SNAPSHOT.jar` und kann in den `plugins/`-Ordner des Servers kopiert werden. + +## Datenbank-Schema + +```sql +CREATE TABLE IF NOT EXISTS zalmik_storage ( + uuid VARCHAR(36) NOT NULL, + page INT NOT NULL, + items TEXT, + PRIMARY KEY (uuid, page) +); +``` + +Items werden als Base64-kodierter `BukkitObjectStream` in der Spalte `items` abgelegt. + +--- + +## Sicherheit & Stabilität + +Das Plugin wurde einer Code-Prüfung unterzogen. Die dabei gefundenen Logik- und Sicherheitsprobleme sind behoben: + +### Kritisch — behoben + +1. **Fehlende JDBC-Treiber im Build** — `sqlite-jdbc` und `mariadb-java-client` wurden der `pom.xml` hinzugefügt. Die Standard-Konfiguration (`type: sqlite`) funktioniert jetzt out of the box. + +2. **Datenverlust beim Server-Stop** — `onDisable()` speichert alle offenen Lager nun **synchron**, bevor die Datenbankverbindung getrennt und die IO-Queues heruntergefahren werden. + +3. **Race-Condition beim schnellen Öffnen/Schließen** — Laden und Speichern laufen pro Spieler über eine **serielle Single-Thread-Queue** (`ExecutorService`). Dadurch ist FIFO-Reihenfolge garantiert: Ein Ladevorgang kann eine noch ausstehende Speicherung nicht mehr überholen. + +### Hoch — behoben + +4. **Permission-Wert wird ignoriert** — `getMaxPages()` überspringt nun Nodes mit `getValue() == false`. Eine auf `false` gesetzte `zalmikstorage.pages.` gewährt keine Seiten mehr. + +5. **Stiller Fehlerzustand bei DB-Verbindungsfehler** — `connect()` wirft jetzt eine Exception statt sie nur zu loggen; `onEnable()` fängt sie ab und **deaktiviert das Plugin**, statt in einem kaputten Zustand weiterzulaufen. + +### Mittel / Niedrig — behoben + +6. **Unsichere Deserialisierung / DoS** — `ItemSerializer` begrenzt die aus den Daten gelesene Array-Größe (`MAX_ITEMS = 54`) und lehnt ungültige Längen ab, bevor Speicher allokiert wird. + +7. **Unbekannter `database.type`** — wird jetzt mit einer klaren `IllegalArgumentException` abgewiesen, statt mit leerer JDBC-URL zu scheitern. + +8. **`getCommand("storage")` ohne Null-Check** — wird vor `setExecutor` auf `null` geprüft und sauber geloggt. + +### Bereits zuvor solide + +- **Keine SQL-Injection:** Alle Queries nutzen `PreparedStatement`, das Tabellen-Statement ist statisch. +- **Permission-Gate** auf `/storage` (`zalmikstorage.use`, Default `false`). +- **Navigations-Slots (45–53) sind klick-geschützt** (`event.setCancelled(true)`), wodurch Item-Klau aus der GUI-Leiste verhindert wird. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ae0a1a9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + de.plugin + ZalmikStorage + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + + + + io.papermc.paper + paper-api + 1.20.4-R0.1-SNAPSHOT + provided + + + com.zaxxer + HikariCP + 5.1.0 + + + org.postgresql + postgresql + 42.7.3 + + + org.xerial + sqlite-jdbc + 3.45.3.0 + + + org.mariadb.jdbc + mariadb-java-client + 3.4.0 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.2 + + + package + + shade + + + + + com.zaxxer.hikari + de.plugin.zalmikstorage.libs.hikari + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/de/plugin/zalmikstorage/DatabaseManager.java b/src/main/java/de/plugin/zalmikstorage/DatabaseManager.java new file mode 100644 index 0000000..be7c943 --- /dev/null +++ b/src/main/java/de/plugin/zalmikstorage/DatabaseManager.java @@ -0,0 +1,129 @@ +package de.plugin.zalmikstorage; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import java.io.File; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class DatabaseManager { + + private final ZalmikStorage plugin; + private HikariDataSource dataSource; + private String dbType; + + public DatabaseManager(ZalmikStorage plugin) { + this.plugin = plugin; + } + + public void connect() { + dbType = plugin.getConfig().getString("database.type", "sqlite").toLowerCase(); + HikariConfig config = new HikariConfig(); + + if (dbType.equals("sqlite")) { + File dbFile = new File(plugin.getDataFolder(), "storage.db"); + config.setJdbcUrl("jdbc:sqlite:" + dbFile.getAbsolutePath()); + config.setDriverClassName("org.sqlite.JDBC"); + config.setMaximumPoolSize(1); + } else if (dbType.equals("postgresql") || dbType.equals("mariadb") || dbType.equals("mysql")) { + String host = plugin.getConfig().getString("database.host"); + int port = plugin.getConfig().getInt("database.port"); + String db = plugin.getConfig().getString("database.database"); + String user = plugin.getConfig().getString("database.username"); + String pass = plugin.getConfig().getString("database.password"); + + if (dbType.equals("postgresql")) { + config.setJdbcUrl("jdbc:postgresql://" + host + ":" + port + "/" + db); + config.setDriverClassName("org.postgresql.Driver"); + } else { + config.setJdbcUrl("jdbc:mariadb://" + host + ":" + port + "/" + db); + config.setDriverClassName("org.mariadb.jdbc.Driver"); + } + + config.setUsername(user); + config.setPassword(pass); + config.setMaximumPoolSize(plugin.getConfig().getInt("database.pool_size", 10)); + } else { + throw new IllegalArgumentException("Unbekannter database.type: '" + dbType + + "'. Erlaubt sind: sqlite, mysql, mariadb, postgresql."); + } + + config.setConnectionTimeout(plugin.getConfig().getLong("database.timeout", 30000)); + config.setPoolName("ZalmikStoragePool"); + + try { + dataSource = new HikariDataSource(config); + createTable(); + plugin.getLogger().info("Datenbank (" + dbType + ") erfolgreich verbunden."); + } catch (Exception e) { + if (dataSource != null) { + dataSource.close(); + dataSource = null; + } + throw new IllegalStateException("Fehler beim Verbinden mit der Datenbank: " + e.getMessage(), e); + } + } + + private void createTable() throws SQLException { + String query = "CREATE TABLE IF NOT EXISTS zalmik_storage (" + + "uuid VARCHAR(36) NOT NULL, " + + "page INT NOT NULL, " + + "items TEXT, " + + "PRIMARY KEY (uuid, page)" + + ");"; + + try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { + stmt.execute(query); + } + } + + public void savePage(String uuid, int page, String base64Items) { + String query = ""; + if (dbType.equals("sqlite")) { + query = "INSERT OR REPLACE INTO zalmik_storage (uuid, page, items) VALUES (?, ?, ?)"; + } else if (dbType.equals("mariadb") || dbType.equals("mysql")) { + query = "INSERT INTO zalmik_storage (uuid, page, items) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE items = VALUES(items)"; + } else if (dbType.equals("postgresql")) { + query = "INSERT INTO zalmik_storage (uuid, page, items) VALUES (?, ?, ?) ON CONFLICT (uuid, page) DO UPDATE SET items = EXCLUDED.items"; + } + + try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(query)) { + pstmt.setString(1, uuid); + pstmt.setInt(2, page); + pstmt.setString(3, base64Items); + pstmt.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public String loadPage(String uuid, int page) { + String query = "SELECT items FROM zalmik_storage WHERE uuid = ? AND page = ?"; + try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(query)) { + pstmt.setString(1, uuid); + pstmt.setInt(2, page); + try (ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + return rs.getString("items"); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + public Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + public void disconnect() { + if (dataSource != null && !dataSource.isClosed()) { + dataSource.close(); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/plugin/zalmikstorage/ItemSerializer.java b/src/main/java/de/plugin/zalmikstorage/ItemSerializer.java new file mode 100644 index 0000000..b3dcfbf --- /dev/null +++ b/src/main/java/de/plugin/zalmikstorage/ItemSerializer.java @@ -0,0 +1,55 @@ +package de.plugin.zalmikstorage; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.bukkit.util.io.BukkitObjectOutputStream; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Base64; + +public class ItemSerializer { + + // Pro Seite werden 45 Slots gespeichert; etwas Spielraum gegen korrupte Daten. + private static final int MAX_ITEMS = 54; + + public static String itemsToBase64(ItemStack[] items) { + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream); + + dataOutput.writeInt(items.length); + for (ItemStack item : items) { + dataOutput.writeObject(item); + } + dataOutput.close(); + return Base64.getEncoder().encodeToString(outputStream.toByteArray()); + } catch (Exception e) { + throw new IllegalStateException("Items konnten nicht serialisiert werden.", e); + } + } + + public static ItemStack[] itemsFromBase64(String data) { + if (data == null || data.isEmpty()) return new ItemStack[45]; + + try { + ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data)); + BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream); + + int length = dataInput.readInt(); + if (length < 0 || length > MAX_ITEMS) { + dataInput.close(); + throw new IllegalStateException("Ungültige Item-Anzahl in gespeicherten Daten: " + length); + } + + ItemStack[] items = new ItemStack[length]; + for (int i = 0; i < items.length; i++) { + items[i] = (ItemStack) dataInput.readObject(); + } + dataInput.close(); + return items; + } catch (Exception e) { + throw new IllegalStateException("Items konnten nicht geladen werden.", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/plugin/zalmikstorage/StorageCommand.java b/src/main/java/de/plugin/zalmikstorage/StorageCommand.java new file mode 100644 index 0000000..2e27b37 --- /dev/null +++ b/src/main/java/de/plugin/zalmikstorage/StorageCommand.java @@ -0,0 +1,32 @@ +package de.plugin.zalmikstorage; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class StorageCommand implements CommandExecutor { + + private final StorageManager storageManager; + + public StorageCommand(StorageManager storageManager) { + this.storageManager = storageManager; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage("Dieser Befehl kann nur von Spielern ausgeführt werden."); + return true; + } + + if (!player.hasPermission("zalmikstorage.use")) { + player.sendMessage("§cDazu hast du keine Berechtigung."); + return true; + } + + storageManager.openStorage(player, 1); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/de/plugin/zalmikstorage/StorageHolder.java b/src/main/java/de/plugin/zalmikstorage/StorageHolder.java new file mode 100644 index 0000000..c37f8ef --- /dev/null +++ b/src/main/java/de/plugin/zalmikstorage/StorageHolder.java @@ -0,0 +1,26 @@ +package de.plugin.zalmikstorage; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +public class StorageHolder implements InventoryHolder { + private final int page; + private Inventory inventory; + + public StorageHolder(int page) { + this.page = page; + } + + public int getPage() { + return page; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + @Override + public Inventory getInventory() { + return inventory; + } +} \ No newline at end of file diff --git a/src/main/java/de/plugin/zalmikstorage/StorageListener.java b/src/main/java/de/plugin/zalmikstorage/StorageListener.java new file mode 100644 index 0000000..c91da3c --- /dev/null +++ b/src/main/java/de/plugin/zalmikstorage/StorageListener.java @@ -0,0 +1,52 @@ +package de.plugin.zalmikstorage; + +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; + +public class StorageListener implements Listener { + + private final StorageManager storageManager; + + public StorageListener(StorageManager storageManager) { + this.storageManager = storageManager; + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player player)) return; + + if (event.getInventory().getHolder() instanceof StorageHolder holder) { + int slot = event.getRawSlot(); + + if (slot >= 45 && slot <= 53) { + event.setCancelled(true); + + if (slot == 45 && holder.getPage() > 1) { + storageManager.saveStorage(player, event.getInventory(), holder.getPage()); + storageManager.openStorage(player, holder.getPage() - 1); + } else if (slot == 53) { + int maxPages = storageManager.getMaxPages(player); + if (holder.getPage() >= maxPages) { + player.sendMessage("§cDu hast dein Seitenlimit von " + maxPages + " erreicht!"); + player.playSound(player.getLocation(), org.bukkit.Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f); + return; + } + storageManager.saveStorage(player, event.getInventory(), holder.getPage()); + storageManager.openStorage(player, holder.getPage() + 1); + } + } + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (!(event.getPlayer() instanceof Player player)) return; + + if (event.getInventory().getHolder() instanceof StorageHolder holder) { + storageManager.saveStorage(player, event.getInventory(), holder.getPage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/plugin/zalmikstorage/StorageManager.java b/src/main/java/de/plugin/zalmikstorage/StorageManager.java new file mode 100644 index 0000000..5918b17 --- /dev/null +++ b/src/main/java/de/plugin/zalmikstorage/StorageManager.java @@ -0,0 +1,137 @@ +package de.plugin.zalmikstorage; + +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.permissions.PermissionAttachmentInfo; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class StorageManager { + + private final ZalmikStorage plugin; + private final DatabaseManager dbManager; + + // Pro-Spieler serielle IO-Queue: garantiert FIFO-Reihenfolge von Laden/Speichern + // und verhindert so, dass ein Laden eine noch ausstehende Speicherung überholt. + private final Map ioExecutors = new ConcurrentHashMap<>(); + + public StorageManager(ZalmikStorage plugin, DatabaseManager dbManager) { + this.plugin = plugin; + this.dbManager = dbManager; + } + + private ExecutorService executorFor(UUID uuid) { + return ioExecutors.computeIfAbsent(uuid, key -> Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable, "ZalmikStorage-IO-" + key); + thread.setDaemon(true); + return thread; + })); + } + + public int getMaxPages(Player player) { + if (player.hasPermission("zalmikstorage.pages.unlimited")) { + return Integer.MAX_VALUE; + } + + int maxPages = 1; + for (PermissionAttachmentInfo info : player.getEffectivePermissions()) { + if (!info.getValue()) continue; + String perm = info.getPermission().toLowerCase(); + if (perm.startsWith("zalmikstorage.pages.")) { + try { + int pages = Integer.parseInt(perm.replace("zalmikstorage.pages.", "")); + if (pages > maxPages) { + maxPages = pages; + } + } catch (NumberFormatException ignored) {} + } + } + return maxPages; + } + + public void openStorage(Player player, int page) { + UUID uuid = player.getUniqueId(); + executorFor(uuid).execute(() -> { + String base64Data = dbManager.loadPage(uuid.toString(), page); + ItemStack[] items = ItemSerializer.itemsFromBase64(base64Data); + + Bukkit.getScheduler().runTask(plugin, () -> { + StorageHolder holder = new StorageHolder(page); + Inventory inv = Bukkit.createInventory(holder, 54, Component.text("ZalmikStorage - Seite " + page)); + holder.setInventory(inv); + + for (int i = 0; i < 45; i++) { + if (i < items.length && items[i] != null) { + inv.setItem(i, items[i]); + } + } + + ItemStack prevPage = createGuiItem(Material.ARROW, "§cVorherige Seite"); + ItemStack nextPage = createGuiItem(Material.ARROW, "§aNächste Seite"); + ItemStack info = createGuiItem(Material.PAPER, "§bAktuelle Seite: " + page); + ItemStack placeholder = createGuiItem(Material.GRAY_STAINED_GLASS_PANE, " "); + + for (int i = 45; i < 54; i++) { + inv.setItem(i, placeholder); + } + + if (page > 1) { + inv.setItem(45, prevPage); + } + inv.setItem(49, info); + inv.setItem(53, nextPage); + + player.openInventory(inv); + }); + }); + } + + public void saveStorage(Player player, Inventory inventory, int page) { + UUID uuid = player.getUniqueId(); + String base64Data = ItemSerializer.itemsToBase64(extractContents(inventory)); + executorFor(uuid).execute(() -> dbManager.savePage(uuid.toString(), page, base64Data)); + } + + /** + * Speichert synchron auf dem aufrufenden Thread. Wird beim Server-Stop genutzt, + * da der Bukkit-Scheduler dann keine asynchronen Tasks mehr ausführt. + */ + public void saveStorageSync(Player player, Inventory inventory, int page) { + String base64Data = ItemSerializer.itemsToBase64(extractContents(inventory)); + dbManager.savePage(player.getUniqueId().toString(), page, base64Data); + } + + public void shutdown() { + for (ExecutorService executor : ioExecutors.values()) { + executor.shutdown(); + } + ioExecutors.clear(); + } + + private ItemStack[] extractContents(Inventory inventory) { + ItemStack[] contents = new ItemStack[45]; + for (int i = 0; i < 45; i++) { + contents[i] = inventory.getItem(i); + } + return contents; + } + + private ItemStack createGuiItem(Material material, String name) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta.setDisplayName(name); + item.setItemMeta(meta); + } + return item; + } +} \ No newline at end of file diff --git a/src/main/java/de/plugin/zalmikstorage/ZalmikStorage.java b/src/main/java/de/plugin/zalmikstorage/ZalmikStorage.java new file mode 100644 index 0000000..8337b13 --- /dev/null +++ b/src/main/java/de/plugin/zalmikstorage/ZalmikStorage.java @@ -0,0 +1,57 @@ +package de.plugin.zalmikstorage; + +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.plugin.java.JavaPlugin; + +public final class ZalmikStorage extends JavaPlugin { + + private DatabaseManager databaseManager; + private StorageManager storageManager; + + @Override + public void onEnable() { + saveDefaultConfig(); + + this.databaseManager = new DatabaseManager(this); + try { + this.databaseManager.connect(); + } catch (Exception e) { + getLogger().severe("Datenbankverbindung fehlgeschlagen, Plugin wird deaktiviert: " + e.getMessage()); + getServer().getPluginManager().disablePlugin(this); + return; + } + + this.storageManager = new StorageManager(this, this.databaseManager); + + PluginCommand command = getCommand("storage"); + if (command != null) { + command.setExecutor(new StorageCommand(this.storageManager)); + } else { + getLogger().severe("Befehl 'storage' konnte nicht registriert werden (fehlt in plugin.yml?)."); + } + getServer().getPluginManager().registerEvents(new StorageListener(this.storageManager), this); + + getLogger().info("ZalmikStorage aktiviert!"); + } + + @Override + public void onDisable() { + if (this.storageManager != null) { + for (Player player : getServer().getOnlinePlayers()) { + Inventory top = player.getOpenInventory().getTopInventory(); + InventoryHolder holder = top.getHolder(); + if (holder instanceof StorageHolder storageHolder) { + this.storageManager.saveStorageSync(player, top, storageHolder.getPage()); + } + } + this.storageManager.shutdown(); + } + if (this.databaseManager != null) { + this.databaseManager.disconnect(); + } + getLogger().info("ZalmikStorage deaktiviert! Datenbankverbindung getrennt."); + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..e2606d7 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,11 @@ +# ZalmikStorage Configuration + +database: + type: sqlite + host: "127.0.0.1" + port: 3306 + database: "minecraft" + username: "root" + password: "password" + pool_size: 10 + timeout: 30000 \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..922a012 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,14 @@ +name: ZalmikStorage +version: 1.0 +main: de.plugin.zalmikstorage.ZalmikStorage +api-version: "1.20" +author: Tronax +description: Mehrseitiges, datenbankgestütztes Spieler-Lagersystem mit konfigurierbarem Seitenlimit per Permission (SQLite, MySQL/MariaDB, PostgreSQL). +commands: + storage: + description: Öffnet den ZalmikStorage + usage: /storage +permissions: + zalmikstorage.use: + description: Erlaubt das Öffnen des Storages + default: false \ No newline at end of file