Added Features
Configurable Vault Limits: Introduced config.yml with max_vaults (default: 10). Admins can now easily increase/decrease the maximum number of vaults per player. Permissions (playervaults.use.) scale automatically to the new max—grant additional perms as needed.
Periodic Auto-Saving: New save_interval config (default: 300 seconds / 5 minutes) triggers automatic saves of modified vaults. Protects against data loss from crashes or unexpected shutdowns without relying solely on inventory close or plugin disable.
Smart Preloading on Startup:
Always preloads vaults for online players to ensure instant access.
Optional preloading for offline players via preload_offline_vaults config (default: false). This prevents memory overload on large servers while keeping lazy-loading for rarely accessed offline data.
Logs preload stats (e.g., "Preloaded vaults for X players") for easy monitoring.
Dirty Flag System for Efficient Saves: Tracks vault modifications per player/vault using a lightweight boolean map. Only changed ("dirty") vaults are saved during periodic saves, on close, or shutdown—reducing unnecessary disk I/O and TPS impact.
Interaction Detection: Added InventoryClickEvent listener to mark vaults as "dirty" on any click (item placement, removal, etc.). Ensures saves only occur after real changes, optimizing for idle sessions.
Bug Fixes
ItemStack Persistence & Deserialization:
Original loadVault used getList with direct cast to ItemStack[], which failed silently on YAML-stored maps (resulting in empty vaults after restarts).
Fixed: Now loads raw List<?> and explicitly deserializes each slot using ItemStack.deserialize(Map) for YAML maps, handles direct ItemStack objects, and treats invalid/null entries as empty slots. Preserves nulls (empty slots) correctly.
Original saveVault used List.of(vault.getContents()), creating a List<ItemStack[]> instead of List, breaking deserialization.
Fixed: Switched to Arrays.asList(vault.getContents()) for proper List serialization. Bukkit's ConfigurationSerializable now handles enchants, NBT, amounts, and custom data reliably across restarts.
Vault Number Validation: Enforced max_vaults limit in preloading, creation, and saving—skips out-of-range vaults to prevent config bloat or errors. Original hardcoded 10 is now flexible.
UUID Handling in Preload: Original had no preloading; new version safely parses UUID strings from Vaults.yml keys, skips invalid ones, and avoids double-loading online players.
Save on Shutdown: Original saved all vaults; now only saves dirty ones, but ensures all modified data is persisted. Clears maps properly to free memory.
Error Resilience: Added try-catch for UUID/vault number parsing in preload and events. Logs warnings for invalid config entries (e.g., bad vault numbers) without crashing.
Improvements
Performance & Memory Optimization:
Lazy-loading remains for unloaded vaults (fallback in getVault), but preloading is now targeted (online-first, optional offline) to avoid loading thousands of unused inventories on startup.
Dirty flags eliminate redundant saves: Unchanged vaults (e.g., opened but untouched) skip saving on close/periodic, reducing file writes and lag on busy servers.
Periodic saves are scoped to dirty vaults only, minimizing TPS dips (e.g., no full scans of clean data every 5 minutes).
Synchronous saves (main thread) for YAML thread-safety, but low-frequency and efficient—ideal for most servers. (Async could be added later if needed.)
Code Structure & Maintainability:
Refactored into modular methods: preloadPlayerVaults(UUID) for reusable loading, saveDirtyVaults() for targeted saves.
Consistent UUID usage (instead of mixing Player/OfflinePlayer) for offline compatibility.
Auto-generates config.yml with defaults via saveDefaultConfig() and explicit sets—reloadable with /reload (restart recommended for max_vaults changes).
Enhanced logging: Startup info, preload counts, save errors, and invalid data warnings for easier debugging.
Security & Usability:
Vault titles and permissions unchanged, but now dynamically respect max_vaults in command feedback (e.g., "between 1 and X").
Handles edge cases like list size mismatches in loading (extras ignored, shorts fill with empties).
Full persistence guarantee: Data survives startups, shutdowns, restarts, and crashes (via periodic saves). Players' items remain until manually removed.
Event Handling:
InventoryCloseEvent: Now checks dirty flag before saving—skips if clean.
New InventoryClickEvent: Quick title check to mark dirty without content diffs (lightweight and accurate).
Configuration Changes
New config.yml (auto-created with defaults):
max_vaults: 10
save_interval: 300 # Seconds between auto-saves (0 to disable)
preload_offline_vaults: false # true to load all offline players' vaults on startup