Skip to content

ImperaZim/LibWindow

Repository files navigation

LibWindow

Version 2.0.0 PocketMine-MP 5.0.0+ PHP 8.2+ License

LibWindow

Description

Inventory-based window menu library for PocketMine-MP that creates fully client-side fake container windows with fluent configuration, click/close handling, layout helpers, pagination, and a NetworkStackLatency-based retry mechanism for reliable delivery.

Features

  • 8 Container Types: Chest (27), Double Chest (54), Hopper (5), Barrel (27), Furnace (3), Blast Furnace (3), Smoker (3), Brewing Stand (5)
  • Two Authoring Styles: Fluent/closure-based via DynamicWindow and class-based via Window subclasses
  • Read-Only Mode: Cancels all item movement while still firing click handlers
  • Transaction Control: Cancel, allow, or schedule post-transaction callbacks through WindowTransaction
  • Fill Helpers: fill(), fillSlots(), fillAll(), fillBorder(), clearAll()
  • Live Refresh: Re-sync inventory contents while the window is already open
  • Container Properties: Control furnace progress bars, brewing stand animations via setProperty()
  • Inventory Binding: Bidirectional sync between the window inventory and any external inventory
  • Layout System: Borders, rows, navigation bars, centered rows, checkerboard patterns, and named slot groups
  • Pagination: Automatic page splitting with configurable navigation buttons
  • Confirmation Preset: Ready-made yes/no dialog with customizable items and callbacks
  • Client-Side Graphics: Fake blocks, tile entities, paired chests, and invisible minecart entities
  • NSL Retry Mechanism: Automatic retry (up to 20 attempts) using NetworkStackLatencyPacket as ACK
  • Per-Player Sessions: Tracks window state, graphic lifecycle, and network timing per player
  • Custom Type Registry: Register your own WindowType implementations
  • Platform-Aware Networking: Per-device timestamp encoding (handles PlayStation differences)

How to Use

Requires PocketMine-MP 5.0.0+ and PHP 8.2+.

Place the LibWindow folder in your server's plugins/ directory. It loads at STARTUP and initializes automatically.

Add to your plugin.yml:

depend:
  - LibWindow

LibWindow initializes itself on enable. If you need manual initialization from another plugin:

use imperazim\window\WindowManager;

WindowManager::init($this);

Components

imperazim\window

Highlights

  • Added Window abstract class -- base for class-based window menus. Extend it and override structure(), onClick(), and onClose(). The window opens automatically on construction. Provides fluent proxy methods (setName(), readonly(), fill(), fillSlots(), fillAll(), fillBorder(), clearAll(), bindInventory(), refresh(), setProperty()) and access to the underlying DynamicWindow, Inventory, Player, and custom data.
  • Added DynamicWindow class -- fluent/closure-based API for inline window creation. Static factories: chest(), doubleChest(), hopper(), barrel(), furnace(), blastFurnace(), smoker(), brewingStand(), and the generic create(). Supports setName(), readonly(), onClick(), onClose(), fill(), fillSlots(), fillAll(), fillBorder(), clearAll(), bindInventory(), refresh(), setProperty(), and sendTo().
  • Added WindowManager class -- central manager that initializes the system, registers event listeners, manages the built-in type registry, and routes packet/inventory events. Call WindowManager::init($plugin) once. Access the type registry via getTypeRegistry() and player sessions via getSession($player).
  • Added ChestWindow abstract class -- Window pre-configured for a single chest (27 slots).
  • Added DoubleChestWindow abstract class -- Window pre-configured for a double chest (54 slots).
  • Added HopperWindow abstract class -- Window pre-configured for a hopper (5 slots).
  • Added BarrelWindow abstract class -- Window pre-configured for a barrel (27 slots).
  • Added FurnaceWindow abstract class -- Window pre-configured for a furnace (3 slots).
  • Added BlastFurnaceWindow abstract class -- Window pre-configured for a blast furnace (3 slots).
  • Added SmokerWindow abstract class -- Window pre-configured for a smoker (3 slots).
  • Added BrewingStandWindow abstract class -- Window pre-configured for a brewing stand (5 slots).

Usage

Fluent/closure-based approach with DynamicWindow:

use imperazim\window\DynamicWindow;
use imperazim\window\transaction\WindowTransaction;
use pocketmine\item\VanillaItems;
use pocketmine\player\Player;

DynamicWindow::chest("Shop")
    ->fill(0, VanillaItems::DIAMOND_SWORD()->setCustomName("Warrior Blade"))
    ->fill(1, VanillaItems::BOW()->setCustomName("Hunter's Bow"))
    ->readonly()
    ->onClick(function (WindowTransaction $tx): void {
        $player = $tx->getPlayer();
        $item = $tx->getItemClicked();
        $player->sendMessage("You selected: " . $item->getCustomName());
        $player->removeCurrentWindow();
    })
    ->onClose(function (Player $player): void {
        $player->sendMessage("Thanks for visiting the shop!");
    })
    ->sendTo($player);

Class-based approach extending a typed Window subclass:

use imperazim\window\ChestWindow;
use imperazim\window\transaction\WindowTransaction;
use pocketmine\block\VanillaBlocks;
use pocketmine\item\VanillaItems;
use pocketmine\player\Player;

class KitSelectorWindow extends ChestWindow {

    protected function structure(Player $player, ?array $data): void {
        $this->setName("Select a Kit");
        $this->readonly();
        $this->fill(11, VanillaItems::IRON_SWORD()->setCustomName("Warrior"));
        $this->fill(13, VanillaItems::BOW()->setCustomName("Archer"));
        $this->fill(15, VanillaItems::SPLASH_POTION()->setCustomName("Healer"));
        $this->fillBorder(
            VanillaBlocks::STAINED_GLASS_PANE()->asItem()->setCustomName(" ")
        );
    }

    protected function onClick(WindowTransaction $tx): void {
        $player = $tx->getPlayer();
        match ($tx->getSlot()) {
            11 => $player->sendMessage("Warrior kit selected"),
            13 => $player->sendMessage("Archer kit selected"),
            15 => $player->sendMessage("Healer kit selected"),
            default => null,
        };
        $player->removeCurrentWindow();
    }

    protected function onClose(Player $player): void {
        $player->sendMessage("Kit selection closed.");
    }
}

// Opens automatically on construction
new KitSelectorWindow($player);

// Pass custom data via the second argument
new KitSelectorWindow($player, ["category" => "pvp"]);

Other container types with DynamicWindow:

use imperazim\window\DynamicWindow;
use pocketmine\item\VanillaItems;

// Hopper (5 slots)
DynamicWindow::hopper("Pick a Difficulty")
    ->fill(0, VanillaItems::FEATHER()->setCustomName("Easy"))
    ->fill(2, VanillaItems::IRON_INGOT()->setCustomName("Normal"))
    ->fill(4, VanillaItems::BLAZE_ROD()->setCustomName("Hard"))
    ->readonly()
    ->sendTo($player);

// Barrel (27 slots)
DynamicWindow::barrel("Loot Barrel")->fillSlots($items)->readonly()->sendTo($player);

// Furnace (3 slots) with progress bar
$window = DynamicWindow::furnace("Smelting");
$window->fill(0, VanillaItems::IRON_ORE()->setCustomName("Input"))
    ->fill(2, VanillaItems::IRON_INGOT()->setCustomName("Output"))
    ->readonly()
    ->sendTo($player, function (bool $success) use ($window, $player): void {
        if ($success) {
            $window->setProperty($player, 1, 200);
        }
    });

// Blast Furnace, Smoker, Brewing Stand
DynamicWindow::blastFurnace("Alloy Forge")->readonly()->sendTo($player);
DynamicWindow::smoker("Food Processor")->readonly()->sendTo($player);
DynamicWindow::brewingStand("Potion Lab")->readonly()->sendTo($player);

imperazim\window\preset

Highlights

  • Added PaginatedWindow class -- pre-built paginated window for browsing large item lists. Uses a double chest by default. Supports configurable items per page, navigation slot positions, custom nav button items, border items, click/close handlers, and read-only mode. Internally uses Pagination for page splitting and automatic navigation.
  • Added ConfirmWindow class -- pre-built confirmation dialog with confirm (slot 11) and cancel (slot 15) items in a single chest. Always read-only. Supports custom confirm/cancel/filler items and onConfirm()/onCancel() callbacks. Closing the window triggers the cancel callback.

Usage

Paginated item browser:

use imperazim\window\preset\PaginatedWindow;
use imperazim\window\transaction\WindowTransaction;
use pocketmine\block\VanillaBlocks;
use pocketmine\item\VanillaItems;
use pocketmine\player\Player;

$allItems = []; // Large array of Item objects
$glass = VanillaBlocks::STAINED_GLASS_PANE()->asItem()->setCustomName(" ");

PaginatedWindow::create("All Items", $allItems)
    ->setItemsPerPage(45)
    ->setNavSlots(45, 53)
    ->setBorderItem($glass)
    ->setPrevItem(VanillaItems::ARROW()->setCustomName("Previous Page"))
    ->setNextItem(VanillaItems::ARROW()->setCustomName("Next Page"))
    ->readonly()
    ->onClick(function (WindowTransaction $tx): void {
        $tx->getPlayer()->sendMessage("Selected: " . $tx->getItemClicked()->getName());
    })
    ->onClose(function (Player $player): void {
        $player->sendMessage("Browser closed.");
    })
    ->sendTo($player);

Confirmation dialog:

use imperazim\window\preset\ConfirmWindow;
use pocketmine\block\VanillaBlocks;
use pocketmine\player\Player;

ConfirmWindow::create("Delete this warp?")
    ->setConfirmItem(
        VanillaBlocks::EMERALD()->asItem()->setCustomName("Yes, delete it")
    )
    ->setCancelItem(
        VanillaBlocks::REDSTONE()->asItem()->setCustomName("No, keep it")
    )
    ->setFillerItem(
        VanillaBlocks::STAINED_GLASS_PANE()->asItem()->setCustomName(" ")
    )
    ->onConfirm(function (Player $player): void {
        $player->sendMessage("Warp deleted.");
    })
    ->onCancel(function (Player $player): void {
        $player->sendMessage("Deletion cancelled.");
    })
    ->sendTo($player);

imperazim\window\layout

Highlights

  • Added Layout class -- pre-defined layout configurations: bordered() fills the border and places items in the inner area, rows() places items by row index, navBar() fills the last row with navigation items and optional filler, centerRow() centers items horizontally in a specific row.
  • Added Pattern class -- visual fill patterns: border() fills border slots, inner() fills inner slots, fill() fills all slots, checkerboard() alternates two items, row() fills a single row, group() fills a SlotGroup with one item, groupItems() distributes an item array across a SlotGroup.
  • Added SlotGroup class -- named group of inventory slot indices. Factory methods: range() for consecutive slots, of() for explicit indices, border() for border slots of a chest-style inventory, inner() for inner (non-border) slots, row() for a specific row, column() for a specific column.
  • Added Pagination class -- automatic page splitting for item lists. Handles applyPage() to place items and navigation buttons, handleClick() to detect nav button clicks and switch pages. Configurable items per page, start slot, navigation slots, custom prev/next/filler items.

Usage

use imperazim\window\DynamicWindow;
use imperazim\window\layout\Layout;
use imperazim\window\layout\Pattern;
use imperazim\window\layout\SlotGroup;
use pocketmine\block\VanillaBlocks;
use pocketmine\item\VanillaItems;

$window = DynamicWindow::doubleChest("Organized Menu");
$glass = VanillaBlocks::STAINED_GLASS_PANE()->asItem()->setCustomName(" ");

// Bordered layout with content items in the inner area
Layout::bordered($window, $glass, $contentItems);

// Row-based layout
Layout::rows($window, [
    0 => [VanillaItems::DIAMOND(), VanillaItems::EMERALD()],
    2 => [VanillaItems::GOLD_INGOT(), VanillaItems::IRON_INGOT()],
]);

// Center items in a specific row
Layout::centerRow($window, 1, [
    VanillaItems::NETHER_STAR()->setCustomName("Special Item"),
]);

// Navigation bar in the last row
Layout::navBar($window, [
    0 => VanillaItems::ARROW()->setCustomName("Back"),
    8 => VanillaItems::ARROW()->setCustomName("Next"),
], $glass);

// Checkerboard pattern
Pattern::checkerboard($window, $glass, VanillaItems::AIR());

// Named slot groups
$innerSlots = SlotGroup::inner(54);
$borderSlots = SlotGroup::border(54);
$thirdRow = SlotGroup::row(2);
$middleColumn = SlotGroup::column(4, 6);
$custom = SlotGroup::of("hotbar", [0, 1, 2, 3, 4, 5, 6, 7, 8]);

// Fill a slot group with one item
Pattern::group($window, $borderSlots, $glass);

// Distribute items across a slot group
Pattern::groupItems($window, $innerSlots, $itemArray);

Using Pagination directly:

use imperazim\window\DynamicWindow;
use imperazim\window\layout\Pagination;
use imperazim\window\transaction\WindowTransaction;
use pocketmine\item\VanillaItems;

$window = DynamicWindow::doubleChest("Browse Items");
$pagination = new Pagination(
    items: $allItems,
    itemsPerPage: 45,
    startSlot: 0,
    prevSlot: 45,
    nextSlot: 53,
);
$pagination->applyPage($window, 0);

$window->readonly()
    ->onClick(function (WindowTransaction $tx) use ($pagination, $window): void {
        if ($pagination->handleClick($window, $tx)) {
            return; // Navigation button was clicked
        }
        // Handle normal item click
    })
    ->sendTo($player);

imperazim\window\inventory

Highlights

  • Added WindowInventory class -- custom SimpleInventory that implements BlockInventory via BlockInventoryTrait. Associates the inventory with a fake block position that is updated when the graphic is placed. Constructor takes the slot count.
  • Added InventoryBridge class -- manages bidirectional synchronization between a window's internal inventory and an external inventory. On construction, copies external contents into internal and attaches listeners in both directions. Call destroy() to remove listeners.
  • Added InventorySync class -- InventoryListener that forwards slot and content changes from one inventory to another. Uses a syncing flag and pause()/resume() to prevent infinite feedback loops during bidirectional sync.

Usage

use imperazim\window\DynamicWindow;
use pocketmine\inventory\SimpleInventory;

// Bind an external inventory for bidirectional sync
$sharedInventory = new SimpleInventory(27);

DynamicWindow::chest("Shared Storage")
    ->bindInventory($sharedInventory)
    ->sendTo($player);

// Changes in the window reflect in $sharedInventory and vice versa

imperazim\window\transaction

Highlights

  • Added WindowTransaction class -- represents a player's click on a window slot. Provides getPlayer(), getItemClicked() (item in the slot), getItemClickedWith() (item on cursor), getSlot(), getAction(), and getTransaction(). By default the transaction is allowed; call cancel() to prevent item movement, allow() to re-enable it. Call then() to schedule a post-transaction callback that fires after the inventory state settles (queued through the NSL system).

Usage

use imperazim\window\transaction\WindowTransaction;

->onClick(function (WindowTransaction $tx): void {
    $player = $tx->getPlayer();
    $clicked = $tx->getItemClicked();
    $cursor = $tx->getItemClickedWith();
    $slot = $tx->getSlot();

    $tx->cancel();

    // Schedule a callback after the transaction is processed
    $tx->then(function () use ($player): void {
        $player->sendMessage("Action completed.");
    });
})

imperazim\window\session

Highlights

  • Added WindowSession class -- per-player session state. Tracks the currently open WindowInfo, manages the graphic lifecycle (send/remove), and coordinates the NSL retry loop for reliable window delivery. Exposes the WindowNetwork instance for async operations. Handles setCurrentWindow() which sends the graphic, waits for an NSL round-trip, opens the inventory, and enters the retry loop if the client rejects the container.
  • Added SessionManager class -- manages WindowSession lifecycle. Creates sessions on PlayerLoginEvent (selecting the appropriate NetworkHandler based on device OS) and destroys them on PlayerQuitEvent. Provides get() and getOrThrow() for session access.
  • Added WindowInfo class -- value object holding a DynamicWindow, its WindowGraphic, and an optional custom name. Represents the state of a currently displayed window menu.

Usage

use imperazim\window\WindowManager;
use pocketmine\player\Player;

// Get a player's session
$session = WindowManager::getSession($player);

// Check if a window is currently open
$current = $session?->getCurrent();
if ($current !== null) {
    $dynamicWindow = $current->window;
    $graphic = $current->graphic;
}

// Remove the current window programmatically
$session?->removeCurrentWindow();

imperazim\window\session\network

Highlights

  • Added WindowNetwork class -- async operation queue using NetworkStackLatencyPacket as an ACK mechanism. The Bedrock client responds to each NSL packet with a matching timestamp, which acts as a "ready signal" for sequencing operations. Provides wait() for immediate async operations, waitUntil() for timed waits, notify() to match incoming timestamps, dropAll() and dropOfType() to cancel pending operations, and hookContainerOpen() to intercept and translate ContainerOpenPacket through the graphic's packet translator.
  • Added NetworkHandler interface -- creates LatencyEntry instances with platform-specific timestamp encoding. Different platforms (e.g., PlayStation) encode NSL timestamps differently.
  • Added NetworkHandlerRegistry class -- maps device OS to NetworkHandler implementations. Includes a default handler (timestamp * 1000000) and a PlayStation-specific handler (timestamp * 1000).
  • Added LatencyEntry class -- value object representing a queued async operation. Holds the delay type, the sent timestamp, the network-encoded timestamp, and the callback closure. The callback returns true to repeat the operation (another NSL round-trip) or false to finish.

Usage

The networking layer is managed automatically by WindowSession and WindowManager. Direct interaction is rarely needed, but it is possible:

use imperazim\window\WindowManager;

// Access the network handler registry to register a custom platform handler
$registry = WindowManager::getSessionManager()->getHandlerRegistry();
$registry->register($customDeviceOs, $customHandler);

// Access a player's network queue
$session = WindowManager::getSession($player);
$pending = $session->network->getPending();

imperazim\window\graphic

Highlights

  • Added WindowGraphic interface -- defines the contract for client-side visual elements: send() to place the graphic, sendInventory() to open the inventory on it, remove() to clean up, getPacketTranslator() for ContainerOpenPacket interception, and getAnimationDuration() for open animation timing.
  • Added PositionedGraphic interface -- extends WindowGraphic with getPosition() for block-based graphics that have a world position.
  • Added BlockActorGraphic class -- sends a fake block with tile entity NBT (used for chest, hopper, barrel, furnace, etc.). On removal, restores the real world block at that position.
  • Added BlockGraphic class -- sends a plain fake block without tile data. Used as a barrier to prevent visual merging between fake and real chests on adjacent positions.
  • Added MultiBlockGraphic class -- composite graphic managing multiple PositionedGraphic instances. Delegates inventory and translation to the primary (first) graphic. Used for double chests (two paired blocks plus barrier blocks).
  • Added PairedChestGraphic class -- sends a fake chest block with TAG_PAIRX/TAG_PAIRZ NBT to link two chests for a double chest window.
  • Added MinecartChestGraphic class -- entity-based graphic that spawns an invisible chest minecart at the player's position. Useful when placing fake blocks is not viable (e.g., in the void or mid-air).

Usage

Graphics are created automatically by WindowType implementations. For custom window types, you can construct them directly:

use imperazim\window\graphic\BlockActorGraphic;
use imperazim\window\graphic\MinecartChestGraphic;
use imperazim\window\graphic\MultiBlockGraphic;
use imperazim\window\graphic\BlockGraphic;
use imperazim\window\graphic\translator\BlockTranslator;
use pocketmine\block\VanillaBlocks;
use pocketmine\math\Vector3;

// Single block actor graphic
$graphic = new BlockActorGraphic(
    new Vector3(100, 64, 200),
    VanillaBlocks::CHEST(),
    "Chest",
    new BlockTranslator(),
);

// Entity-based graphic for void/air scenarios
$graphic = new MinecartChestGraphic(
    $player->getPosition(),
);

// Composite with barriers for double chest
$primary = new BlockActorGraphic($pos, VanillaBlocks::CHEST(), "Chest", new BlockTranslator());
$barrier = new BlockGraphic($adjacentPos, VanillaBlocks::BARRIER());
$composite = new MultiBlockGraphic($primary, $barrier);

imperazim\window\graphic\translator

Highlights

  • Added PacketTranslator interface -- translates a ContainerOpenPacket in-place to point to the correct fake block/entity. Used to rewrite block position, actor ID, or window type.
  • Added BlockTranslator class -- rewrites the ContainerOpenPacket block position to match the graphic's PositionedGraphic::getPosition().
  • Added CompositeTranslator class -- chains multiple PacketTranslator instances sequentially. Applies each translator in order.
  • Added WindowTypeTranslator class -- overwrites the window type field in ContainerOpenPacket. Used for non-chest containers (hopper, furnace, blast furnace, smoker, brewing stand) that require a specific UI type constant.

Usage

Translators are composed automatically by BlockActorWindowType. For custom types:

use imperazim\window\graphic\translator\BlockTranslator;
use imperazim\window\graphic\translator\WindowTypeTranslator;
use imperazim\window\graphic\translator\CompositeTranslator;
use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes;

// Single translator
$translator = new BlockTranslator();

// Composite: rewrite position + window type
$translator = new CompositeTranslator(
    new BlockTranslator(),
    new WindowTypeTranslator(WindowTypes::HOPPER),
);

imperazim\window\type

Highlights

  • Added WindowType interface -- defines a window menu type. Each implementation provides createInventory() to create the internal inventory, createGraphic() to create the client-side graphic for a player, and getSize() for the slot count.
  • Added WindowTypeIds interface -- string constants for all built-in window type identifiers: TYPE_CHEST, TYPE_DOUBLE_CHEST, TYPE_HOPPER, TYPE_BARREL, TYPE_FURNACE, TYPE_BLAST_FURNACE, TYPE_SMOKER, TYPE_BREWING_STAND.
  • Added WindowTypeRegistry class -- maps string identifiers to WindowType instances. Provides register(), get(), has(), and getIds(). All built-in types are registered automatically during WindowManager::init().
  • Added BlockActorWindowType class -- creates a single fake block with tile entity. Used for chest, hopper, barrel, furnace, blast furnace, smoker, and brewing stand. When the block is a chest, barrier blocks are automatically placed on adjacent real chests to prevent visual merging. Composes BlockTranslator and optionally WindowTypeTranslator for non-default window types.
  • Added DoubleBlockWindowType class -- creates two paired chest blocks side by side with PairedChestGraphic for a 54-slot double chest. Places barriers on adjacent real chests.

Usage

use imperazim\window\WindowManager;
use imperazim\window\type\WindowType;
use imperazim\window\type\WindowTypeIds;
use imperazim\window\graphic\WindowGraphic;
use imperazim\window\graphic\MinecartChestGraphic;
use imperazim\window\inventory\WindowInventory;
use pocketmine\inventory\Inventory;
use pocketmine\player\Player;

// Register a custom window type
$registry = WindowManager::getTypeRegistry();

$registry->register("myPlugin:minecart_chest", new class implements WindowType {
    public function createInventory(): Inventory {
        return new WindowInventory(27);
    }
    public function createGraphic(Player $player): ?WindowGraphic {
        return new MinecartChestGraphic($player->getPosition());
    }
    public function getSize(): int {
        return 27;
    }
});

// Use the custom type
use imperazim\window\DynamicWindow;

DynamicWindow::create("myPlugin:minecart_chest")
    ->setName("Minecart Storage")
    ->readonly()
    ->sendTo($player);

NSL Retry Mechanism

LibWindow uses NetworkStackLatencyPacket (NSL) as an acknowledgment system to guarantee reliable window delivery. The full sequence is:

  1. The fake graphic (block or entity) is sent to the client.
  2. An NSL packet with a unique timestamp is sent. The system waits for the client to echo back the matching timestamp, confirming it processed the graphic.
  3. Once acknowledged, the inventory is opened via ContainerOpenPacket. The WindowNetwork hooks into PocketMine's ContainerOpenCallbacks to intercept and translate the packet (rewriting position, window type) through the graphic's PacketTranslator.
  4. The system enters a retry loop: another NSL packet is sent. If the client accepted the window, no ContainerClosePacket arrives during the round-trip, and the open is confirmed.
  5. If the client rejects the container (sends a ContainerClosePacket with window ID 255/-1), the system retries by resending ContainerClose + ContainerOpen + inventory contents, then waits for another NSL round-trip.
  6. This repeats for up to 20 attempts. If all retries fail or the player disconnects, the window is cleaned up and the callback receives false.

The NetworkHandlerRegistry ensures platform-specific timestamp encoding is handled correctly (e.g., PlayStation echoes timestamp * 1000 instead of the standard format). Developers never need to interact with this system directly -- all windows automatically benefit from reliable delivery.

Licensing information

This project is licensed under MIT. Please see the LICENSE file for details.

EasyLibrary internal package

LibWindow now publishes an EasyLibrary 3.x internal package asset for the migration from standalone-only libraries to installable internal packages.

Release assets now include:

  • LibWindow-2.0.0.phar
  • LibWindow-2.0.0.easylib.zip
  • package.yml
  • checksums.txt

The standalone plugin remains supported. During the EasyLibrary 3.x migration, if the standalone plugin is installed on a server, EasyLibrary marks the internal package as shadowed and does not autoload it.

About

Inventory window UI utilities for PocketMine-MP plugins.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages