Skip to content

ImperaZim/LibWorld

Repository files navigation

LibWorld

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

Requirements

  • PocketMine-MP API 5.0.0+
  • PHP 8.2+
  • LibCommons 1.0+
  • LibCommand 2.0+
  • LibPacket, required transitively by LibCommand

Main Features

  • 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 EditSession API.
  • Runtime handoff between standalone LibWorld and EasyLibrary.
  • Administrative /libworld command with /lw alias.

Installation

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:
  - LibWorld

Plugins supporting either standalone or EasyLibrary can use:

softdepend:
  - LibWorld
  - EasyLibrary

Before using an optional integration:

use imperazim\world\WorldAPI;

if (!class_exists(WorldAPI::class) || !WorldAPI::isAvailable()) {
    return;
}

World Management

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.

Backups

$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/

Templates and Instances

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.

Regions

Cuboid

use imperazim\world\region\CuboidRegion;

$region = CuboidRegion::fromPositions(
    id: 'spawn',
    first: $positionA,
    second: $positionB,
    priority: 10,
    flags: ['build' => false]
);

Other Shapes

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.

Region Events

use imperazim\world\event\RegionEnterEvent;
use imperazim\world\event\RegionLeaveEvent;

public function onRegionEnter(RegionEnterEvent $event): void {
    $region = $event->getRegion();
}

Selections

$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.

Low-Level Editing

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.

EditSession

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);

Replace

use imperazim\world\mask\BlockTypeMask;

$session->replace(
    region: $region,
    mask: new BlockTypeMask($dirt, $grass),
    replacement: $stone
);

Patterns

Weighted

use imperazim\world\pattern\WeightedPattern;

$pattern = new WeightedPattern([
    ['block' => $stone, 'weight' => 70],
    ['block' => $cobblestone, 'weight' => 30],
]);

$session->set($region, $pattern);

Checker

use imperazim\world\pattern\CheckerPattern;
use imperazim\world\pattern\SingleBlockPattern;

$pattern = new CheckerPattern(
    new SingleBlockPattern($white),
    new SingleBlockPattern($black),
    scale: 2
);

Available pattern contracts:

  • SingleBlockPattern
  • WeightedPattern
  • CheckerPattern
  • ConditionalPattern
  • custom implementations of Pattern

Masks

Available masks:

  • AnyMask
  • BlockTypeMask
  • BlockStateMask
  • NotMask
  • AllOfMask
  • AnyOfMask
  • CuboidBoundaryMask
  • custom implementations of Mask

Masks and patterns are independent and composable.

Clipboard

$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);

History

$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.

Schematics

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.

Brushes

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.

Portals

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.

Commands

/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

Lifecycle and Distribution

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.

Development

composer test
composer analyse

The test suite covers geometry, selections, operations, patterns, masks, history, clipboard, world filesystem operations, templates, instances, and standalone/package-backed runtime behavior.

Changelog

See changelogs/1.0.md.

License

LibWorld is licensed under the MIT License.

EasyLibrary internal package asset

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.

About

World management and world utility APIs for PocketMine-MP plugins.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages