BuildSystem 4.0.0 is a major release. World statuses and navigator categories are no longer hardcoded, worlds can be downloaded as single-player saves, physics can be controlled per behavior, and the developer API has been reworked. Existing data, config.yml and messages.yml are migrated automatically on first start.
Requirements
- Java 25. Older runtimes will not start the plugin.
- Minecraft 26.1 or newer.
Upgrading from 3.x? Read the Migration Guide first, especially if you have plugins built against buildsystem-api.
Highlights
- Custom world statuses — Statuses are no longer a fixed list. Create, restyle, reorder and delete them in-game (
/setup→ World Statuses) or instatuses.yml. Each status has its own name, colour, icon, ordering, a building-allowed flag and an optional auto-progress target. The six built-ins are seeded on first start and can be changed or removed (never the last one). Assign withbuildsystem.setstatus.<id>. - Navigator categories — The fixed public/archive/private tabs become categories you define. A category groups worlds by visibility and a set of statuses, with its own name, colour and icon. A world appears in every category that matches it. Create worlds in a category with
buildsystem.create.category.<id>. - Navigator layout editor —
/setup→ Navigator Layout arranges where each category and the settings button sit in the navigator. Pick a category up and drop it in a slot, drop it outside to remove it, drop it on the delete target to delete it, right-click to edit, or create a new one from the same screen. The status picker for/worlds setStatususes the same editor. - Per-category commands — Every category gets a
/worlds <category>shortcut, replacing the hardcoded/worlds archive,/worlds privateand/worlds public. The three built-in categories still answer to those names.buildsystem.navigator.category.<id>(granted by default) controls both the shortcut and whether the category is shown. - World downloads —
/worlds download <world>(buildsystem.download) packs a world into a single-player save and sends a clickable link that expires with the archive. Served by a built-in HTTP server (storage: local) or as pre-signed S3 links (storage: s3). Off by default; enable underworld.downloadinconfig.yml. Player data never leaves the server. - Per-world physics exceptions — While a world's physics are off, individual behaviors (block updates, connections, falling blocks, fluid flow, leaf decay, growth, spreading, block forming, block fading) can be re-allowed. Right-click the physics toggle in
/worlds edit. Defaults live underworld.defaults.physics-exceptions. - In-game
/setup— A redesigned hub for default world-type icons, world statuses and the navigator layout, with a colour picker, a searchable item picker, skull-texture support and reset-to-defaults.
Added
- Custom icons for worlds, folders and categories: a material or a player-head texture (
%viewer%shows the viewer's own head). In/worlds edit, click the world item to pick a material or right-click a head icon to set its texture (buildsystem.edit.icon). - Pinned worlds (#319) sort above everything else in the navigator regardless of the active sort, with a configurable prefix. Toggle in the edit menu;
buildsystem.edit.pin. - Custom seed on creation. Shift-click a world type in the create menu to enter a seed. Numbers are used as-is, other text is hashed, blank falls back to random.
/worlds saveTemplate <world> [name](#449) copies a world intotemplates/at runtime.buildsystem.savetemplate.- Configurable void-world block.
world.void-block.enabledandworld.void-block.material(defaultGOLD_BLOCK) control the block placed at the spawn of new void worlds. It is placed only when the world is first generated and never overwrites an existing block. - EssentialsX warps into unloaded worlds (#528). A warp pointing into a world BuildSystem had unloaded failed with "warp not found". BuildSystem now loads the target world before EssentialsX resolves the warp. Nothing to configure.
- Per-option
/settingspermissions:buildsystem.setting.<option>(#250). - Per-template and per-generator create permissions:
buildsystem.create.template.<name>andbuildsystem.create.generator.<name>(#322, #323). Granted by default; deny a node to restrict it. settings.world-permission-whitelistrestricts what/worlds setPermissionmay assign.- World-editor toggles show their current value in the lore.
- Import, unimport and deletion of a world are now logged to the console, like loading and unloading already were.
Changed
config.ymlandmessages.ymlare versioned and migrated on load. Missing keys are added; existing values are kept.- External storage credentials move from
world.backup.storage.<type>to a rootstoragesection, andworld.backup.storage.typebecomesworld.backup.storage. Backups and downloads share the credentials and each name their backend. Where files land moves with the feature (world.backup.path). Existing configs migrate automatically. - World limits:
world.max-amount.*is nowworld.limits.*and the value is how many worlds one player may create of that visibility, not a server-wide count.buildsystem.create.<visibility>.<amount>overrides it,buildsystem.adminignores it, and limits are counted per visibility. Values carry over. world.disabled-physicsis replaced byworld.defaults.physics-exceptions(migrated automatically).- Two config flags are removed and ignored if present:
settings.per-option-permissions(superseded by the always-onbuildsystem.setting.<option>nodes) andsettings.restrict-template-access(superseded bybuildsystem.create.template.<name>). - Backups and downloads wait for Paper's chunk writer to finish before archiving, so an archive no longer contains half-written region files.
- The shaded jar dropped from 32 MB to 4 MB.
Fixed
- Paper 26.1:
IncompatibleClassChangeErroronorg.bukkit.GameRule(#457), and support for the 26.1 world storage layout where every world is a dimension of the main level. - World renaming could destroy a world. A partly failed directory copy was logged, not reported, so the rename deleted the original and reported success. The copy now fails loudly and the original is kept. The same fix applies to template saving and creating a world from a template.
- Backups: the first backup of a world failed because its directory was never created;
/buildsystem reloadleft worlds bound to a closed S3/SFTP client; two backups of the same world no longer overlap; the default world's backup no longer includes every other world (and restoring it in place is refused, since that would delete every world on the server);AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYandBUILDSYSTEM_SFTP_PASSWORDnow actually work as credential sources. - World loading: a world with an unreadable difficulty no longer vanishes on load; settings missing from disk load with the defaults a new world gets instead of disabled or at epoch zero.
- Bulk import: failed worlds are reported instead of silently dropped, and a world that throws mid-import no longer blocks every later
importAlluntil restart./worlds import -tand/worlds importAll -gwith an unknown value now say so and stop instead of importing with the default. - Reset to defaults for statuses or categories now cascades like deleting one does, so folders in a discarded category no longer vanish until restart.
- Disable interactions now covers every interactable block (crafter, trial spawner, vault, decorated pot, copper doors, pale oak doors, creaking heart and so on).
- Clicks in the player's own inventory no longer trigger the menu button in the same slot.
/nighton an unknown world reported the/dayerror.- Scoreboard placeholders were resolved off the main thread and once per line; now once per refresh, on the main thread.
settings.date-formatusesjava.time; an unparsable pattern logs a warning and falls back todd/MM/yyyy.- Missing message keys used for lore are reported in the console.
- Failed world-directory deletions are reported.
- Main-thread safety for the NoClip block check, world-rename registry mutation and post-async skull updates; duplicate listener registration on reload; placeholder substitution is literal, not regex.
- Path-traversal guards on template and world directories. A world name resolving outside the world container (for example
../../plugins/x) is rejected at creation and deletion, as are./..and Windows device names (#481).
Performance
- Navigator armor stands are cached instead of scanned every tick.
- The NoClip check no longer allocates a
Locationper tick. - Less allocation in scoreboard rendering, colour processing and the template menu.
- World, folder and player file I/O run on one bounded background pool.
API
Recompile against buildsystem-api 4.0.0. The full list is in the Migration Guide; the short version:
- Get the instance through
BuildSystemProvider.get(); it is registered with Bukkit'sServicesManager. BuildWorldStatusandNavigatorCategoryare interfaces resolved throughWorldStatusRegistryandNavigatorCategoryRegistry. Compare bygetId()/equals(), never with==orswitch.- World creation and import are fluent:
WorldService.newWorld(name)/importWorld(name)ending inbuild().WorldService.importWorlds()imports in bulk. Type<T>and the per-setting getters/setters are gone;WorldDatais accessed through typed keys:data.get(WorldDataKey.PERMISSION),data.set(WorldDataKey.BLOCK_BREAKING, true).BuildWorld.getWorld()returnsOptional<World>.- Private worlds are a
Visibility(EVERYONE/ADDED_PLAYERS) onWorldData;isPrivateWorld()/setPrivateWorld()are gone. BuildWorldManipulationEventmoved tode.eintosti.buildsystem.api.event.world, joined byBuildWorldCreateEvent,BuildWorldPostCreateEvent,BuildWorldDeleteEvent,BuildWorldPostDeleteEvent,BuildWorldUnimportEvent,BuildWorldRenameEventandBuildWorldStatusChangeEvent, plus backup and folder events.- XSeries is gone from the public API. Icons are Bukkit
Material; head textures are plain strings viaDisplayable.getIconSkullTexture(). - Removed:
BuildWorld.setLoaded(boolean),BuildWorld.asProfileable(),WorldData.getConfigFormat(),WorldData.getAllData(), and the publicBuildSystemProvider.register()/unregister().
Migration
Everything below happens on the first 4.0.0 start with no manual steps:
- Stored status names (
IN_PROGRESS) become ids (in_progress); unknown values fall back tonot_started. - The per-world
privateflag becomes aVisibility. - Folder categories (
PUBLIC,ARCHIVE,PRIVATE) become the matching built-in category ids. - If
statuses.ymldoes not exist yet, built-in statuses are seeded from your existingstatus_<id>keys inmessages.yml. After that,statuses.ymlis authoritative and thestatus_*message keys are unused.
Full Changelog: 3.0.2...4.0.0