From edee44630c77bde96335096bd23c0343bfaca1df Mon Sep 17 00:00:00 2001
From: Koopa <115321970+KoopaCode@users.noreply.github.com>
Date: Fri, 31 Jan 2025 21:19:49 -0500
Subject: [PATCH] Public Release
---
.github/workflows/build.yml | 90 +++++
README.md | 125 +++++++
pom.xml | 83 +++++
.../armorstandstorage/ArmorStandListener.java | 342 ++++++++++++++++++
.../armorstandstorage/ArmorStandStorage.java | 79 ++++
.../armorstandstorage/Database.java | 201 ++++++++++
src/main/resources/config.yml | 25 ++
src/main/resources/plugin.yml | 9 +
8 files changed, 954 insertions(+)
create mode 100644 .github/workflows/build.yml
create mode 100644 README.md
create mode 100644 pom.xml
create mode 100644 src/main/java/com/koopacraft/armorstandstorage/ArmorStandListener.java
create mode 100644 src/main/java/com/koopacraft/armorstandstorage/ArmorStandStorage.java
create mode 100644 src/main/java/com/koopacraft/armorstandstorage/Database.java
create mode 100644 src/main/resources/config.yml
create mode 100644 src/main/resources/plugin.yml
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..e0e0d76
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,90 @@
+name: Build
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+ release:
+ types: [created]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: maven
+
+ - name: Get Project Info
+ run: |
+ echo "PLUGIN_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
+ echo "PLUGIN_NAME=$(mvn help:evaluate -Dexpression=project.name -q -DforceStdout)" >> $GITHUB_ENV
+ echo "PLUGIN_FILE=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)" >> $GITHUB_ENV
+
+ - name: Build with Maven
+ run: mvn -B package --file pom.xml
+
+ - name: Debug Directory
+ run: ls -la target/
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ env.PLUGIN_FILE }}-${{ env.PLUGIN_VERSION }}
+ path: target/${{ env.PLUGIN_FILE }}-${{ env.PLUGIN_VERSION }}.jar
+ retention-days: 5
+
+ - name: Send Build Notification
+ if: always()
+ uses: sarisia/actions-status-discord@v1
+ with:
+ webhook: ${{ secrets.DISCORD_WEBHOOK }}
+ title: "${{ job.status == 'success' && '✅ Build Success!' || '❌ Build Failed!' }}"
+ description: |
+ **${{ env.PLUGIN_NAME }} v${{ env.PLUGIN_VERSION }}**
+ By ${{ github.actor }} • ${{ github.sha }}
+ ${{ github.repository }}
+ color: ${{ job.status == 'success' && '0x00ff00' || '0xff0000' }}
+ username: "🏺Artifact Build's"
+ avatar_url: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
+
+ release:
+ needs: build
+ if: github.event_name == 'release'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Download Artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: ${{ env.PLUGIN_FILE }}-${{ env.PLUGIN_VERSION }}
+
+ - name: Upload Release
+ uses: softprops/action-gh-release@v2
+ with:
+ files: ${{ env.PLUGIN_FILE }}-${{ env.PLUGIN_VERSION }}.jar
+ fail_on_unmatched_files: true
+
+ - name: Send Release Notification
+ if: always()
+ uses: sarisia/actions-status-discord@v1
+ with:
+ webhook: ${{ secrets.DISCORD_WEBHOOK }}
+ title: "${{ job.status == 'success' && '🎉 Release Published!' || '❌ Release Failed!' }}"
+ description: |
+ **${{ env.PLUGIN_NAME }} v${{ env.PLUGIN_VERSION }}**
+ ${{ job.status == 'success' && '➜ https://github.com/${{ github.repository }}/releases/latest' || '' }}
+ color: ${{ job.status == 'success' && '0x00ff00' || '0xff0000' }}
+ username: "🏺Artifact Build's"
+ avatar_url: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fffdfce
--- /dev/null
+++ b/README.md
@@ -0,0 +1,125 @@
+
+
+
+
+🛡️ Armor Stand Storage v1.0
+============================
+
+
+Transform your armor stands into functional storage units while maintaining their display capabilities! Perfect for kits, storage rooms, and displays.
+
+**Version:** 1.0
+
+**Minecraft Versions:** 1.8 - 1.21.x
+
+**Release Date:** 1/27/25
+
+✨ Features
+----------
+
+* **Smart Storage System**
+ * Shift + Right-Click any armor stand to access storage
+ * 27 slots of storage space per armor stand
+ * Items in armor slots automatically display on the stand
+ * Regular right-click still works for vanilla interactions
+* **Intelligent Armor Detection**
+ * Automatically detects and displays armor pieces
+ * Works with any armor type (leather, iron, gold, diamond, netherite)
+ * Supports modded armor that follows naming conventions
+ * Compatible with Elytra and player heads
+* **Server Friendly**
+ * Multi-world support with configurable blocked worlds
+ * Minimal performance impact
+ * SQLite database for reliable storage
+ * Works on Minecraft 1.8+
+
+🎮 Usage
+--------
+
+1. Place an armor stand
+2. Add armor/items normally OR
+3. Shift + Right-Click to open storage
+4. Place items anywhere in the inventory:
+ * Armor pieces will automatically display
+ * Other items are safely stored
+ * First 6 slots are prioritized for display
+
+⚙️ Configuration
+----------------
+
+ # Disable storage in specific worlds
+ disabled-worlds:
+ #- spawn_world
+ #- event_world
+
+ # Debug mode for detailed logging
+ debug: false
+
+ # Storage settings
+ storage:
+ inventory-title: "Armor Stand Storage"
+ inventory-rows: 3
+
+🔒 Permissions
+--------------
+
+* `armorstandstorage.use` - Access to armor stand storage (default: op)
+
+📋 Commands
+-----------
+
+No commands - just shift-right-click to use!
+
+💡 Tips
+-------
+
+* Place armor in any slot - it will automatically display
+* Use regular right-click for vanilla armor stand features
+* Items are saved even if the armor stand breaks
+* Works great for shop displays and storage rooms
+* Perfect for RPG servers and town builds
+
+🔧 Installation
+---------------
+
+1. Download the plugin
+2. Place in your plugins folder
+3. Restart server
+4. Configure (optional)
+5. Start using!
+
+🎯 Planned Features
+-------------------
+
+* Custom inventory sizes
+* Per-world inventory settings
+* Hologram support
+* Shop integration
+* API for developers
+
+🆕 What's New in 1.0
+--------------------
+
+* Initial release
+* Full armor stand storage functionality
+* Multi-world support
+* Automatic armor detection and display
+* SQLite database integration
+* Legacy version support (1.8+)
+
+📝 Version History
+------------------
+
+* **1.0** (1.27.2025)
+ * Initial release
+ * Core functionality implemented
+ * Database storage system
+ * Multi-version support
+
+ArmorStandStorage v1.0 - © 2024 Koopa
+
+Made with ❤️ by Koopa
+
+[GitHub Issues](https://github.com/KoopaCode/ArmorStand-Storage/issues) | [Discord](https://discord.gg/KmHGjaHWct)
+
+`storage` `free` `armorstand` `plugin` `1.8-1.21`
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d401bf4
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,83 @@
+
+
+ 4.0.0
+
+ com.koopacraft
+ armorstandstorage
+ 1.0-SNAPSHOT
+ jar
+
+ ArmorStandStorage
+ A plugin that allows storing items in armor stands
+
+
+ 17
+ UTF-8
+
+
+
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
+
+
+
+
+
+ io.papermc.paper
+ paper-api
+ 1.21.1-R0.1-SNAPSHOT
+ provided
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.45.1.0
+ compile
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ ${java.version}
+ ${java.version}
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.5.1
+
+
+ package
+
+ shade
+
+
+ false
+
+
+ org.sqlite
+ com.koopacraft.armorstandstorage.lib.sqlite
+
+
+
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/koopacraft/armorstandstorage/ArmorStandListener.java b/src/main/java/com/koopacraft/armorstandstorage/ArmorStandListener.java
new file mode 100644
index 0000000..6cf3a1f
--- /dev/null
+++ b/src/main/java/com/koopacraft/armorstandstorage/ArmorStandListener.java
@@ -0,0 +1,342 @@
+package com.koopacraft.armorstandstorage;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.ArmorStand;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.event.player.PlayerInteractAtEntityEvent;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryDragEvent;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.event.inventory.InventoryAction;
+import org.bukkit.inventory.EntityEquipment;
+import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
+
+import java.util.HashMap;
+import java.util.UUID;
+
+public class ArmorStandListener implements Listener {
+ private final ArmorStandStorage plugin;
+ private final HashMap openInventories = new HashMap<>();
+ private final boolean isLegacyVersion;
+
+ public ArmorStandListener(ArmorStandStorage plugin) {
+ this.plugin = plugin;
+ // Check if we're running on a legacy version (pre 1.13)
+ isLegacyVersion = !isMethodAvailable("org.bukkit.entity.ArmorStand", "getEquipment");
+
+ // Register all existing armor stands on startup
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ for (org.bukkit.World world : Bukkit.getWorlds()) {
+ for (Entity entity : world.getEntities()) {
+ if (entity instanceof ArmorStand) {
+ plugin.getDatabase().registerArmorStand(entity.getLocation());
+ }
+ }
+ }
+ plugin.getLogger().info("Registered all existing armor stands!");
+ }, 20L);
+ }
+
+ private boolean isMethodAvailable(String className, String methodName) {
+ try {
+ Class> clazz = Class.forName(className);
+ clazz.getMethod(methodName);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private void setArmorStandEquipment(ArmorStand armorStand, ItemStack[] contents) {
+ EntityEquipment equipment = armorStand.getEquipment();
+ if (equipment == null) return;
+
+ equipment.clear();
+
+ try {
+ // Scan entire inventory for armor and items
+ for (int i = 0; i < contents.length; i++) {
+ ItemStack item = contents[i];
+ if (item == null) continue;
+
+ String type = item.getType().name().toUpperCase();
+
+ // Check for armor pieces
+ if (type.endsWith("_HELMET") || type.contains("_HEAD") || type.equals("PLAYER_HEAD") || type.equals("SKULL") || type.equals("SKULL_ITEM")) {
+ equipment.setHelmet(item);
+ }
+ else if (type.endsWith("_CHESTPLATE") || type.equals("ELYTRA")) {
+ equipment.setChestplate(item);
+ }
+ else if (type.endsWith("_LEGGINGS")) {
+ equipment.setLeggings(item);
+ }
+ else if (type.endsWith("_BOOTS")) {
+ equipment.setBoots(item);
+ }
+ // Handle weapons/tools/items for hands
+ else if (i == 4 || (equipment.getHelmet() == null && equipment.getChestplate() == null
+ && equipment.getLeggings() == null && equipment.getBoots() == null)) {
+ if (isLegacyVersion) {
+ equipment.setItemInHand(item);
+ } else {
+ equipment.setItemInMainHand(item);
+ }
+ }
+ else if (!isLegacyVersion && i == 5) {
+ equipment.setItemInOffHand(item);
+ }
+ }
+ } catch (Exception e) {
+ plugin.getLogger().warning("Error setting armor stand equipment: " + e.getMessage());
+ }
+ }
+
+ private void getArmorStandEquipment(ArmorStand armorStand, Inventory inventory) {
+ EntityEquipment equipment = armorStand.getEquipment();
+ if (equipment == null) return;
+
+ try {
+ inventory.setItem(0, equipment.getHelmet());
+ inventory.setItem(1, equipment.getChestplate());
+ inventory.setItem(2, equipment.getLeggings());
+ inventory.setItem(3, equipment.getBoots());
+
+ // Handle main and off hand differently for legacy versions
+ if (isLegacyVersion) {
+ inventory.setItem(4, equipment.getItemInHand()); // Legacy method
+ } else {
+ inventory.setItem(4, equipment.getItemInMainHand());
+ inventory.setItem(5, equipment.getItemInOffHand());
+ }
+ } catch (Exception e) {
+ plugin.getLogger().warning("Error getting armor stand equipment: " + e.getMessage());
+ }
+ }
+
+ @EventHandler
+ public void onArmorStandPlace(CreatureSpawnEvent event) {
+ if (event.getEntityType() == EntityType.ARMOR_STAND) {
+ plugin.getDatabase().registerArmorStand(event.getLocation());
+ plugin.debug("New armor stand registered at: " + formatLocation(event.getLocation()));
+ }
+ }
+
+ @EventHandler
+ public void onArmorStandRemove(EntityDeathEvent event) {
+ if (event.getEntityType() == EntityType.ARMOR_STAND) {
+ if (plugin.getDatabase().isRegistered(event.getEntity().getLocation())) {
+ plugin.getDatabase().removeArmorStand(event.getEntity().getLocation());
+ plugin.getLogger().info("Registered armor stand removed at: " + formatLocation(event.getEntity().getLocation()));
+ } else {
+ plugin.getLogger().info("Unregistered armor stand removed at: " + formatLocation(event.getEntity().getLocation()));
+ }
+ }
+ }
+
+ private String formatLocation(org.bukkit.Location loc) {
+ return String.format("World: %s, X: %.2f, Y: %.2f, Z: %.2f",
+ loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ());
+ }
+
+ @EventHandler
+ public void onArmorStandInteract(PlayerInteractAtEntityEvent event) {
+ if (!(event.getRightClicked() instanceof ArmorStand)) {
+ return;
+ }
+
+ Player player = event.getPlayer();
+ ArmorStand armorStand = (ArmorStand) event.getRightClicked();
+
+ // Check if in disabled world
+ if (plugin.isWorldDisabled(player.getWorld().getName())) {
+ player.sendMessage(plugin.getMessage("blocked-world"));
+ return;
+ }
+
+ // Check if player is sneaking
+ if (!player.isSneaking()) {
+ return; // Allow normal armor stand interaction when not sneaking
+ }
+
+ // Check permission
+ if (!player.hasPermission("armorstandstorage.use")) {
+ player.sendMessage(plugin.getMessage("no-permission"));
+ return;
+ }
+
+ event.setCancelled(true);
+
+ // Create inventory with configured size
+ int size = Math.max(27, plugin.getInventoryRows() * 9);
+ Inventory inventory = Bukkit.createInventory(null, size, plugin.getInventoryTitle());
+
+ // First, check if there's saved inventory data
+ ItemStack[] savedItems = plugin.getDatabase().getArmorStandInventory(armorStand.getLocation());
+
+ if (savedItems.length > 0 && hasItems(savedItems)) {
+ // Load saved inventory
+ for (int i = 0; i < Math.min(savedItems.length, size); i++) {
+ inventory.setItem(i, savedItems[i]);
+ }
+ } else {
+ // No saved items, get current equipment
+ EntityEquipment equipment = armorStand.getEquipment();
+ if (equipment != null) {
+ // Store current equipment in first slots
+ inventory.setItem(0, equipment.getHelmet());
+ inventory.setItem(1, equipment.getChestplate());
+ inventory.setItem(2, equipment.getLeggings());
+ inventory.setItem(3, equipment.getBoots());
+ if (isLegacyVersion) {
+ inventory.setItem(4, equipment.getItemInHand());
+ } else {
+ inventory.setItem(4, equipment.getItemInMainHand());
+ inventory.setItem(5, equipment.getItemInOffHand());
+ }
+
+ // Save this initial state to database
+ plugin.getDatabase().saveArmorStand(armorStand.getLocation(), inventory.getContents());
+ }
+ }
+
+ // Open inventory
+ player.openInventory(inventory);
+ openInventories.put(player.getUniqueId(), armorStand);
+ }
+
+ // Helper method to check if an ItemStack array has any non-null items
+ private boolean hasItems(ItemStack[] items) {
+ for (ItemStack item : items) {
+ if (item != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @EventHandler
+ public void onArmorStandManipulate(PlayerArmorStandManipulateEvent event) {
+ ArmorStand armorStand = event.getRightClicked();
+
+ // Don't interfere if player is sneaking (our storage UI handles that)
+ if (event.getPlayer().isSneaking()) {
+ return;
+ }
+
+ // Schedule a task to save the new equipment state after the vanilla interaction
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ if (!plugin.getDatabase().isRegistered(armorStand.getLocation())) {
+ plugin.getDatabase().registerArmorStand(armorStand.getLocation());
+ }
+
+ // Get current equipment and save it
+ EntityEquipment equipment = armorStand.getEquipment();
+ if (equipment != null) {
+ ItemStack[] contents = new ItemStack[27];
+ contents[0] = equipment.getHelmet();
+ contents[1] = equipment.getChestplate();
+ contents[2] = equipment.getLeggings();
+ contents[3] = equipment.getBoots();
+ if (isLegacyVersion) {
+ contents[4] = equipment.getItemInHand();
+ } else {
+ contents[4] = equipment.getItemInMainHand();
+ contents[5] = equipment.getItemInOffHand();
+ }
+ plugin.getDatabase().saveArmorStand(armorStand.getLocation(), contents);
+ plugin.debug("Saved armor stand equipment after manual interaction");
+ }
+ });
+ }
+
+ @EventHandler
+ public void onInventoryClick(InventoryClickEvent event) {
+ Player player = (Player) event.getWhoClicked();
+ ArmorStand armorStand = openInventories.get(player.getUniqueId());
+
+ if (armorStand == null || !event.getView().getTitle().equals(plugin.getInventoryTitle())) {
+ return;
+ }
+
+ plugin.debug("Player " + player.getName() + " clicked in armor stand inventory");
+
+ // Update equipment immediately after click
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ Inventory inv = event.getView().getTopInventory();
+ ItemStack[] contents = inv.getContents();
+
+ // Update armor stand equipment
+ setArmorStandEquipment(armorStand, contents);
+
+ // Save to database
+ plugin.getDatabase().saveArmorStand(armorStand.getLocation(), contents);
+ });
+ }
+
+ @EventHandler
+ public void onInventoryDrag(InventoryDragEvent event) {
+ Player player = (Player) event.getWhoClicked();
+ ArmorStand armorStand = openInventories.get(player.getUniqueId());
+
+ if (armorStand == null || !event.getView().getTitle().equals(plugin.getInventoryTitle())) {
+ return;
+ }
+
+ plugin.debug("Player " + player.getName() + " dragged in armor stand inventory");
+
+ // Update equipment immediately after drag
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ Inventory inv = event.getView().getTopInventory();
+ ItemStack[] contents = inv.getContents();
+
+ // Update armor stand equipment
+ setArmorStandEquipment(armorStand, contents);
+
+ // Save to database
+ plugin.getDatabase().saveArmorStand(armorStand.getLocation(), contents);
+ });
+ }
+
+ @EventHandler
+ public void onInventoryClose(InventoryCloseEvent event) {
+ Player player = (Player) event.getPlayer();
+ ArmorStand armorStand = openInventories.remove(player.getUniqueId());
+
+ if (armorStand == null || !event.getView().getTitle().equals(plugin.getInventoryTitle())) {
+ return;
+ }
+
+ // Save the exact inventory contents, preserving null slots
+ Inventory inv = event.getView().getTopInventory();
+ ItemStack[] contents = new ItemStack[inv.getSize()];
+
+ // Count non-null items while copying contents
+ int itemCount = 0;
+ for (int i = 0; i < inv.getSize(); i++) {
+ contents[i] = inv.getItem(i);
+ if (contents[i] != null) {
+ itemCount++;
+ }
+ }
+
+ // Save to database
+ plugin.getDatabase().saveArmorStand(armorStand.getLocation(), contents);
+
+ // Update armor stand equipment
+ setArmorStandEquipment(armorStand, contents);
+
+ plugin.debug("Saving inventory with " + contents.length + " slots");
+ plugin.debug("Found " + itemCount + " items to save");
+ plugin.debug("Saved armor stand inventory for " + player.getName());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/koopacraft/armorstandstorage/ArmorStandStorage.java b/src/main/java/com/koopacraft/armorstandstorage/ArmorStandStorage.java
new file mode 100644
index 0000000..5df6053
--- /dev/null
+++ b/src/main/java/com/koopacraft/armorstandstorage/ArmorStandStorage.java
@@ -0,0 +1,79 @@
+package com.koopacraft.armorstandstorage;
+
+import org.bukkit.plugin.java.JavaPlugin;
+import java.io.File;
+import java.util.List;
+import java.util.ArrayList;
+
+public class ArmorStandStorage extends JavaPlugin {
+ private Database database;
+ private List disabledWorlds;
+ private boolean debugMode;
+
+ @Override
+ public void onEnable() {
+ // Create config if it doesn't exist
+ saveDefaultConfig();
+
+ // Load settings
+ disabledWorlds = getConfig().getStringList("disabled-worlds");
+ if (disabledWorlds == null) {
+ disabledWorlds = new ArrayList<>();
+ }
+ debugMode = getConfig().getBoolean("debug", false);
+
+ // Initialize database
+ database = new Database(this, new File(getDataFolder(), "armorstands.db"));
+
+ // Register events
+ getServer().getPluginManager().registerEvents(new ArmorStandListener(this), this);
+
+ // Log startup
+ getLogger().info("ArmorStandStorage has been enabled!");
+ if (!disabledWorlds.isEmpty()) {
+ getLogger().info("Armor stand storage is disabled in: " + String.join(", ", disabledWorlds));
+ }
+ }
+
+ @Override
+ public void onDisable() {
+ if (database != null) {
+ database.close();
+ }
+ getLogger().info("ArmorStandStorage has been disabled!");
+ }
+
+ public Database getDatabase() {
+ return database;
+ }
+
+ public String getMessage(String path) {
+ return getConfig().getString("messages." + path, "Message not found: " + path);
+ }
+
+ public String getDisabledWorld() {
+ return getConfig().getString("disabled-world", "world_KC_SPAWN");
+ }
+
+ public String getInventoryTitle() {
+ return getConfig().getString("storage.inventory-title", "Armor Stand Storage");
+ }
+
+ public int getInventoryRows() {
+ return getConfig().getInt("storage.inventory-rows", 3);
+ }
+
+ public boolean isWorldDisabled(String worldName) {
+ return disabledWorlds.contains(worldName);
+ }
+
+ public void debug(String message) {
+ if (debugMode) {
+ getLogger().info("[Debug] " + message);
+ }
+ }
+
+ public boolean isDebugMode() {
+ return debugMode;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/koopacraft/armorstandstorage/Database.java b/src/main/java/com/koopacraft/armorstandstorage/Database.java
new file mode 100644
index 0000000..1b617f8
--- /dev/null
+++ b/src/main/java/com/koopacraft/armorstandstorage/Database.java
@@ -0,0 +1,201 @@
+package com.koopacraft.armorstandstorage;
+
+import org.bukkit.Location;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.util.io.BukkitObjectInputStream;
+import org.bukkit.util.io.BukkitObjectOutputStream;
+
+import java.io.File;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.sql.*;
+import java.util.Base64;
+
+public class Database {
+ private Connection connection;
+ private final ArmorStandStorage plugin;
+
+ public Database(ArmorStandStorage plugin, File file) {
+ this.plugin = plugin;
+ try {
+ Class.forName("org.sqlite.JDBC");
+ connection = DriverManager.getConnection("jdbc:sqlite:" + file.getAbsolutePath());
+
+ // Create tables if they don't exist
+ try (Statement stmt = connection.createStatement()) {
+ stmt.execute("CREATE TABLE IF NOT EXISTS armor_stands (" +
+ "id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "world TEXT NOT NULL," +
+ "x DOUBLE NOT NULL," +
+ "y DOUBLE NOT NULL," +
+ "z DOUBLE NOT NULL," +
+ "inventory TEXT," +
+ "registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP" +
+ ")");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void registerArmorStand(Location location) {
+ try {
+ String sql = "INSERT OR IGNORE INTO armor_stands (world, x, y, z) VALUES (?, ?, ?, ?)";
+ try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
+ pstmt.setString(1, location.getWorld().getName());
+ pstmt.setDouble(2, location.getX());
+ pstmt.setDouble(3, location.getY());
+ pstmt.setDouble(4, location.getZ());
+ pstmt.executeUpdate();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public boolean isRegistered(Location location) {
+ try {
+ String sql = "SELECT id FROM armor_stands WHERE world = ? AND x = ? AND y = ? AND z = ?";
+ try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
+ pstmt.setString(1, location.getWorld().getName());
+ pstmt.setDouble(2, location.getX());
+ pstmt.setDouble(3, location.getY());
+ pstmt.setDouble(4, location.getZ());
+
+ ResultSet rs = pstmt.executeQuery();
+ return rs.next();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public void removeArmorStand(Location location) {
+ try {
+ String sql = "DELETE FROM armor_stands WHERE world = ? AND x = ? AND y = ? AND z = ?";
+ try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
+ pstmt.setString(1, location.getWorld().getName());
+ pstmt.setDouble(2, location.getX());
+ pstmt.setDouble(3, location.getY());
+ pstmt.setDouble(4, location.getZ());
+ pstmt.executeUpdate();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void saveArmorStand(Location location, ItemStack[] inventory) {
+ try {
+ String serializedInventory = serializeItems(inventory);
+ plugin.debug("Saving inventory: " + serializedInventory);
+
+ String sql = "UPDATE armor_stands SET inventory = ? WHERE world = ? AND x = ? AND y = ? AND z = ?";
+ try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
+ pstmt.setString(1, serializedInventory);
+ pstmt.setString(2, location.getWorld().getName());
+ pstmt.setDouble(3, location.getX());
+ pstmt.setDouble(4, location.getY());
+ pstmt.setDouble(5, location.getZ());
+ int updated = pstmt.executeUpdate();
+
+ // If no rows were updated, the armor stand might not be registered yet
+ if (updated == 0) {
+ sql = "INSERT INTO armor_stands (world, x, y, z, inventory) VALUES (?, ?, ?, ?, ?)";
+ try (PreparedStatement insertStmt = connection.prepareStatement(sql)) {
+ insertStmt.setString(1, location.getWorld().getName());
+ insertStmt.setDouble(2, location.getX());
+ insertStmt.setDouble(3, location.getY());
+ insertStmt.setDouble(4, location.getZ());
+ insertStmt.setString(5, serializedInventory);
+ insertStmt.executeUpdate();
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public ItemStack[] getArmorStandInventory(Location location) {
+ try {
+ String sql = "SELECT inventory FROM armor_stands WHERE world = ? AND x = ? AND y = ? AND z = ?";
+ try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
+ pstmt.setString(1, location.getWorld().getName());
+ pstmt.setDouble(2, location.getX());
+ pstmt.setDouble(3, location.getY());
+ pstmt.setDouble(4, location.getZ());
+
+ ResultSet rs = pstmt.executeQuery();
+ if (rs.next()) {
+ String inventoryData = rs.getString("inventory");
+ plugin.debug("Loading inventory: " + inventoryData);
+ if (inventoryData != null && !inventoryData.isEmpty()) {
+ return deserializeItems(inventoryData);
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return new ItemStack[27];
+ }
+
+ private String serializeItems(ItemStack[] items) throws Exception {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
+
+ // Count non-null items
+ int nonNullCount = 0;
+ for (ItemStack item : items) {
+ if (item != null) nonNullCount++;
+ }
+ dataOutput.writeInt(nonNullCount);
+
+ // Write items with their slot numbers
+ for (int i = 0; i < items.length; i++) {
+ if (items[i] != null) {
+ dataOutput.writeInt(i); // Write the slot number
+ dataOutput.writeObject(items[i]);
+ }
+ }
+
+ dataOutput.close();
+ return Base64.getEncoder().encodeToString(outputStream.toByteArray());
+ }
+
+ private ItemStack[] deserializeItems(String data) throws Exception {
+ if (data == null || data.isEmpty()) {
+ return new ItemStack[27]; // Return empty array with correct size
+ }
+
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data));
+ BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
+
+ int size = dataInput.readInt();
+ ItemStack[] items = new ItemStack[27]; // Fixed size for chest inventory
+
+ // Read items into their exact positions
+ for (int i = 0; i < size; i++) {
+ int slot = dataInput.readInt(); // Read the slot number
+ ItemStack item = (ItemStack) dataInput.readObject();
+ if (slot >= 0 && slot < items.length) {
+ items[slot] = item;
+ }
+ }
+
+ dataInput.close();
+ return items;
+ }
+
+ public void close() {
+ try {
+ if (connection != null && !connection.isClosed()) {
+ connection.close();
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+}
\ 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..a9b750d
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,25 @@
+# ArmorStandStorage Configuration
+
+# Worlds where armor stand storage is disabled
+# Leave empty to allow in all worlds
+# Example:
+disabled-worlds:
+ #- world_KC_SPAWN
+ #- world_event
+ #- minigames_lobby
+
+# Debug mode - set to true for detailed logging
+debug: false
+
+# Messages
+messages:
+ no-permission: "§cYou don't have permission to use armor stand storage!"
+ must-sneak: "§cYou must be sneaking to use this!"
+ blocked-world: "§cArmor stand storage is disabled in this world!"
+
+# Storage settings
+storage:
+ # Inventory title
+ inventory-title: "Armor Stand Storage"
+ # Number of rows in the storage (must be between 1 and 6)
+ inventory-rows: 3
\ 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..fbe9145
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,9 @@
+name: ArmorStandStorage
+version: 1.0
+main: com.koopacraft.armorstandstorage.ArmorStandStorage
+api-version: 1.13
+description: Allows players to store items in armor stands
+permissions:
+ armorstandstorage.use:
+ description: Allows players to use armor stand storage
+ default: op
\ No newline at end of file