Перейти к содержимому
Mineforgian

BackPackSSS

Custom tiered backpacks with progression crafting, upgrade system and persistent storage — designed for survival servers.

Загрузки
349
Подписчики
2
Обновлён
4 июля 2026 г.
Лицензия
Apache-2.0

Опубликован 13 февраля 2026 г.

BackPackSSS

BackPackSSS is a lightweight, survival-friendly backpack plugin for PaperMC 1.21+. It features a 3-tier crafting progression system, custom 3D resource-pack models, SQLite persistence, and robust anti-dupe protection — with minimal server overhead.

Current version: 1.9


Features

Feature Details
3 backpack tiers Weak (18 slots) → Good (27 slots) → Strong (54 slots)
Tier upgrade crafting Craft a higher tier using the previous tier as center ingredient
3D resource pack Custom bundle model per tier (brown / yellow / red)
SQLite persistence Data survives restarts; async dirty-flag saves, sync save on shutdown
Anti-dupe protection Session lock, opening cooldown, death/quit save
Nesting prevention Blocks backpacks, shulker boxes, and bundles inside backpacks
Item filter Explicitly blocks armor, tools, weapons, and enchanted books
Max backpack limit Configurable per-player limit (default: 1)
Permission-based admin backpack.admin for give/reload; works with LuckPerms etc.

Requirements

  • Server: PaperMC 1.21 or later
  • Java: 21+
  • SQLite JDBC: bundled by PaperMC — no extra jars needed

Building from Source

git clone <repo-url>
cd BackPackSSS
mvn clean package
# Output: target/BackPackSSS-1.9.jar

Requires Maven 3.8+ and JDK 21.


Installation

  1. Drop BackPackSSS-1.9.jar into your server's plugins/ folder.
  2. Restart (or reload) the server.
  3. (Optional) Copy resource-pack/ contents, host the pack, and set it as your server resource pack in server.properties.

Commands

Command Permission Description
/backpack or /bp backpack.use Show craft reminder
/bp give <tier> backpack.admin Give yourself a backpack (tier 1–3)
/bp give <tier> <player> backpack.admin Give a backpack to another online player
/bp reload backpack.admin Reload config without restarting

Permissions

Node Default Description
backpack.use everyone Allows opening a backpack
backpack.admin OP Access to give and reload sub-commands

Crafting Recipes

Tier 1 — Weak Backpack (18 slots)

[L][L][L]
[L][C][L]
[L][L][L]
L = Leather  |  C = Chest

Tier 2 — Good Backpack (27 slots)

[I][R][I]
[R][B1][R]
[I][R][I]
I = Iron Ingot  |  R = Redstone  |  B1 = Tier 1 Backpack

Tier 3 — Strong Backpack (54 slots)

[S][D][S]
[D][B2][D]
[S][D][S]
S = Shulker Shell  |  D = Diamond  |  B2 = Tier 2 Backpack

Configuration (config.yml)

auto-discover-recipes: true   # Show recipes in recipe book on join

restrictions:
  max-backpacks: 1            # Max backpacks per player inventory (0 = unlimited)
  allow-nesting: false        # Block backpacks/shulkers/bundles inside backpacks
  filter-special-items: true  # Block armor, tools, weapons, enchanted books

tiers:
  1:
    name: "&fWeak Backpack"
    size: 18                  # Must be a multiple of 9 (max 54)
    cmd: 10101                # CustomModelData matching your resource pack
    lore:
      - "&7Level: &fWeak"
      - "&7Capacity: &b18 slots"
      - ""
      - "&aRight-click to open!"
  # tiers 2 and 3 follow the same structure

Notes on size: Changing a tier's size after players have already stored items is safe — the plugin will trim excess items and log a warning rather than crash.


Item Filter (filter-special-items)

When enabled, the following item categories cannot be placed inside a backpack:

  • Armor (helmets, chestplates, leggings, boots — all materials)
  • Melee weapons (swords — all materials)
  • Tools (axes, pickaxes, shovels, hoes — all materials)
  • Ranged weapons (bows, crossbows, tridents)
  • Shields, elytras, shears, flint and steel
  • Enchanted books
  • Totems of undying

Regular items with a stack size of 1 from other plugins are not blocked unless they match one of the categories above.


Changelog

See CHANGELOG.md for the full history of changes.


Author

Duong2012G — feedback and bug reports welcome.

Ченджлог

1.9.1Релиз1.21.9, 1.21.10, 1.21.11 · 4 июля 2026 г.

BackPackSSS v1.9 → v1.9.1

Reported symptoms

  1. The upgrade recipes for backpacks do not behave correctly in the crafting recipe book.
  2. Players cannot craft the Good Backpack ("Medium", Tier 2) even with the correct materials.

Both symptoms trace back to a single root cause.


BUG-11 (Critical): ExactChoice NBT-lock on the upgrade recipes

File: BackPackSSS.javaregisterRecipes()

Root cause

The Tier 2 and Tier 3 recipes used the center ingredient slot ('B') like this:

ItemStack centerItem = backpackManager.createBackpack(1); // freshly generated, no BP_ID yet
...
recipe.setIngredient('B', new RecipeChoice.ExactChoice(centerItem));

RecipeChoice.ExactChoice requires the item placed in the crafting grid to be byte-for-byte NBT-identical to centerItem (via ItemStack.isSimilar). At the moment registerRecipes() runs, centerItem only carries BP_TIER_KEY in its PersistentDataContainer — it has never been opened, so it has no BP_ID_KEY yet.

The problem is BackpackListener#onInteract:

if (!pdc.has(BackpackManager.BP_ID_KEY, PersistentDataType.STRING)) {
    UUID newId = UUID.randomUUID();
    pdc.set(BackpackManager.BP_ID_KEY, PersistentDataType.STRING, newId.toString());
    item.setItemMeta(meta);
}

The very first time a player right-clicks their Weak Backpack to open it (which is the normal thing every player does), a random BP_ID_KEY is permanently stamped onto that specific item's NBT. From that point on, the item can never be isSimilar() to the "clean" centerItem used to register the recipe — every real, previously-used backpack in the world is now a different NBT snapshot than the one the recipe was built from.

When that happens, Bukkit's own recipe matcher rejects the shape entirely before BackpackListener#onPrepareCraft (the plugin's own tier-validation handler, which already only checks BP_TIER_KEY and is correct) ever gets a chance to run:

  • The crafting table shows no output at all → "cannot craft the Medium Backpack".
  • The recipe-book auto-fill/highlight can't find any inventory item that satisfies the exact NBT requirement, so the upgrade recipe never lights up as craftable even when the player is holding a valid Weak Backpack → "recipe doesn't show/work in the recipe book".

In short: the plugin already had correct validation logic in onPrepareCraft(), but the ExactChoice ingredient acted as a stricter gate in front of it that silently blocked everything as soon as any backpack had been used once.

Fix

Replaced the ingredient with a plain MaterialChoice, matching the fallback branch the code already used when centerItem was null:

recipe.setIngredient('B', new RecipeChoice.MaterialChoice(Material.BUNDLE));

Now Bukkit's matcher only checks "is this a Bundle in the center slot", and the existing onPrepareCraft() logic (isBackpackTier(center, 1) / isBackpackTier(center, 2)) — which correctly reads only BP_TIER_KEY and ignores the per-instance BP_ID_KEY — is what actually decides whether the craft is valid and sets the result. A used backpack, a freshly crafted one, or one obtained via /backpack give all now work identically as upgrade material. A plain vanilla Bundle or wrong-tier backpack placed in the center slot still correctly produces no result.

Files changed

  • BackPackSSS.java (registerRecipes()), Tier 2 and Tier 3 blocks.

Verification steps for testing

  1. /backpack give 1 <player> → craft is not required for this check.
  2. Right-click the Weak Backpack to open it once (this stamps BP_ID_KEY).
  3. Place it in the center of a crafting table with Iron + Redstone around it.
  4. Confirm the Good Backpack (Tier 2 / "Medium") now appears as the output and can be taken.
  5. Confirm the recipe is discoverable and highlights correctly in the recipe book.
  6. Repeat for Tier 2 → Tier 3 with Shulker Shell + Diamond.

Version bumped: 1.9 → 1.9.1 (pom.xml, plugin.yml, config.yml header).

BackPackSSSРелиз1.21.9, 1.21.10, 1.21.11 · 24 мая 2026 г.

[1.9] - 2026-05-24

Fixed

  • 🔴 Critical — Race condition: async save after connection close saveSession() dispatches asynchronous write tasks. If one of those tasks was still in flight when syncSaveAll() returned and DatabaseManager.closeConnection() was called (during shutdown or /bp reload), the task would attempt to write to an already-closed connection, silently losing data. Fixed by introducing a volatile boolean shuttingDown flag in BackpackManager: syncSaveAll() sets the flag before entering, which causes saveSession() to no-op for any new attempts. syncSaveAll() then spin-waits (up to 5 seconds) for any already-running async tasks to land before proceeding with its own synchronous pass, ensuring the connection is only closed after all writes have completed.

  • 🔴 Critical — Memory leak: sessions map never evicted after close closeSession() removed a backpack from activeBackpacks and playerToBackpack but never called sessions.remove(). Every unique backpack UUID opened since the last restart accumulated indefinitely, causing unbounded memory growth on long-running servers. Fixed by adding sessions.remove(backpackUUID) in both closeSession() and closePlayerSession().

  • 🟡 Medium — DatabaseManager.init() could fail silently on first run createNewFile() throws IOException when its parent directory does not exist. On a fresh install, if saveDefaultConfig() had not yet been called, the database file could not be created, leaving the connection null and causing NPEs on any subsequent load. Fixed by calling plugin.getDataFolder().mkdirs() before attempting to create the file, and added a return-early path when createNewFile() returns false.

  • 🟡 Medium — BackpackSession.playerUUID was always null getBackpackSession() always passed null as the first constructor argument, making BackpackSession.getPlayerUUID() permanently useless. Fixed by threading the Player's UUID from openBackpack() through to getBackpackSession(), which now accepts UUID playerUUID as its first parameter.

  • 🟡 Medium — isForbiddenItem() used getMaxStackSize() == 1, which was too broad The heuristic blocked music discs, name tags, saddles, empty buckets, and any item from third-party plugins that happens to have a stack size of 1 for unrelated reasons. Replaced with an explicit category check using Material.name() suffixes (armor, swords, axes, pickaxes, shovels, hoes) and individual constants for bows, crossbows, tridents, shields, elytras, shears, flint-and-steel, enchanted books, and totems of undying — exactly the items documented in config.yml.

  • 🟡 Medium — onClick() marked session dirty before restriction checks session.setDirty(true) was called at the top of onClick(), before the nesting and item-filter checks that might cancel the event. A cancelled click does not modify the inventory, so marking it dirty caused unnecessary async saves. Fixed by moving setDirty(true) to execute only after all checks pass. Same fix applied to onDrag().

  • 🟡 Medium — PlayerInteractEvent fired for both hands, processing the backpack twice Without an EquipmentSlot.HAND guard, right-clicking with a backpack in the main hand generated two PlayerInteractEvent calls per click. The opening cooldown absorbed the second call, but both calls entered onInteract() and evaluated the PDC. Added if (event.getHand() != EquipmentSlot.HAND) return; as an early exit.

  • 🟡 Medium — InventorySerializer did not close streams on exception Both toBase64() and fromBase64() closed their BukkitObject*Stream only on the happy path. If serialization or deserialization threw mid-loop, the stream leaked. Replaced both methods with try-with-resources blocks.

  • 🟡 Medium — PrepareItemCraftEvent iterated player inventory unnecessarily countBackpacks() was called on every PrepareItemCraftEvent tick even when max-backpacks was 0 (unlimited). Added an early-return guard so the inventory scan only runs when a real limit is configured.

  • 🟢 Minor — BackpackCommand lacked explicit tier range validation A tier number outside [1, 3] was silently forwarded to createBackpack(), which returned null, producing a generic "invalid tier" message through an indirect path. Added upfront Integer.parseInt() + range check against TIER_MIN/TIER_MAX constants, returning the messages.invalid-tier config message immediately.


1.8Релиз1.21.9, 1.21.10, 1.21.11 · 22 апреля 2026 г.

[1.8] - 2026-04-21

Fixed

  • 🔴 Critical — onDrop never fired: getSessions() returns a Map<BackpackUUID, Session> but the old code checked getSessions().containsKey(playerUUID). Because the key is a backpack UUID, the check was always false — players could freely drop items while a backpack was open. Fixed by adding BackpackManager.isPlayerHasOpenBackpack(playerUUID) which queries the correct playerToBackpack map.

  • 🔴 Critical — isBackpack() missed freshly crafted items: isBackpack() previously checked for BP_ID_KEY, which is only assigned on the first right-click of a backpack item. Brand-new crafted backpacks had no ID yet, so they were invisible to the pickup/max-backpack limit check. Fixed by checking BP_TIER_KEY instead, which is always present from createBackpack().

  • 🔴 Critical — Data loss race condition in saveAll(): The old saveAll() dispatched async save tasks and then immediately called sessions.clear(). The async lambdas could still be running when the map was cleared, leading to NPE or lost saves on server shutdown/reload. Replaced with a new syncSaveAll() that blocks on the main thread during disable/reload, ensuring all data is written before the connection is closed.

  • 🟡 Task leak on /bp reload: reloadPlugin() created a new BackpackManager (and called startAutoSaveTask() on it) without ever cancelling the previous manager's repeating task. Each reload stacked another permanent 2-minute timer. Fixed by calling cancelAutoSaveTask() before tearing down the old manager.

  • 🟡 Auto-save not restarted after reload: reloadPlugin() built a new BackpackManager but forgot to call startAutoSaveTask() on it — the replacement manager ran with no auto-save at all. Fixed.

  • 🟡 Hardcoded version string in startup log: onEnable() printed "version 1.0" regardless of the actual version. Changed to getDescription().getVersion().

  • 🟢 Config typo — "Week Backpack": Tier-1 display name was "Week Backpack" (a day of the week) instead of "Weak Backpack". Fixed in config.yml.

Improved

  • BackpackCommand now uses backpack.admin permission instead of player.isOp(), allowing server admins to grant give/reload access via any permissions plugin (LuckPerms, etc.).

  • /backpack give <tier> [player]: Admin can now give a backpack directly to another online player: /bp give 2 Steve. Tab-completion lists online player names.

  • Recipe deduplication on reload: registerRecipes() now removes old recipes before re-adding them, preventing "duplicate recipe" warnings in the console on each /bp reload.

  • Safe inventory size mismatch handling: If a backpack's stored item count exceeds the configured size (e.g. after an admin reduces the tier size), the plugin now logs a warning and truncates gracefully instead of throwing an ArrayIndexOutOfBoundsException.

  • plugin.yml: Added backpack.admin permission entry with default: op. Updated description and usage string for the backpack command.


1.7Релиз1.21.9, 1.21.10, 1.21.11 · 28 марта 2026 г.

[1.7] - 2025-03-28

Fixed

  • Memory Leak: Fixed recipeDiscoveryCache not clearing when players quit - UUIDs now properly removed on PlayerQuitEvent
  • Deprecated API: Replaced deprecated org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder with native java.util.Base64
  • Code Quality: Improved serialization stability using Java 8+ standard Base64 encoder/decoder
1.6Релиз1.21.9, 1.21.10, 1.21.11 · 17 февраля 2026 г.

Added a null check in BackpackListener.java to prevent the crash.

1.4Релиз1.21.9, 1.21.10, 1.21.11 · 14 февраля 2026 г.

Stability Update

Reworked backpack saving system to prevent lag, duplication and rollback. Backpacks now use session caching, async safe saving and autosave protection.

Improved shutdown handling and fixed rare issue where a backpack could stop saving after a disk error.

Safe for long-term public servers.

1.2Релиз1.21.9, 1.21.10, 1.21.11 · 13 февраля 2026 г.

🛡️ Industrial-Grade Stability Thread-Safe Snapshot Saving: Implemented inventory snapshotting. Inventory contents are now cloned on the Main Thread before being passed to asynchronous SQLite tasks, completely eliminating data corruption or item loss during high-activity movement. BackpackHolder (Custom Identifier): Introduced a custom InventoryHolder specifically for backpacks. This ensures 100% accurate identification of backpack inventories, preventing any accidental interaction with Chests, Barrels, or other GUI menus. Force-Save on Disconnect: Added a fallback mechanism to trigger an immediate save during PlayerQuitEvent (Quit, Kick, or Timeout), ensuring player data is never rolled back even during erratic disconnects. ⚡ Performance & Anti-Exploit SQLite Database Integration: Migrated from slow YAML file storage to a professional SQLite engine. All I/O operations are performed asynchronously, maintaining a stable 20 TPS even with a high player count. Anti-Dupe Opening Lock: Integrated a logic lock and a 0.1s cooldown for opening backpacks to prevent common duplication glitches caused by rapid interaction spam. Shift + Click Support: Fully enabled Shift-Click functionality, allowing users to quickly deposit or withdraw items without tedious drag-and-drop operations. 🚫 Custom Restrictions & Rules (Configurable) Nesting Prevention: Strictly blocks players from placing a backpack inside another backpack to prevent infinite storage loops. Carriage Limits: Added a configurable max-backpacks limit (default: 1) to control the number of backpacks a single player can carry. Intelligent Item Filtering: Introduced a toggleable filter to block Armor, Weapons, Tools, and Books from being stored in backpacks, ensuring balanced gameplay and reduced NBT complexity. 🎨 Compatibility & Graphics 1.21.x Hybrid Compatibility: Optimized the file structure to support both legacy 1.21 rendering and the new 1.21.4+ Item Components system. Namespace Standardization: Unified all assets under the backpacks namespace, resolving "black & purple" texture errors and ensuring smooth 3D model rendering. Tiered Crafting Progression: Refined recipes to require the previous tier's backpack as the core ingredient, creating a more rewarding upgrade path.

1.1Релиз1.21.9, 1.21.10, 1.21.11 · 13 февраля 2026 г.

BackPackSSS Industrial Stabilization Update Comprehensive stability, performance, and security overhaul for production environments.

🛡️ Industrial-Grade Stability Thread-Safe Snapshot Saving: Implemented inventory snapshotting. Contents are cloned on the main thread before being passed to asynchronous SQLite tasks, completely eliminating data corruption or loss during item movement. Reliable Identification (BackpackHolder): Introduced a custom InventoryHolder to uniquely identify backpack inventories. This prevents the plugin from accidentally interacting with or overwriting data from standard chests or other GUIs. Force-Save on Disconnect: Added a fallback save mechanism for PlayerQuitEvent. Whether a player quits, is kicked, or times out, their backpack contents are instantly saved to the database. ⚡ Performance & Anti-Exploit Logic Anti-Dupe Opening Lock: Integrated a 2-tick (0.1s) cooldown and a logic lock for opening backpacks. This eliminates race conditions and duplication glitches caused by rapid interaction spam. State Cleanup: Integrated mandatory closeInventory() calls before every new backpack session to ensure clean transitions and prevent persistent inventory UI bugs. Memory Optimization: Improved memory management by purging player session locks and temporary caches immediately upon logout. 🎨 High-Fidelity Resource Pack (v1.21.x) Hybrid Version Support: Fully optimized structure supporting both Minecraft 1.21-1.21.3 (via models) and 1.21.4+ (via new items range_dispatch syntax). Universal Namespace: Standardized all assets under the backpacks namespace for consistent texture mapping and professional organization. 3D Model Integration: Verified and synced high-quality 3D backpack models (Brown, Yellow, Red) with correct CustomModelData mapping.

Комментарии

Загружаем…