Skip to content

InvMenu v4.0 API Documentation

Muqsit Rayyan edited this page Sep 3, 2022 · 5 revisions

InvMenu is a pmmp/PocketMine-MP virion that provides a simple API to create portable ("fake") inventories and handle inventory transactions happening within.

Initialization

If you are new to virions, refer to Using InvMenu in a plugin to learn how to setup virions to work with plugins.
Before a plugin is able to create InvMenus and handle transactions, InvMenuHandler must be registered during plugin enable / onEnable.

final class MyPlugin extends PluginBase{

	protected function onEnable() : void{
+		if(!InvMenuHandler::isRegistered()){
+			InvMenuHandler::register($this);
+		}
	}
}

Registering a Custom InvMenu Type / Metadata

InvMenu comes with the following pre-registered InvMenu types out of the box: InvMenu::TYPE_CHEST, InvMenu::TYPE_DOUBLE_CHEST and InvMenu::TYPE_HOPPER.

Registering an InvMenu type of inventory that hasn't already been pre-registered requires gathering some information about the MCPE protocol. Most of this information can be found in PocketMine's BedrockProtocol library.

InvMenuType objects process how inventories and GUIs are displayed to the client. These objects are mapped to a unique string identifier which can be passed to InvMenu::create() to create an InvMenu instance of a given InvMenuType. InvMenu maps InvMenu::TYPE_CHEST ("invmenu::chest") to an InvMenuType object that processes how a 27-slot chest inventory is displayed to the client.

As the process of creating an InvMenuType object from scratch is rather complex, there are a few helper methods for assistance. InvMenuTypeRegistry class has a few examples on how these methods are used. To register a dispenser menu type for example, execute this during plugin enable / onEnable:

// class MyPluginMainClass extends PluginBase {
public const TYPE_DISPENSER = "my_dispenser_menu";

protected function onEnable() : void{
	if(!InvMenuHandler::isRegistered()){
		InvMenuHandler::register($this);
	}

	InvMenuHandler::getTypeRegistry()->register(self::TYPE_DISPENSER /* identifier */, InvMenuTypeBuilders::BLOCK_ACTOR_FIXED()
		->setBlock(VanillaBlocks::DISPENSER()) // block type
		->setSize(9) // number of slots
		->setBlockActorId("Dispenser") // MCPE tile entity identifier
		->setNetworkWindowType(WindowTypes::DISPENSER) // MCPE window type id
	->build());
}
// }

Once registered, a dispenser menu can be created using InvMenu::create(MyPluginMainClass::TYPE_DISPENSER) (or InvMenu::create("my_dispenser_menu")).

Creating an InvMenu

An InvMenu is an object that holds an inventory along with information about it, such as the name of the menu and transaction handlers (if any). To create an InvMenu, run InvMenu::create specifying a registered menu identifier. For example, to create an InvMenu of type InvMenu::TYPE_CHEST:

$menu = InvMenu::create(InvMenu::TYPE_CHEST);

Setting a name to an InvMenu

By default, an InvMenu is created with vanilla inventory title (the title of a chest inventory for example would be "Chest"). InvMenu Name

To set a custom menu name, use InvMenu::setName().

$menu->setName("Custom Title");

Sending an InvMenu

To send an InvMenu instance to a player, use InvMenu::send().

$menu->send($player);

An InvMenu instance can be sent to multiple players.

$menu->send($player1);
$menu->send($player2);

As an InvMenu holds just one inventory, players that are sent the same InvMenu instance will be viewing the same inventory.

A per-player custom name can be specified to InvMenus by passing the name parameter to InvMenu::send().

$menu->send($player, "Hello " . $player->getName());

When no per-player custom name is specified (or if null is passed to the name parameter: $menu->setName($player, null)), the name passed to InvMenu::setName() is used instead. If no name was specified for the menu, the menu's vanilla inventory title is used instead.

InvMenu instances are sent asynchronously to players due to game limitations. This also means InvMenu::send() isn't guaranteed to send the inventory to the player. To verify whether an inventory was sent to a player, specify a callback parameter to InvMenu::send().

$menu->send($player, $name, function(bool $success) : void{
	if($success){
		// successfully sent to player
	}else{
		// failed to send to player
	}
});

An InvMenu::send request can fail due to reasons such as:

  • Another InvMenu::send request (cancels the previous InvMenu::send request if there's one queued).
    $menu1->send($player);
    $menu2->send($player); // $menu1 will fail to be sent to player
  • Failure when setting a ghost block for the inventory, such as InvMenu attempting to set a chest block at Y>256 or Y<0.
  • Player quitting the server before the request could be fulfilled.

Handling Inventory Transactions within an InvMenu

By default, all inventory transactions happening within an InvMenu are unhandled, meaning a player could move items in and out of the inventory just like you could within a chest under vanilla circumstances. To limit inventory actions within an InvMenu, an InvMenu handler can be assigned to a given InvMenu.

An InvMenu handler is a Closure with the signature:

Closure(InvMenuTransaction $transaction) : InvMenuTransactionResult;

InvMenuTransaction holds information about the inventory transaction, such as the player initiating the transaction, the inventory instance being modified, item that is being placed in the inventory, and the item that is being removed from the inventory.

An InvMenuTransactionResult has two roles:

  • Limiting the transaction (by discarding or continuing the transaction).
  • Providing a mechanism to run code after the transaction has been fully processed.

To disallow players from modifying contents of an inventory, $transaction->discard() can be returned.

$menu->setListener(function(InvMenuTransaction $transaction) : InvMenuTransactionResult{
	return $transaction->discard(); // revert the inventory transaction
});

To disallow players from taking out an apple from the inventory, but allow taking out any other item:

$menu->setListener(function(InvMenuTransaction $transaction) : InvMenuTransactionResult{
	if($transaction->getOut()->getId() === ItemIds::APPLE){
		return $transaction->discard(); // revert the inventory transaction
	}else{
		return $transaction->continue(); // allow the inventory transaction
	}
});

To disallow players from placing items in the inventory:

$menu->setListener(function(InvMenuTransaction $transaction) : InvMenuTransactionResult{
	if($transaction->getIn()->isNull()){
		return $transaction->continue(); // allow the inventory transaction
	}else{
		return $transaction->discard(); // revert the inventory transaction
	}
});

InvMenuTransactionResult::then() can be used to take an action once the inventory transaction has completed. This is useful in cases where you'd want to open a Form after an inventory.

$menu->setListener(function(InvMenuTransaction $transaction) : InvMenuTransactionResult{
	return $transaction->discard()->then(function(Player $player) : void{
		$player->sendForm($someForm);
	});
});

A short-hand InvMenu::readonly() can be used to discard all transactions.

$menu->setListener(InvMenu::readonly()); // disallow modifying the inventory

To listen for transactions with InvMenu::readonly(), a Closure of the type Closure(DeterministicInvMenuTransaction $transaction) : InvMenuTransactionResult can be supplied to InvMenu::readonly(). As the listener returns void instead of InvMenuTransactionResult, DeterministicInvMenuTransaction::then() can be used instead to perform post-transaction actions.

$menu->setListener(InvMenu::readonly(function(DeterministicInvMenuTransaction $transaction) : void{
	// do anything
}));

Listening Inventory Close within an InvMenu

An InvMenu close listener is a Closure with the signature:

Closure(Player $player, Inventory $inventory) : void;

To set an inventory close listener to an InvMenu, use InvMenu::setInventoryCloseListener():

$menu->setInventoryCloseListener(function(Player $player, Inventory $inventory) : void{
	echo $player->getName(), " closed the inventory!";
});

Examples

Example usage of InvMenu can be found in InvMenu v4.0 Examples
A list of open-source plugins that make use of InvMenu can be found at https://github.com/Muqsit/InvMenu/wiki/List-of-plugins-using-InvMenu

Precautions

  • As with any object holding a scoped closure in PHP, a reference to an object may be held for longer than expected. Generally, circular references as such are automatically collected much later (when /gc is executed (see it's Cycles report) or when gc_collect_cycles() is called). You may want to set a null transaction handler (or an InvMenu::readonly() transaction handler) and a null inventory close listener when you are done handling the InvMenu to ensure circular references are not held once the InvMenu instance is out of scope.
    // dispose an InvMenu
    $menu->setListener(InvMenu::readonly() /* or null */);
    $menu->setInventoryCloseListener(null);