LibWorld is a world-management, region, and block-editing engine for PocketMine-MP 5. It combines APIs commonly associated with MultiWorld and WorldEdit-style plugins while keeping the engine reusable by third-party plugins.
It is distributed both as a standalone plugin and as an EasyLibrary package-backed provider under the same namespace:
imperazim\world- PocketMine-MP API 5.0.0+
- PHP 8.2+
- LibCommons 1.0+
- LibCommand 2.0+
- LibPacket, required transitively by LibCommand
- Safe world creation, loading, unloading, cloning, renaming, deletion, import, export, backup, and restoration.
- Built-in void, normal, flat, Nether, and externally registered generators.
- Reusable world templates and disposable per-match instances.
- Cuboid, cylinder, sphere, ellipsoid, polygon, pyramid, and composite regions.
- Region priorities, flags, plugin ownership, and enter/leave events.
- Per-player selections.
- Tick-budgeted block editing with adaptive TPS throttling.
- Single, weighted, checker, and conditional patterns.
- Type, state, negated, union, intersection, and boundary masks.
- Clipboard capture, rotation, copy, cut, and paste.
- JSON schematic persistence.
- Sphere and cylinder brush contracts.
- Region-driven portals with cooldowns.
- Per-actor undo and redo history.
- Fluent
EditSessionAPI. - Runtime handoff between standalone LibWorld and EasyLibrary.
- Administrative
/libworldcommand with/lwalias.
Place LibWorld.phar in the server plugins/ directory.
The standalone distribution requires LibCommons, LibCommand and the
LibPacket dependency required transitively by LibCommand. EasyLibrary 3.x
uses package-backed libcommons and libcommand providers before loading the
libworld package.
Plugins that require the standalone distribution can declare:
depend:
- LibWorldPlugins supporting either standalone or EasyLibrary can use:
softdepend:
- LibWorld
- EasyLibraryBefore using an optional integration:
use imperazim\world\WorldAPI;
if (!class_exists(WorldAPI::class) || !WorldAPI::isAvailable()) {
return;
}use imperazim\world\WorldAPI;
$world = WorldAPI::worlds()->create(
name: 'arena',
generator: 'void',
seed: 1234,
backgroundGeneration: false
);
WorldAPI::worlds()->unload('arena');
WorldAPI::worlds()->load('arena');
WorldAPI::worlds()->duplicate('arena', 'arena-copy');
WorldAPI::worlds()->rename('arena-copy', 'arena-backup');
WorldAPI::worlds()->delete('arena-backup');Import and export world directories:
WorldAPI::worlds()->import('/server/imports/arena', 'arena');
WorldAPI::worlds()->export('arena', '/server/exports/arena');Manage runtime world properties:
WorldAPI::worlds()->setSpawn('arena', $spawn);
WorldAPI::worlds()->setTime('arena', 6000, running: false);
WorldAPI::worlds()->setDifficulty('arena', World::DIFFICULTY_HARD);
WorldAPI::worlds()->setAutoSave('arena', true);
WorldAPI::worlds()->setDisplayName('arena', 'Main Arena');World names are validated before filesystem operations. Directory copy and delete operations remain inside PocketMine's worlds and LibWorld backup directories.
$backupId = WorldAPI::worlds()->backup('arena');
WorldAPI::worlds()->restore(
backupId: $backupId,
worldName: 'arena-restored',
overwrite: false
);LibWorld accepts void in its API and commands, but registers the built-in
generator globally as libworld_void. This prevents collisions with generator
plugins that publish their own generic void alias. Use libworld_void when
you specifically need LibWorld's implementation.
Backups are stored under:
backups/libworld/
Register a reusable source world:
WorldAPI::templates()->register(
id: 'skywars',
sourceWorld: 'skywars-template',
owner: $this,
metadata: ['mode' => 'solo']
);Create an isolated instance:
$instance = WorldAPI::instances()->create(
templateId: 'skywars',
id: 'match-42',
owner: $this,
metadata: ['players' => 8],
deleteOnClose: true
);
$world = $instance->getWorld();
WorldAPI::instances()->close('match-42', $this);Temporary instances are unloaded and deleted on close. Instances are also cleaned when their owner plugin is disabled or LibWorld is shut down.
use imperazim\world\region\CuboidRegion;
$region = CuboidRegion::fromPositions(
id: 'spawn',
first: $positionA,
second: $positionB,
priority: 10,
flags: ['build' => false]
);use imperazim\world\region\CylinderRegion;
use imperazim\world\region\CompositeRegion;
use imperazim\world\region\CompositeMode;
use imperazim\world\region\EllipsoidRegion;
use imperazim\world\region\PolygonRegion;
use imperazim\world\region\PyramidRegion;
use imperazim\world\region\SphereRegion;Composite regions support UNION, INTERSECTION, and DIFFERENCE.
Every region provides:
$region->contains($position);
$region->getMinimum();
$region->getMaximum();
$region->getCenter();
$region->getVolume();
$region->positions();
$region->getFlag('build', true);Register runtime regions with plugin ownership:
WorldAPI::regions()->register($region, owner: $this);
$regions = WorldAPI::regions()->at($player->getPosition());
$highest = WorldAPI::regions()->highestAt($player->getPosition());Higher-priority overlapping regions are returned first.
use imperazim\world\event\RegionEnterEvent;
use imperazim\world\event\RegionLeaveEvent;
public function onRegionEnter(RegionEnterEvent $event): void {
$region = $event->getRegion();
}$selection = WorldAPI::selections()->get($player);
$selection->setFirst($positionA);
$selection->setSecond($positionB);
$region = $selection->toCuboid('player-selection');Changing to a position in another world resets the previous selection.
use imperazim\world\pattern\SingleBlockPattern;
$operation = WorldAPI::operations()
->edit($world, $region, owner: $this)
->pattern(new SingleBlockPattern($block))
->history($player->getUniqueId()->toString())
->blocksPerTick(2000)
->submit();Monitor an operation:
$operation
->onProgress(function($operation): void {
$progress = $operation->getProgress();
})
->onComplete(function($operation): void {
$changed = $operation->getChangedBlocks();
})
->onFailure(function($operation, \Throwable $error): void {
// Report the failure.
});The operation manager adjusts its global block budget according to average TPS. PocketMine world mutations remain on the main thread.
The higher-level API is designed for plugin code:
$session = WorldAPI::edit(
world: $world,
owner: $this,
historyActor: $player->getUniqueId()->toString()
);
$session->set($region, $stone);
$session->walls($cuboid, $glass);
$session->hollow($cuboid, $stone);
$session->overlay($region, $grass);
$session->sphere($center, 8, $stone);
$session->cylinder($center, 6, 12, $stone);use imperazim\world\mask\BlockTypeMask;
$session->replace(
region: $region,
mask: new BlockTypeMask($dirt, $grass),
replacement: $stone
);use imperazim\world\pattern\WeightedPattern;
$pattern = new WeightedPattern([
['block' => $stone, 'weight' => 70],
['block' => $cobblestone, 'weight' => 30],
]);
$session->set($region, $pattern);use imperazim\world\pattern\CheckerPattern;
use imperazim\world\pattern\SingleBlockPattern;
$pattern = new CheckerPattern(
new SingleBlockPattern($white),
new SingleBlockPattern($black),
scale: 2
);Available pattern contracts:
SingleBlockPatternWeightedPatternCheckerPatternConditionalPattern- custom implementations of
Pattern
Available masks:
AnyMaskBlockTypeMaskBlockStateMaskNotMaskAllOfMaskAnyOfMaskCuboidBoundaryMask- custom implementations of
Mask
Masks and patterns are independent and composable.
$clipboard = WorldAPI::clipboards()->capture(
actor: $player,
world: $world,
region: $region
);
$clipboard->paste(
operations: WorldAPI::operations(),
world: $world,
target: $target,
owner: $this,
rotation: 1,
historyActor: $player->getUniqueId()->toString()
);Rotation uses quarter turns:
0 = 0 degrees
1 = 90 degrees
2 = 180 degrees
3 = 270 degrees
With an edit session:
$session->copy('builder', $region);
$session->cut('builder', $region);
$session->paste('builder', $target, rotation: 1);
$session->stack('builder', $target, new Vector3(0, 10, 0), count: 5);
$session->move($region, $target);$actor = $player->getUniqueId()->toString();
$changeSet = WorldAPI::history()->undo($actor);
$changeSet = WorldAPI::history()->redo($actor);History stores block state IDs. Tile data such as chest inventories and sign text is outside the current block-history contract and should be handled through LibSerializer when required.
WorldAPI::schematics()->save('castle', $clipboard);
$clipboard = WorldAPI::schematics()->load('castle');
WorldAPI::schematics()->delete('castle');LibWorld's native JSON format stores dimensions, relative coordinates, and PocketMine block state IDs. Native schematics are intended for the same PocketMine runtime line; use LibSerializer when stable cross-version block serialization or tile data is required.
use imperazim\world\brush\SphereBrush;
$session->brush(
new SphereBrush(radius: 6),
target: $position,
value: $stone
);SphereBrush, CylinderBrush, and custom Brush implementations use regular
regions and edit operations.
WorldAPI::portals()->register(
id: 'lobby-to-games',
source: $portalRegion,
targetWorld: 'games',
target: new Vector3(0, 70, 0),
owner: $this,
cooldownSeconds: 1.0
);Portals load their target world when necessary and are removed automatically
when their owner plugin is disabled. PortalUseEvent is cancellable.
/libworld and /lw are available from standalone LibWorld and from the
EasyLibrary embedded host. LibWorldCommandRegistry keeps exactly one command
instance registered and transfers ownership together with the runtime.
The root command extends imperazim\command\Command. Every group and action is
a concrete imperazim\command\SubCommand class in its own file. Nested
subcommands, typed arguments, constraints, permissions, autocomplete, and
Bedrock command UI metadata are provided by LibCommand.
/lw world list
/lw world info <world>
/lw world create <world> [generator] [seed]
/lw world load <world>
/lw world unload <world> [force] [save]
/lw world clone <source> <destination> [load]
/lw world rename <source> <destination> [load]
/lw world delete <world> [force]
/lw world backups
/lw world backup <world> [id]
/lw world restore <backup> <world> [overwrite] [load]
/lw selection pos1|pos2|info|clear
/lw edit set <block>
/lw edit replace <from> <to>
/lw edit walls <block>
/lw edit hollow <shell> [inside]
/lw edit sphere <radius> <block>
/lw edit cylinder <radius> <height> <block>
/lw clipboard copy
/lw clipboard paste [0|90|180|270]
/lw clipboard info|clear
/lw schematic list
/lw schematic save <name> [overwrite]
/lw schematic load <name>
/lw schematic delete <name>
/lw history undo|redo|status|clear
/lw operation list
/lw operation status <id>
/lw operation cancel <id>
/lw operation purge
Group aliases include w, sel, e, clip, schem, hist, and ops.
Action aliases include duplicate, cyl, remove, reset, info, and
clean. Use /lw help [group] for the in-game command guide.
The implementation is organized by responsibility:
command/
LibWorldCommand.php
LibWorldCommandRegistry.php
LibWorldPermissions.php
subcommand/
LibWorldSubCommand.php
WorldSubCommand.php
world/CreateWorldSubCommand.php
edit/SetEditSubCommand.php
clipboard/PasteClipboardSubCommand.php
...
Permissions:
libworld.command
libworld.command.world
libworld.command.selection
libworld.command.edit
libworld.command.clipboard
libworld.command.schematic
libworld.command.history
libworld.command.operation
The standalone and package-backed distributions share the same public API.
During mixed migration, a standalone LibWorld plugin wins over an installed
EasyLibrary package. When the standalone plugin is removed and the package proxy
is active on the next restart, the package-backed provider owns the runtime.
WorldManager::init($host);
WorldManager::shutdown($host);Shutdown is protected by exact host identity.
composer test
composer analyseThe test suite covers geometry, selections, operations, patterns, masks, history, clipboard, world filesystem operations, templates, instances, and standalone/package-backed runtime behavior.
See changelogs/1.0.md.
LibWorld is licensed under the MIT License.
LibWorld can also be published as an EasyLibrary 3.x internal package asset. The release workflow now builds LibWorld-1.0.0.easylib.zip, package.yml and checksums.txt in addition to the standalone PHAR. The internal package declares libcommons and libcommand as required dependencies. Servers should keep the standalone PHAR during migration; EasyLibrary's standalone shadow guard prevents the internal package from autoloading while the standalone plugin is active.