This page lists what changed in each release, in the order it shipped. Each entry describes the final, working behavior - not the intermediate steps taken to get there.
Coming next
1.3.4 - dialog system research
A community member proposed replacing the current inventory/anvil/chat interaction system with Minecraft's dialog system (issue #1). It would unify every current interaction method into one interface and could add a button to the pause menu. Adopting it means either dropping support for versions below 1.21.6, or maintaining both interaction systems side by side. Research is starting; nothing about this is final yet.
1.3.5 - language overhaul
Every bundled language file except English and Spanish is currently outdated to varying degrees, some carrying leftover content from features that no longer exist in this fork. 1.3.5 replaces every one of those files wholesale with the equivalent file from the upstream project, then reconciles it with the strings this fork adds on top (integrations, per-world config, and so on).
Alongside that, a community translation system is planned: a dedicated languages section on the project's Discord (https://discord.gg/RRcjuMr9Jd), where contributors can submit translation updates and corrections for review instead of relying on a single pass to keep every language current.
1.3.3 is finally out
After a long development cycle, I'm officially releasing 1.3.3. This is the biggest update this fork has shipped: five new plugin integrations (WorldGuard, Lands, Towny, ClaimChunk, PlaceholderAPI), item frame and storage vehicle protection, villager workstation protection, raid detection, a rebuilt lockables GUI, and a long list of inherited and fork-specific bug fixes. Everything below this point describes what actually shipped.
1.3.3
Breaking changes (0.1% of servers affected)
- All lockable lists are empty by default. On a fresh install,
blocks.ymlstarts with[]for every list, includinglockable_entities. The admin configures lockable blocks via/bp lockablesin-game, by editingblocks.ymldirectly, or by running the/bp recommendedconsole command to populate a sensible default set. Existing servers upgrading are unaffected: theirblocks.ymlis never overwritten. - Bidirectional blocks.yml startup conversion re-added with guard. The
modern_family_blocksflag now auto-converts blocks.yml format on/bp reload(viaconvertBlocksFormatIfNeeded()), but only when the file format actually mismatches the flag. This conversion preserves all material selections, only the format changes. On the first reload after changing the flag, the file is converted; subsequent reloads are no-ops. Manual edits are preserved (materials are never dropped, only reformatted). The infinite-reload-loop bug is prevented bysuppressNext()before every save. - Config file writes only when dirty.
cleanLegacyConfigKeys()andmergeMissingConfigKeys()now only write to disk when a change was actually made. This stops the infinite auto-reload loop caused by the plugin overwriting its own config files. - File watcher console spam reduced. The watcher logs one short line to the console ("Configuration change detected; reloading...") and one line to the session log per change. Previously it logged two console lines. World scan messages only print when worlds are actually added or patched.
- Startup banner with ASCII art. BlockProt prints an ASCII "BPR" logo on startup in a light coffee color. The plugin name, version line, and boot status lines that follow print uncolored.
/bp recommendedconsole command. Generates a sensible default blocks.yml. Console-only. See "First-start console guide" below for how new admins are pointed to it.- Per-world lockable GUI. When
per_worlds_config: true, the lockables GUI shows a MAP button (slot 48) that opens a world selection inventory. Each world shows its enabled/disabled status and protected block count. Click a world to configure its individual lockable lists. - Auto-disable modern_family_blocks. When all lockable lists become empty via GUI toggling,
modern_family_blocksauto-sets tofalsein config.yml. - Translation keys added:
worlds.status,worlds.protected_count,worlds.no_worlds,worlds.world_config_hint,worlds.per_world_config,worlds.worlds,worlds.configured.
Additions
Fixed menu layout for all block and entity types
Auto-drop to inventory configuration GUI
A new GUI to configure auto_drop_to_inventory.blocks is accessible from the /bp lockables inventory (DROPPER item at slot 46). The page shows each family (Tile Entities, Shulker Boxes, Blocks, Doors, Entities) with a representative icon and its active/inactive/partial status.
- Right-click a family toggles all its members at once (enables or disables auto-drop for the entire family).
- Left-click opens a paged sub-menu listing every material in that family. Each material can be toggled individually.
- The back button returns to the lockables GUI.
New methods in DefaultConfig: toggleAutoDropMaterial(), toggleAutoDropFamily(), representativeMaterialForFamily(). All toggles persist to blocks.yml on disk.
Lockables GUI blocks no longer move on toggle
The active-status sort was removed. Blocks stay in their family section sorted by name only. Toggling a block on or off no longer changes its position in the inventory.
Fixed crash on non-item materials in auto-drop sub-menu
Materials that cannot be held in an ItemStack (LAVA_CAULDRON, WATER_CAULDRON, POWDER_SNOW_CAULDRON, wall-mounted signs) are resolved to display equivalents before creating ItemStacks. The sub-menu no longer crashes when opening the Blocks family.
The Block Lock menu previously placed its buttons using a counter that incremented for each button actually shown. When a button did not apply to the current block (for example, no Redstone button on a workstation), every button after it shifted one slot to the left. This meant the same menu looked different depending on which block type opened it, and the bottom-row buttons (Inspect, Audit Log, Paste, Copy, Info) ended up in inconsistent slots between block types.
Every button now has one fixed slot, used by every block and entity type. A button that does not apply to the current type simply leaves its slot empty; the other buttons never move.
Fixed slots, top row:
| Slot | Item | Shown for |
|---|---|---|
| 0 | Block icon | Always |
| 1 | Redstone | Storage and traversal blocks |
| 2 | Player Head | Always, unless friend functionality is disabled |
| 3 | Name Tag | Always |
| 4 | Ender Pearl | Always |
| 5 | Emerald | Workstation blocks only |
Fixed slots, bottom row:
| Slot | Item | Shown for |
|---|---|---|
| 9 | Spyglass | Storage blocks with an inventory, owner or admin |
| 13 | Clock | Owner or admin, audit logger active |
| 14 | Knowledge Book | Manager role, clipboard has data |
| 15 | Paper | Manager role |
| 16 | Compass | Manager role or admin |
| 17 | Barrier | Always |
The entity menu (fillForEntity) was fixed the same way: Name Tag stays at slot 0, Redstone and Ender Pearl only appear for storage vehicles (chest boats, storage/hopper minecarts) and always sit at slots 1 and 4, Player Head always sits at slot 2, Spyglass and Clock sit at 9 and 10 for storage vehicles, and Compass sits at 13.
Entity protection rename
The protection system covering tamed animals (wolves, cats, parrots, horses, llamas) was renamed end-to-end from "pet protection" to "entity protection" across class names, package names, config keys, NBT persistent-data keys, and method names. This is purely a naming and organization change; behavior is identical to the previous pet protection feature.
The pet_protection key in config.yml is renamed to entity_protection. Upgrading servers do not need to edit anything: on first startup after the upgrade, the plugin reads the old key, writes its value under the new name, and removes the old entry automatically. Old NBT data stored under the legacy pet_owner / pet_protected keys is migrated to the new keys automatically as well.
Villager workstation protection
A villager whose job-site memory points to a protected workstation block (grindstone, stonecutter, loom, cartography table, smithing table, enchanting table, fletching table, lectern, composter, brewing stand, blast furnace, smoker, barrel, cauldron) inherits that block's protection automatically - no separate configuration is needed per villager.
Protected actions:
- Damage to the villager by non-owners (melee and projectiles)
- Trading interaction by non-owners
- Breaking blocks in a search area around the workstation
- Interacting with blocks in that area (except the workstation block itself)
The search area is configurable independently of whether the feature is enabled:
villager_workstation_protection:
enabled: true
radius: 2 # horizontal search radius in blocks, clamped to 0-8
vertical_radius: 1 # vertical search radius in blocks, clamped to 0-4A new Emerald button ("Locate Linked Villager") appears in the lock menu for a protected workstation that has a villager linked to it. Clicking it closes the menu and shows a short particle effect at the villager's location, visible only to the clicking player, for a configurable duration (entity_protection.villager_locate_seconds, 1-10 seconds, default 6). If no villager is linked, nothing happens and no error is shown.
Item frame protection and automatic block linking
ITEM_FRAME and GLOW_ITEM_FRAME are full members of the ENTITIES family under the ITEM_FRAMES sub-family. They appear in /bp lockables with Status: INACTIVE by default (not included in lockable_entities) or ACTIVE when listed.
A frame mounted on the face of a lockable block is automatically linked to that block at the moment it is placed. A linked frame has no protection state of its own: no owner, no friend list, no lock toggle. Interacting with it opens the underlying block's lock menu directly, and the frame's protection always matches the block's exactly - same owner, same friends, locks and unlocks together. The link is stored bidirectionally (the frame stores the block's coordinates, the block stores the frame's entity UUID) so it survives chunk reloads and server restarts, and is validated on each interaction in case the frame or block changed since linking.
A frame not mounted on a lockable block, or mounted on a block that is not currently lockable, keeps the standalone entity-protection flow: sneak and right-click with an empty hand opens the BlockProt entity menu directly, exactly like 1.3.2.
Supported token: *-ITEM_FRAMES to include only frames, or -*ITEM_FRAMES to exclude all frames, in a lockable_entities family expression.
Storage vehicle protection completed
Chest boats, storage minecarts, and hopper minecarts use the same sneak-and-right-click flow as item frames. Once protected:
- Right-clicking without sneaking is blocked for non-owners and non-friends.
- Damaging or destroying the vehicle (
VehicleDamageEvent,VehicleDestroyEvent) is blocked for everyone except the owner and admins. - Hopper-pipeline extraction from the vehicle's inventory is blocked unless the owner has disabled that protection from the in-menu toggle.
- Placing the vehicle auto-locks it to the placing player if their "lock on place" setting is enabled and they are not sneaking, mirroring the behavior already in place for blocks and item frames.
ChestBoat is resolved at runtime via reflection so the same code path supports both the 1.20.x class location (org.bukkit.entity.ChestBoat) and the 1.21+ location (org.bukkit.entity.boat.ChestBoat).
EntityInfoInventory location display
The info panel for a protected entity shows an Oak Sign in slot 1 displaying the entity's type and current world/coordinates, replacing the Compass-based block-info display that does not apply to entities (entities do not have NBT-stored placement coordinates the way blocks do).
BlockFamilyParser cross-family validation and /bp lockables
BlockFamilyParser parses compact family expressions in blocks.yml and worlds.yml. Expressions are always parsed regardless of the modern_family_blocks flag in config.yml - that flag only controls whether flat material lists are auto-converted to expressions on startup.
A NAME or -NAME token inside an expression is now validated against the family of the config key it appears in. A material that does not belong to that family is rejected with a console warning and discarded, rather than silently producing an incorrect or empty result. For example, FLETCHING_TABLE (a BLOCKS workstation) inside a lockable_tile_entities expression is rejected with a warning, while COPPER_CHEST (a valid CHEST sub-family member) is accepted.
/bp lockables is a new admin command (blockprot.user.admin) that opens a six-row paged GUI listing every block and entity the family system knows about, regardless of whether it is currently enabled. Active entries show a green Status: ACTIVE label; disabled entries show a red Status: INACTIVE label. Left-clicking an entry sends a clickable chat message that copies the material name; right-clicking copies the exclusion token (-MATERIAL_NAME). The info book in the GUI shows server version, client version (via ViaVersion, if installed), and active/inactive counts.
Token reference:
| Token | Meaning |
|---|---|
* |
All members of the family |
*-TAG |
All members of the named sub-family |
-*TAG |
Exclude all members of the named sub-family |
NAME |
Include one specific material |
-NAME |
Exclude one specific material |
Sub-families per config key:
| Key | Sub-families |
|---|---|
lockable_tile_entities |
CHEST, FURNACE, SHELF, TRANSPORT, MISC, SIGN |
lockable_shulker_boxes |
SHULKERS |
lockable_blocks |
ANVIL, CAULDRON, WORKSTATION, TRAPDOOR, FENCE_GATE |
lockable_doors |
DOORS |
lockable_entities |
CHEST_BOATS, CHEST_MINECARTS, HOPPER_MINECARTS, ITEM_FRAMES |
Full syntax guide and worked examples: docs/MODERN SYNTAX AND LEGACY/BLOCK_FAMILY_SYNTAX.md. Both the plain flat-list format (one material per line with -) and the bracketed expression format are accepted, and can be mixed across different lines of the same list - see the "Two valid formats" section of that document.
ViaBackwards and ViaRewind detection
ViaVersionIntegration now detects ViaBackwards and ViaRewind in addition to ViaVersion, logging the version of each detected companion plugin.
WorldGuard integration
WorldGuardIntegration registers a custom region flag, allow-blockprot, defaulting to allow. Setting the flag to deny on a specific region (/rg flag <region> allow-blockprot deny) blocks BlockProt protections from being created or accessed inside that region. Everywhere else behaves exactly as if WorldGuard were not installed. The flag is registered during plugin load, before WorldGuard finishes enabling, per WorldGuard's custom-flag registration timing.
Configured from integrations.yml:
worldguard:
enabled: true
enable_flag_functionality: trueSetting enable_flag_functionality: false keeps WorldGuard detected (it still shows Hooked on the startup banner) without ever checking the flag.
Lands integration
LandsPluginIntegration registers a custom role-flag, "Protect containers (BlockProt)", that land owners assign per role. A player without that flag in the claimed area covering a protected block is denied locking or accessing it. Unclaimed wilderness follows a separate config option instead of the role-flag.
An optional second role-flag, "Require "Protect containers" for friends (BlockProt)", lets a land owner require that both the block owner and the friend being added hold the protect-containers flag before that friend can access the block.
Configured from integrations.yml:
lands:
enabled: true
allow_protecting_containers_in_wilderness: true
require_protect_for_friends_flag: falseBoth role-flags are registered during plugin load, mirroring WorldGuard's registration timing requirement.
Towny integration
TownyIntegration checks Towny protection on blocks and entities. A player must be a town resident to lock or access protected blocks within that town's claimed area. Ruined towns bypass the resident check entirely (cancelling the lock/access attempt before even checking, rather than allowing it when the resident check fails).
Three bugs fixed in this release:
onLockOnPlaceresident check was inverted: a missing!caused locking to be denied in every town instead of being denied outside towns.shouldBypassProtectionsInRuinedTownsused||instead of&&: the wrong operator meant all players bypassed protection in ruined towns.shouldCleanupAfterUnclaimused||instead of&&: protection cleanup ran on every world change instead of only when a town was actually unclaimed.
Configured from integrations.yml:
towny:
enabled: true
cleanup_plots_after_unclaim: true
restrict_access_to_residents: false
allow_mayor_to_see_block_info: false
bypass_protection_in_ruined_towns: trueAdditional options: restrict_access_to_residents denies lock/access to non-residents even on unlocked blocks; allow_mayor_to_see_block_info grants the /bp info permission to the town mayor; cleanup_plots_after_unclaim removes protections when a plot is cleared or unclaimed; bypass_protection_in_ruined_towns allows container access in ruined towns without owning the block.
ClaimChunk integration
Requested in issue #298.
ClaimChunkIntegration restricts block locking and full access (lock/unlock/edit) to the chunk owner or a trusted member:
- Locking (
onLockOnPlace,onPreLock): only the chunk owner can create protections. - Full access (
onPreventFullAccess): only the chunk owner (or trusted members, ifrestrict_access_to_chunk_owneris false) can unlock or edit settings. isFriend(): only the chunk owner is accepted as a "natural friend"; there is no per-chunk friend list of its own, so the standard BlockProt friend-management page continues to work normally for owner-to-owner sharing.
Configured from integrations.yml:
claimchunk:
enabled: true
restrict_access_to_chunk_owner: falseSetting restrict_access_to_chunk_owner: true restricts full access (lock/unlock/edit) to the chunk owner only, in addition to locking.
PlaceholderAPI integration
PlaceholderAPIIntegration registers a PlaceholderExpansion providing BlockProt placeholders for the server's PAPI system. Available placeholders:
| Placeholder | Returns |
|---|---|
%blockprot_global_block_count% |
Total protections on the server |
%blockprot_own_block_count% |
Number of protections owned by the querying player |
%blockprot_default_friends% |
The player's default friend list |
Configured from integrations.yml:
placeholderapi:
enabled: trueViaVersion integration
ViaVersionIntegration detects ViaVersion, ViaBackwards, and ViaRewind at startup and logs the version of each detected companion plugin. The /bp debug run command includes ViaVersionIntegration.getDetailedStatus() for the full version summary.
Configured from integrations.yml:
viaversion:
enabled: trueCentralized integrations.yml configuration
All integration configuration is now in a single integrations.yml file inside the plugin data folder (plugins/BlockProtReloaded/integrations.yml). The old per-integration files (integrations/worldguard.yml, integrations/lands.yml, integrations/towny.yml, etc.) are no longer shipped or read.
Each integration section has an enabled: true/false toggle. Setting enabled: false for an integration stops it from registering hooks and listeners without requiring the external plugin to be removed from the server; the integration is simply ignored at startup.
The WorldEdit paste-autolock settings (paste_autolock.enabled, paste_autolock.radius, paste_autolock.delay_ticks, paste_autolock.block_limit) were moved from config.yml into integrations.yml under the worldedit section. Old keys in config.yml are still read (marked @Deprecated in code) but no longer written.
Full integrations.yml structure:
worldguard:
enabled: true
enable_flag_functionality: true
lands:
enabled: true
allow_protecting_containers_in_wilderness: true
require_protect_for_friends_flag: false
towny:
enabled: true
cleanup_plots_after_unclaim: true
restrict_access_to_residents: false
allow_mayor_to_see_block_info: false
bypass_protection_in_ruined_towns: true
claimchunk:
enabled: true
restrict_access_to_chunk_owner: false
worldedit:
paste_autolock:
enabled: false
radius: 24
max_blocks_per_paste: 5000
delay_ticks: 20
placeholderapi:
enabled: true
viaversion:
enabled: trueFirst-start console guide
On the very first startup of a fresh install, if no lockable blocks are configured yet, the console prints a short guide directly under the startup banner: use /bp lockables in-game, edit blocks.yml manually, read the full docs on GitHub, or run /bp recommended in console for a sensible default set. The guide prints once per fresh install, tracked by a .first_start_done marker file in the plugin data folder. A server migrated from a legacy install never sees it, since it is not a fresh install.
Debug command expanded
/bp debug run now also tests:
| Check | What it verifies |
|---|---|
| Lockable entities | Lists all ENTITIES family members with ACTIVE / INACTIVE status |
| Item frame protection | Reads isLockableEntity(ITEM_FRAME) and isLockableEntity(GLOW_ITEM_FRAME) and logs the result |
| Raid detection config | Reads raid_detection.enabled and confirms the audit logger is active |
| Integrations (expanded) | Calls ViaVersionIntegration.getDetailedStatus() for the full Via summary including companion plugins |
| NBT entity write/read | Spawns a temporary ArmorStand, writes an owner UUID, reads it back, then removes the entity |
Audit log action classification
Clarified and corrected audit-trail event meanings:
ACCESS_DENIED- a player attempted to access a protected block/entity and was blockedOPENED- a player with access opened the inventory of a protected blockRAID_EXPLOSION- an explosion affected a protected blockITEM_TAKEN/ITEM_PLACED- a player moved items in a protected inventory
StatHandler rewrite
StatHandler.saveFile() was rewritten. The previous implementation called Files.move(ATOMIC_MOVE) unconditionally, which throws IOException: Failed to swap backup NBT file on Windows and on Linux filesystems that do not support atomic cross-directory moves. The new implementation writes to a temp file, validates it with NBT-API before touching the live file, copies the validated temp file to the backup location, then replaces the live file. When atomic move is unavailable it falls back to a standard copy-and-delete. A synchronized block prevents concurrent writes. The dirty flag is preserved across the rewrite.
Performance improvements
purgeStalePbsEntries now checks World.isChunkLoaded() before calling Block.getType(). Previously, every stored block location whose chunk was not already resident in memory triggered a synchronous chunk load, freezing the server thread for up to 20 seconds for players with large block lists. The fix skips any entry whose chunk is not already loaded and revisits it on a later call.
A Caffeine cache (purgeThrottle, 60-second TTL, 512 entries) throttles the per-player stale-entry scan to at most once per 60 seconds, preventing the scan from running on every block-place event for active players.
Block family expression system
See "BlockFamilyParser cross-family validation and /bp lockables" above for the full token reference and validation behavior.
Config and blocks auto-merge on every reload, with content preserved
reloadConfigAndTranslations() runs all merge and sync operations on every /bp reload call, not only on startup: cleanLegacyConfigKeys(), mergeMissingConfigKeys(), mergeMissingBlocksKeys(), language-file key merging, and the world-config re-scan all run on reload.
The merge guarantee for blocks.yml, config.yml, and worlds.yml is: any top-level section that is entirely missing from the file on disk is added from the bundled JAR defaults, and a console line records what was added. Any key or section that already exists is never overwritten - this means user-configured lockable lists, family expressions, and all other values are preserved across reloads and version upgrades. A future version that adds a brand-new top-level key (for example, a new boolean toggle) will see that key appended automatically; a family expression you already wrote, such as '[*-CHEST_BOATS ITEM_FRAME]', is never rewritten, reordered, or stripped, regardless of how many versions you upgrade through.
Individual entry-level merge (appending new materials to existing flat lists) was removed in 1.3.3 to prevent accidental population of user-configured lists. Only missing top-level sections are merged.
Renamed config keys with automatic migration
worlds_config_enabled is renamed to per_worlds_config. On first startup after upgrading, the plugin reads the old key, writes its value under the new name, and removes the old entry from config.yml. No manual action is required.
auto_reload_configs flag
New config key, default true. When set to false, the ConfigFileWatcher does not start and config files are not reloaded automatically; use /bp reload to apply changes manually. Useful on high-load servers or when editor tooling causes spurious filesystem events.
SQLite usercache moved into plugin folder
blockprot_usercache.sqlite is now created inside plugins/BlockProtReloaded/ rather than next to server.jar. On first startup after upgrading, any file found at the old location is moved automatically.
Legacy folder migration improved
migrateFromLegacyFolders() also calls mergeYamlUserValues() after copying files. Admin values from config.yml and all language files in the legacy folder are merged key-by-key into the new location. The destination's existing value always wins on conflict, so no configured value is ever overwritten by the migration.
Raid detection
RaidDetectionListener monitors every explosion (BlockExplodeEvent and EntityExplodeEvent) for proximity to lockable blocks. It does not cancel the explosion; it only detects and notifies.
Behavior:
- Any lockable block in the explosion's block list triggers a
WARNline in the session log with block type, world, coordinates, and the actor (player name, entity type, or "environment"). - If the block is protected and its owner is online: an action-bar alert fires immediately, followed by a chat message with full coordinates. If the owner has
blockprot.blocks.tp, a clickable[Teleport]link is appended. - If the owner is offline: the alert is queued in memory and delivered as a chat message the next time they join, with the teleport link included if they have
blockprot.blocks.tpat join time.
Controlled by raid_detection.enabled in config.yml (default true). Set to false to stop all detection and notification without removing the listener.
New translation keys: messages.raid_alert, messages.raid_coords, messages.raid_tp_label, messages.raid_pending.
Support policy change
Starting with 1.3.3, support tiers are:
| Version range | Tier |
|---|---|
| 26.x (Paper/Purpur/Pufferfish), no upper cap | Primary development target |
| 1.21.1 through 1.21.x | Active support |
| 1.20.x and below | Not supported |
The compile target is raised from paper-api:1.20.6 to paper-api:1.21.1. Servers below 1.21.1 no longer load the plugin (enforced by api-version: 1.21.1 in plugin.yml). Legacy 1.20.x users must stay on 1.3.3 or earlier.
Documentation policy change
Starting with 1.3.4, the project's README.md will be condensed to a short summary of all current features rather than documented feature-by-feature in full detail. RELEASE_NOTES.md becomes the single authoritative place where what changed between every version is recorded, including this entry. docs/READ_MEs/MODR_HANG_README.md, the short description used on Modrinth and Hangar, will continue to be updated for store listings but is not a substitute for this file.
BEACON added to lockable tile entities
BEACON is now recognized as a lockable tile entity. It can be locked via flat list (add BEACON to lockable_tile_entities) or family expression (*-MISC catches it as an ungrouped tile entity). The default blocks.yml now includes BEACON.
SIGNS enabled by default in blocks.yml
All sign variants (floor, wall, hanging, wall-hanging for all 12 wood types) are now enabled by default via the family expression [*-SIGN] in lockable_tile_entities. This works in both flat-list and modern-expression modes since family expressions are always parsed.
FLETCHING_TABLE added to WORKSTATION sub-family
FLETCHING_TABLE was present in the default flat list of lockable_blocks but was missing from the isWorkstationMaterial() check in BlockFamilyParser and the blockValidator in DefaultConfig. This meant it was silently rejected during family expression resolution and the loadBlocksFromConfig() validator. It is now a proper member of the *-WORKSTATION sub-family and the lockable_blocks BLOCKS family.
/bp lockables click-to-toggle
Left-clicking a material in /bp lockables toggles it active or inactive and saves the change immediately. Right-click copies the material name; shift-right-click copies the exclusion token (-MATERIAL_NAME). This is permanent, built-in behavior with no config option to disable it. Every toggle is logged to the session log with the player and the resulting state.
/bp lockables modern mode compliance
toggleLockable() now respects modern_family_blocks. In legacy mode, toggling adds/removes flat material names as before. In modern mode, the current config list is parsed into a material set via BlockFamilyParser.parse(), the target material is added or removed, and the set is converted back to a compact family expression via BlockFamilyParser.toFamilyExpression(). This keeps blocks.yml clean and avoids mixing flat names with expressions.
/bp lockables "Enable all" entry per category
A NETHER_STAR "Enable all" entry appears at the top of every category section (Chests, Shulkers, Furnaces, Storage, Signs, Doors, Trapdoors, Fence Gates, Workstations, Interactive, Entities). Clicking it adds the appropriate family expression (e.g. [*-CHEST], [*], [*-WORKSTATION]) to blocks.yml, enabling every material in that category at once. The change is saved to disk and logged.
/bp lockables active-first sorting
Both the category-grouped entries and the entity entries in the lockables GUI are now sorted with active (currently lockable) items first, followed by inactive items. Previously the order was inactive-first.
External plugin API: BlockProtAPI.registerIntegration()
External plugins can now register their own PluginIntegration subclasses to hook into BlockProt's protection flow:
// From onEnable() of the external plugin:
Bukkit.getScheduler().runTaskLater(this, () -> {
BlockProtAPI api = BlockProtAPI.getInstance();
if (api != null) {
api.registerIntegration(new MyIntegration());
}
}, 1L);registerIntegration() tracks BlockProt's own lifecycle internally: if BlockProt is already past the load() or enable() phase when the external integration is registered, those phases are called immediately. This means the one-delay-tick pattern above is only needed because BlockProtAPI.getInstance() is not available until BlockProt's onEnable() completes; the register call itself handles lifecycle automatically.
PluginIntegration is fully public and abstract. Subclasses must provide:
- a
namestring (used in boot logging) - a
load()method (runs duringBlockProt.onLoad()) - an
enable()method (runs duringBlockProt.onEnable())
The ConfigurationSection for the integration is available via getSection(name) (reads from integrations.yml), and the enabled toggle should be checked at the top of enable().
Events available for integration hooks: BlockAccessEvent, BlockAccessMenuEvent, BlockLockOnPlaceEvent, all in de.sean.blockprot.bukkit.events.
Fixed & Improved
Bug fixes
- README.md stale config reference: The
worldedit_paste_autolocksection in README.md still listed these as liveconfig.ymloptions. They were moved tointegrations.ymlin this release. Changed to a DEPRECATED comment block. Theblockprot.debugpermission default was also corrected fromfalsetoopto matchplugin.yml. isKeyInExpressionFormat()ignoredmodern_family_blocks: false: when the flag was set to false butblocks.ymlstill contained family expressions like- '[*-CHEST]', every GUI toggle stayed in expression format instead of converting to flat material names. The flag now determines the output format unconditionally: whenfalse, all keys are written as flat sorted material lists regardless of the file's current format.toggleLockable()andtoggleFamily()had two code paths: the expression branch parsed both formats correctly, the flat branch only handled flat input. Whenmodern_family_blockstoggled from true to false, the flat branch would not convert existing expressions: toggles silently did nothing. Both methods now use a unified path: parse the raw value (handles both formats), modify the material set, then write in the flag-determined format.blocks.ymlkey order shuffled: SnakeYAML writes keys in insertion order, so anyblocksConfig.set()call moved the modified key to the end. Repeated toggles left the file with keys in random order. AddedDefaultConfig.reorderBlocksKeys()which returns a newYamlConfigurationwith keys in the fixed order (lockable_tile_entities,lockable_shulker_boxes,lockable_blocks,lockable_doors,lockable_entities,auto_drop_to_inventory). Applied to all save paths:saveBlocksConfig(),mergeMissingBlocksKeys(), andconvertBlocksFormatIfNeeded().- Auto-format conversion on reload: new
convertBlocksFormatIfNeeded()in BlockProt.java runs during everyreloadConfigAndTranslations(). It reads blocks.yml, checks if the file's format matchesmodern_family_blocks, and if not, converts all 5 lockable keys to the correct format (expressions or flat names) while preserving every material selection. This means settingmodern_family_blocks: falseand doing/bp reloadnow correctly converts- '[*-CHEST]'to flat names like- CHEST\n- TRAPPED_CHEST\n.... - Translation incomplete message spammed console on every reload and GUI toggle:
loadFromConfigs()logged the "N incomplete key(s)" message togetLogger().info()every time translations were loaded, which happened on every file-watcher-triggered reload (i.e., after every/bp lockablestoggle). Changed to a statictranslationIncompleteLoggedflag: the message is printed once per server session. - Auto-reload console message was removed entirely: the
ConfigFileWatcherpreviously logged "Configuration change detected; reloading..." to the console but was changed to session-log-only to reduce spam. The message is restored to console (one line): the session log retains the additional detail. - Block Lock menu button shifting: every button used a shared incrementing slot counter, so any block type missing one button (for example, no Redstone on a workstation) shifted every later button one slot to the left. The same menu rendered differently for a chest versus a workstation versus a door. Fixed by giving every button a fixed slot in both
fill()andfillForEntity()- see "Fixed menu layout" above. - StatHandler NBT save crash:
Files.move(ATOMIC_MOVE)failed on cross-device paths. Rewritten to temp-write, validate, backup, replace with fallback copy. - Server freeze on stats purge:
purgeStalePbsEntriescalledBlock.getType()on unloaded chunks, triggering synchronous chunk loads. Fixed with aWorld.isChunkLoaded()guard. - EntityChangeBlockEvent AIR crash: missing early-return for AIR blocks caused a
RuntimeExceptioninBlockNBTHandler. - Shulker auto-lock NPE: wrote NBT via
block.getState()on freshly placed shulkers whoseBlockEntitywas not yet initialized. Fixed withblock.getState(true)and aTileStateguard. /bp protdeltypo: was registered asprotdell(double L); corrected toprotdel./bp protdelmissing non-tile-entity blocks: the non-MySQL fallback path only scannedchunk.getTileEntities(), missing all non-tile lockable blocks (doors, trapdoors, fence gates, anvils, cauldrons, workstations, interactive blocks). The fallback now also scans every block in loaded chunks for lockable non-tile types./bp protdelmissing double-chest and door cleanup:handler.clear()was called withouthandler.applyToOtherContainer(), leaving protection data on the other half of double chests and double doors. Added the missing call.- Update checker silent failure: produced no output when running ahead of the latest release; now logs silently instead of erroring.
- EntitySettingsInventory permission: used the raw string
"blockprot.admin"instead ofPermissions.USER_ADMINand lacked anisOp()fallback. Fixed. - CraftBukkit error path: the error message shown to plain-CraftBukkit servers is now properly translated instead of showing a raw key string.
BlockProtAPIinstantiation was too late for external integrations:BlockProtAPI.getInstance()returnednulluntil after all startup logging, the first-run guide, and the banner were printed.new BlockProtAPI(this)is now created right after integrations are enabled, making the API available to external plugins earlier in the startup sequence. Additionally,registerIntegration()now tracks BlockProt's internal lifecycle (integrationsLoaded,integrationsEnabled) and callsload()/enable()on newly registered integrations immediately if BlockProt has already passed that phase; external integrations registered viaBukkit.getScheduler().runTaskLater(..., 1L)no longer skip lifecycle hooks.
Inherited bug fixes
| Issue | Title | Fixed in |
|---|---|---|
| #302 | 1.21.1 not supported properly | Upstream |
| #304 | Unknown warning/error occurred | Upstream |
| #305 | Taking map from Search Players slot | Upstream |
| #319 | Emptying large chests incorrectly on hoppers | Upstream |
| #323 | 1.21.3 not supported properly | Upstream |
| #326 | Redstone protection bugged with trapdoors | Upstream |
| #327 | Paper NoClassDefFoundError Container | Upstream |
| #328 | Colored shulker boxes reset when broken | Upstream |
| #329 | AIR block error on entity change | Upstream |
| #330 | Pale oak door/gate cannot be locked | Upstream |
| #332 | Unable to use BlockProt on 1.21.7 | Upstream |
| #338 | Support for Minecraft 1.21.9 | Upstream |
| #339 | Shulker box vanishes when broken | Upstream |
| #165 | Random inventory close on interaction | Upstream |
| #266 | public_is_friend_by_default setting |
Upstream |
| #294 | Geyser players missing from friend search | Upstream |
| #185 | Hopper protection below chest not working | Upstream |
| #214 | Making doors public only half the door | Upstream |
| #224 | Locked shulker boxes break without dropping | Upstream |
| #227 | Hopper and piston protection swapped | Upstream |
| #235 | Lectern protection issues | Upstream |
| #240 | Hopper protecting not working | Upstream |
| #250 | Can get Player Head from menu | Upstream |
| #256 | Can't copy/paste settings between blocks | Upstream |
| #263 | Can take Telescope from Menu | Upstream |
| #272 | Can't move items as admin/op | Upstream |
| #276 | Items can still be placed into chests | Upstream |
| #279 | Items being deleted on interact | Upstream |
| #280 | Shulker box items deleted after breaking | Upstream |
| #281 | Dropper not working after unlock | Upstream |
| #287 | Double chest not handled on inventory click | Upstream |
| #289 | Chest locked but friends can open | Upstream |
| #296 | Item lost on interact | Upstream |
| #297 | Item drops can't be picked by hopper | Upstream |
| #321 | Wither explosions breaking protected blocks | Upstream |
Fork-specific fixes
| Issue | Title | Fix |
|---|---|---|
| #268 | Friend list copy/paste should replace instead of appending | Paste replaces friend list |
| #306 | Plugin causes server lag (hopper checks) | ProtectedBlockCache with O(1) early-exit |
| #324 | Option to allow all protected blocks to be broken | allow_break_protected_blocks config key |
| (not tracked upstream) | Players can access second row of large chest | Inventory double-chest gating |
| #349 | Warn spam in console (NBT swap error) | StatHandler rewrite with temp-write-validate-replace |
| #238 | Piston Protection Shulker Box | Shulker box check added to piston retract logic |
Dependency updates
| Dependency | Old | New | Reason |
|---|---|---|---|
paper-api (compile) |
1.20.6-R0.1-SNAPSHOT |
1.21.1-R0.1-SNAPSHOT |
Support baseline raise |
FoliaLib |
0.4.3 |
0.5.1 |
Bug fixes, latest stable |
item-nbt-api |
2.15.6 |
2.15.7 |
26.1.x and 1.21.11 support |
MockBukkit (test) |
v1.20:3.93.2 |
v1.21:4.8.0 |
Aligns with new compile baseline |
Known Issues
- MockBukkit unresolvable:
MockBukkit-v1.21:4.8.0returns HTTP 401 from jitpack.io. Build with-x testto skip tests. - Gradle
shadowJarfalse up-to-date: on some local setups, running:blockprot-spigot:shadowJarafter only source changes (noclean) can reportBUILD SUCCESSFULwhile reusing a stale or absent output if Gradle's up-to-date check does not detect the change. If the jar is missing or unexpectedly small after a build, rerun with--rerun-tasks, or rungradlew clean :blockprot-spigot:shadowJarto force a full rebuild.
Complete Issue Reference (>= #250)
Every non-trivial issue from the upstream repository sorted by category. Issues closed upstream are fixed in this fork by inheritance. Issues open upstream but resolved in this fork are noted as fork-specific fixes.
Bugs fixed upstream (inherited)
These were closed in the upstream repository and are fixed in this fork:
| Issue | Title |
|---|---|
| #339 | Shulker box vanishes when broken |
| #338 | Support for 1.21.9 |
| #337 | Bug (untitled) |
| #336 | Bug (untitled) |
| #335 | Bug (untitled) |
| #332 | Unable to use on 1.21.7 server |
| #330 | Pale oak door/gate cannot be locked |
| #329 | Air block error on entity change |
| #328 | Colored shulker boxes reset when broken |
| #327 | Paper NoClassDefFoundError Container |
| #326 | Redstone protection bugged with trapdoors |
| #323 | 1.21.3 not supported properly |
| #319 | Emptying large chests incorrectly on hoppers |
| #305 | Taking map from Search Players slot |
| #304 | Unknown warning or error occurred |
| #302 | 1.21.1 not supported properly |
| #297 | Item drops can't be picked by hopper |
| #296 | Item lost on interact |
| #293 | Problem with chests regarding hoppers |
| #290 | Bug (untitled) |
| #289 | Chest locked but friends can open |
| #281 | Dropper not working after unlock |
| #280 | Shulker box items deleted after breaking |
| #279 | Items being deleted on interact |
| #276 | Items can still be placed into chests |
| #275 | Hoppers can take items from protected chests |
| #274 | Players can access second row of large chest |
| #272 | Can't move items as admin/op |
| #263 | Can take Telescope from Menu |
| #256 | Can't copy/paste settings between blocks |
| #255 | Can't remove everyone from settings |
| #250 | Can get Player Head from menu |
| #240 | Hopper protecting not working |
| #236 | Copy Paste not working properly |
| #235 | Lectern protection issues |
| #232 | Sign not in lockable_blocks |
| #230 | Can't unlock wall sign |
| #227 | Hopper and piston protection swapped |
| #226 | Chest unlocked but shown as locked |
| #224 | Locked shulker boxes break without dropping |
| #221 | Chest locked but friends can open |
| #215 | Chests cannot be placed on public |
| #214 | Making doors public only half the door |
| #185 | Hopper protection below chest not working |
Bugs open upstream, fixed in fork
| Issue | Title | Fork Fix |
|---|---|---|
| #306 | Plugin causes server lag | ProtectedBlockCache with Caffeine O(1) early-exit |
| #324 | Allow breaking protected blocks | allow_break_protected_blocks config key |
| #349 | Warn spam in console (NBT swap error) | StatHandler rewrite with temp-write-validate-replace |
| #344 | Console spam (NbtApiException) | Same NBT swap warning as #349, covered by the same StatHandler rewrite |
Features open upstream, implemented in fork
| Issue | Title | Fork Implementation |
|---|---|---|
| #298 | Integration support for ClaimChunk | ClaimChunkIntegration, see "ClaimChunk integration" above |
| #295 | Lock trapdoors and iron doors | Trapdoors are members of the TRAPDOOR sub-family under lockable_blocks; all *_DOOR materials, including IRON_DOOR, are members of the DOORS family under lockable_doors |
| #318 | Ability to configure lockable blocks per world | per_worlds_config in config.yml plus worlds.yml, see "Per-world lockable GUI" above |
| #303 | Respect spawn protection radius | respect_spawn_protection in config.yml (default true); non-op, non-admin players cannot lock a block inside the server's spawn-protection radius |
| #282 | MySQL Support | HybridDatabase, a Hikari-pooled MySQL/MariaDB index configured in mysql/mysql.yml. NBT stays the source of truth for ownership and friends; the database mirrors block locations and global trust lists for auditing and search on large servers |
| #334 | Colored prompt messages | Fully supported via both MiniMessage and legacy color codes in Translator |
| #192 | Support Inspect contents block name i18n | Inspect contents inventory titles are now localized |
| #346 | New configuration option | Added clear_protection_on_shulker_break config |
| #322 | Toggle for simplified hopper logic | Added simplified_hopper_logic config |
| #269 | Shows the owner of the box | Implemented via Action Bar on interact |
| #267 | Expire locks after owner offline for x time | Implemented via world expiry subsystem |
| #228 | Separate Redstone in config | Implemented via redstone_disallowed_by_default |
| #225 | Stats more Features | Covered by the StatisticListInventory GUI |
| #183 | Support Residence | ResidenceIntegration added |
| #154 | Support for GriefPrevention | GriefPreventionIntegration added |
Reporting an Issue
If you reached this point in the document, thank you for reading. When reporting a bug, please include the following information so the issue can be diagnosed and fixed faster:
| Field | What to include |
|---|---|
| Discord (optional) | Your Discord username so I can follow up directly |
| Error description | Exactly what the error says and step-by-step how to reproduce it |
| Multimedia | Screenshots or videos showing the issue |
| Server info | Server version, software (Paper / Purpur / Spigot), and a list of other plugins installed |
| Error log | The full error log (use a paste site like https://pastes.dev if it is long) |
