Separate inventory holder info from container & player inventories (#6533)

This PR breaks the cyclic dependency between `Inventory` and its holder, which unblocks a lot of new developments.

### Related issues & PRs
- Fixes #5033
- Removes a blocker for #6147 (which in turn means that async tasks will eventually be able to work with tiles)
- Removes a blocker for #2684

## Changes
### API changes
- `Player->getCurrentWindow()` now returns `?InventoryWindow` instead of `?Inventory`
- `Player->setCurrentWindow()` now accepts `?InventoryWindow` instead of `?Inventory`
- `InventoryWindow` introduced, which is created for each player viewing the inventory, provides decorative information like holder info for `InventoryTransactionEvent`, and is destroyed when the window is closed, eliminating cyclic references
- Added:
  - `player\InventoryWindow`
  - `player\PlayerInventoryWindow` - wraps all permanent inventories of Player with type info for transactions
  - `inventory\Hotbar` - replaces all hotbar usages in `PlayerInventory`
  - `Human->getHotbar()`
  - `Human->getMainHandItem()`, `Human->setMainHandItem()`, `Human->getOffHandItem()`, `Human->setOffHandItem()`
  - `block\utils\AnimatedContainerLike` & `block\utils\AnimatedContainerLikeTrait` (for chests, shulkerboxes, etc)
  - `block\utils\Container` & `block\utils\ContainerTrait` for blocks containing items (chests, etc)
  - `block\utils\MenuAccessor` implemented by all blocks that can open inventory menus
  - `block\utils\MenuAccessorTrait` used by blocks with menus but without inventories (anvils, crafting tables etc)
- Removed:
  - `inventory\DelegateInventory` (only used for ender chests)
  - `inventory\PlayerInventory`,
  - `inventory\PlayerOffHandInventory`,
  - `inventory\PlayerCraftingInventory`,
  - `inventory\PlayerCursorInventory` - these have all been internally replaced by `SimpleInventory` & they will appear as `PlayerInventoryWindow` in transactions (check `getType()` against the `PlayerInventoryWindow::TYPE_*` constants to identify them)
  - `block\inventory\AnimatedBlockInventoryTrait`, (blocks now handle this logic directly using `AnimatedContainer` and `AnimatedContainerTrait`)
  - `block\inventory\BlockInventoryTrait`,
  - `block\inventory\BlockInventory`
- Most `BlockInventory` classes have been transitioned to `InventoryWindow` wrappers
- Tiles now all use `SimpleInventory` internally (no cyclic references) except for `Chest` (which uses `CombinedInventory`, without holder info)
- `InventoryOpenEvent` and `InventoryCloseEvent` now provide `InventoryWindow` instead of `Inventory` (to provide type information)
- `InventoryTransaction` and `SlotChangeAction` now provide `InventoryWindow` instead of `Inventory`
- Renamed `TransactionBuilderInventory` to `SlotChangeActionBuilder`
- `TransactionBuilderInventory->getBuilder()` now accepts `InventoryWindow` instead of `Inventory`
- `DoubleChestInventory` superseded by `CombinedInventory` - this new class allows combining any number of inventories behind a single object; mainly used for double chests but plugins could use it to do lots of fun things

### Impacts to plugins
Plugins can now do the following:
```php
$block = $world->getBlockAt($x, $y, $z);
if($block instanceof MenuAccessor){
    $block->openToUnchecked($player);
}
```
As compared to the old way:
```php
$tile = $world->getTileAt($x, $y, $z);
if($tile instanceof Container){
    $player->setCurrentWindow($tile->getInventory());
}
```

#### Advantages
- No tile access needed
- Works for menu blocks without inventories as well as container blocks
- Less code

### Behavioural changes
Inventories no longer keep permanent cyclic references to their holders.

## Backwards compatibility
This makes significant BC breaks. However, all changes are able to be adapted to and the same amount of information is present on all APIs and events.

## Follow-up
- Implement #6147 
- Support inventory inheritance when copying blocks from one position to another
This commit is contained in:
Dylan T.
2025-09-02 19:23:16 +01:00
committed by GitHub
parent 702733bdde
commit 644f73aa84
91 changed files with 1985 additions and 1608 deletions

View File

@ -1,67 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\player\Player;
use pocketmine\world\sound\Sound;
use function count;
trait AnimatedBlockInventoryTrait{
use BlockInventoryTrait;
public function getViewerCount() : int{
return count($this->getViewers());
}
/**
* @return Player[]
* @phpstan-return array<int, Player>
*/
abstract public function getViewers() : array;
abstract protected function getOpenSound() : Sound;
abstract protected function getCloseSound() : Sound;
public function onOpen(Player $who) : void{
parent::onOpen($who);
if($this->holder->isValid() && $this->getViewerCount() === 1){
//TODO: this crap really shouldn't be managed by the inventory
$this->animateBlock(true);
$this->holder->getWorld()->addSound($this->holder->add(0.5, 0.5, 0.5), $this->getOpenSound());
}
}
abstract protected function animateBlock(bool $isOpen) : void;
public function onClose(Player $who) : void{
if($this->holder->isValid() && $this->getViewerCount() === 1){
//TODO: this crap really shouldn't be managed by the inventory
$this->animateBlock(false);
$this->holder->getWorld()->addSound($this->holder->add(0.5, 0.5, 0.5), $this->getCloseSound());
}
parent::onClose($who);
}
}

View File

@ -1,57 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\block\Barrel;
use pocketmine\inventory\SimpleInventory;
use pocketmine\world\Position;
use pocketmine\world\sound\BarrelCloseSound;
use pocketmine\world\sound\BarrelOpenSound;
use pocketmine\world\sound\Sound;
class BarrelInventory extends SimpleInventory implements BlockInventory{
use AnimatedBlockInventoryTrait;
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(27);
}
protected function getOpenSound() : Sound{
return new BarrelOpenSound();
}
protected function getCloseSound() : Sound{
return new BarrelCloseSound();
}
protected function animateBlock(bool $isOpen) : void{
$holder = $this->getHolder();
$world = $holder->getWorld();
$block = $world->getBlock($holder);
if($block instanceof Barrel){
$world->setBlock($holder, $block->setOpen($isOpen));
}
}
}

View File

@ -1,34 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\world\Position;
trait BlockInventoryTrait{
protected Position $holder;
public function getHolder() : Position{
return $this->holder;
}
}

View File

@ -1,37 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\world\Position;
final class CartographyTableInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(2);
}
}

View File

@ -1,56 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\world\Position;
use pocketmine\world\sound\ChestCloseSound;
use pocketmine\world\sound\ChestOpenSound;
use pocketmine\world\sound\Sound;
class ChestInventory extends SimpleInventory implements BlockInventory{
use AnimatedBlockInventoryTrait;
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(27);
}
protected function getOpenSound() : Sound{
return new ChestOpenSound();
}
protected function getCloseSound() : Sound{
return new ChestCloseSound();
}
public function animateBlock(bool $isOpen) : void{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0));
}
}

View File

@ -1,118 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\inventory\BaseInventory;
use pocketmine\inventory\InventoryHolder;
use pocketmine\item\Item;
use pocketmine\world\sound\ChestCloseSound;
use pocketmine\world\sound\ChestOpenSound;
use pocketmine\world\sound\Sound;
class DoubleChestInventory extends BaseInventory implements BlockInventory, InventoryHolder{
use AnimatedBlockInventoryTrait;
public function __construct(
private ChestInventory $left,
private ChestInventory $right
){
$this->holder = $this->left->getHolder();
parent::__construct();
}
public function getInventory() : self{
return $this;
}
public function getSize() : int{
return $this->left->getSize() + $this->right->getSize();
}
public function getItem(int $index) : Item{
return $index < $this->left->getSize() ? $this->left->getItem($index) : $this->right->getItem($index - $this->left->getSize());
}
protected function internalSetItem(int $index, Item $item) : void{
$index < $this->left->getSize() ? $this->left->setItem($index, $item) : $this->right->setItem($index - $this->left->getSize(), $item);
}
public function getContents(bool $includeEmpty = false) : array{
$result = $this->left->getContents($includeEmpty);
$leftSize = $this->left->getSize();
foreach($this->right->getContents($includeEmpty) as $i => $item){
$result[$i + $leftSize] = $item;
}
return $result;
}
protected function internalSetContents(array $items) : void{
$leftSize = $this->left->getSize();
$leftContents = [];
$rightContents = [];
foreach($items as $i => $item){
if($i < $this->left->getSize()){
$leftContents[$i] = $item;
}else{
$rightContents[$i - $leftSize] = $item;
}
}
$this->left->setContents($leftContents);
$this->right->setContents($rightContents);
}
protected function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{
$leftSize = $this->left->getSize();
return $slot < $leftSize ?
$this->left->getMatchingItemCount($slot, $test, $checkTags) :
$this->right->getMatchingItemCount($slot - $leftSize, $test, $checkTags);
}
public function isSlotEmpty(int $index) : bool{
$leftSize = $this->left->getSize();
return $index < $leftSize ?
$this->left->isSlotEmpty($index) :
$this->right->isSlotEmpty($index - $leftSize);
}
protected function getOpenSound() : Sound{ return new ChestOpenSound(); }
protected function getCloseSound() : Sound{ return new ChestCloseSound(); }
protected function animateBlock(bool $isOpen) : void{
$this->left->animateBlock($isOpen);
$this->right->animateBlock($isOpen);
}
public function getLeftSide() : ChestInventory{
return $this->left;
}
public function getRightSide() : ChestInventory{
return $this->right;
}
}

View File

@ -1,88 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\event\player\PlayerEnchantingOptionsRequestEvent;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\item\enchantment\EnchantingHelper as Helper;
use pocketmine\item\enchantment\EnchantingOption;
use pocketmine\item\Item;
use pocketmine\world\Position;
use function array_values;
use function count;
class EnchantInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
public const SLOT_INPUT = 0;
public const SLOT_LAPIS = 1;
/**
* @var EnchantingOption[] $options
* @phpstan-var list<EnchantingOption>
*/
private array $options = [];
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(2);
}
protected function onSlotChange(int $index, Item $before) : void{
if($index === self::SLOT_INPUT){
foreach($this->viewers as $viewer){
$this->options = [];
$item = $this->getInput();
$options = Helper::generateOptions($this->holder, $item, $viewer->getEnchantmentSeed());
$event = new PlayerEnchantingOptionsRequestEvent($viewer, $this, $options);
$event->call();
if(!$event->isCancelled() && count($event->getOptions()) > 0){
$this->options = array_values($event->getOptions());
$viewer->getNetworkSession()->getInvManager()?->syncEnchantingTableOptions($this->options);
}
}
}
parent::onSlotChange($index, $before);
}
public function getInput() : Item{
return $this->getItem(self::SLOT_INPUT);
}
public function getLapis() : Item{
return $this->getItem(self::SLOT_LAPIS);
}
public function getOutput(int $optionId) : ?Item{
$option = $this->getOption($optionId);
return $option === null ? null : Helper::enchantItem($this->getInput(), $option->getEnchantments());
}
public function getOption(int $optionId) : ?EnchantingOption{
return $this->options[$optionId] ?? null;
}
}

View File

@ -1,88 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\block\tile\EnderChest;
use pocketmine\inventory\DelegateInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerEnderInventory;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\player\Player;
use pocketmine\world\Position;
use pocketmine\world\sound\EnderChestCloseSound;
use pocketmine\world\sound\EnderChestOpenSound;
use pocketmine\world\sound\Sound;
/**
* EnderChestInventory is not a real inventory; it's just a gateway to the player's ender inventory.
*/
class EnderChestInventory extends DelegateInventory implements BlockInventory{
use AnimatedBlockInventoryTrait {
onClose as animatedBlockInventoryTrait_onClose;
}
public function __construct(
Position $holder,
private PlayerEnderInventory $inventory
){
parent::__construct($inventory);
$this->holder = $holder;
}
public function getEnderInventory() : PlayerEnderInventory{
return $this->inventory;
}
public function getViewerCount() : int{
$enderChest = $this->getHolder()->getWorld()->getTile($this->getHolder());
if(!$enderChest instanceof EnderChest){
return 0;
}
return $enderChest->getViewerCount();
}
protected function getOpenSound() : Sound{
return new EnderChestOpenSound();
}
protected function getCloseSound() : Sound{
return new EnderChestCloseSound();
}
protected function animateBlock(bool $isOpen) : void{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0));
}
public function onClose(Player $who) : void{
$this->animatedBlockInventoryTrait_onClose($who);
$enderChest = $this->getHolder()->getWorld()->getTile($this->getHolder());
if($enderChest instanceof EnderChest){
$enderChest->setViewerCount($enderChest->getViewerCount() - 1);
}
}
}

View File

@ -1,67 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\block\BlockTypeIds;
use pocketmine\inventory\SimpleInventory;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\world\Position;
use pocketmine\world\sound\ShulkerBoxCloseSound;
use pocketmine\world\sound\ShulkerBoxOpenSound;
use pocketmine\world\sound\Sound;
class ShulkerBoxInventory extends SimpleInventory implements BlockInventory{
use AnimatedBlockInventoryTrait;
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(27);
}
protected function getOpenSound() : Sound{
return new ShulkerBoxOpenSound();
}
protected function getCloseSound() : Sound{
return new ShulkerBoxCloseSound();
}
public function canAddItem(Item $item) : bool{
$blockTypeId = ItemTypeIds::toBlockTypeId($item->getTypeId());
if($blockTypeId === BlockTypeIds::SHULKER_BOX || $blockTypeId === BlockTypeIds::DYED_SHULKER_BOX){
return false;
}
return parent::canAddItem($item);
}
protected function animateBlock(bool $isOpen) : void{
$holder = $this->getHolder();
//event ID is always 1 for a chest
$holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0));
}
}

View File

@ -1,37 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\world\Position;
final class SmithingTableInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(3);
}
}

View File

@ -21,20 +21,21 @@
declare(strict_types=1);
namespace pocketmine\block\inventory;
namespace pocketmine\block\inventory\window;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\player\Player;
use pocketmine\player\TemporaryInventoryWindow;
use pocketmine\world\Position;
class AnvilInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
final class AnvilInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{
public const SLOT_INPUT = 0;
public const SLOT_MATERIAL = 1;
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(2);
public function __construct(
Player $viewer,
Position $holder
){
parent::__construct($viewer, new SimpleInventory(2), $holder);
}
}

View File

@ -0,0 +1,59 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory\window;
use pocketmine\block\utils\AnimatedContainerLike;
use pocketmine\inventory\Inventory;
use pocketmine\player\InventoryWindow;
use pocketmine\player\Player;
use pocketmine\world\Position;
class BlockInventoryWindow extends InventoryWindow{
public function __construct(
Player $viewer,
Inventory $inventory,
protected Position $holder
){
parent::__construct($viewer, $inventory);
}
public function getHolder() : Position{ return $this->holder; }
public function onOpen() : void{
parent::onOpen();
$block = $this->holder->getWorld()->getBlock($this->holder);
if($block instanceof AnimatedContainerLike){
$block->onViewerAdded();
}
}
public function onClose() : void{
$block = $this->holder->getWorld()->getBlock($this->holder);
if($block instanceof AnimatedContainerLike){
$block->onViewerRemoved();
}
parent::onClose();
}
}

View File

@ -21,22 +21,12 @@
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory;
use pocketmine\world\Position;
class BrewingStandInventory extends SimpleInventory implements BlockInventory{
use BlockInventoryTrait;
namespace pocketmine\block\inventory\window;
final class BrewingStandInventoryWindow extends BlockInventoryWindow{
public const SLOT_INGREDIENT = 0;
public const SLOT_BOTTLE_LEFT = 1;
public const SLOT_BOTTLE_MIDDLE = 2;
public const SLOT_BOTTLE_RIGHT = 3;
public const SLOT_FUEL = 4;
public function __construct(Position $holder, int $size = 5){
$this->holder = $holder;
parent::__construct($size);
}
}

View File

@ -21,20 +21,19 @@
declare(strict_types=1);
namespace pocketmine\block\inventory;
namespace pocketmine\block\inventory\window;
use pocketmine\inventory\SimpleInventory;
use pocketmine\player\Player;
use pocketmine\player\TemporaryInventoryWindow;
use pocketmine\world\Position;
class CampfireInventory extends SimpleInventory implements BlockInventory{
use BlockInventoryTrait;
final class CartographyTableInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(4);
}
public function getMaxStackSize() : int{
return 1;
public function __construct(
Player $viewer,
Position $holder
){
parent::__construct($viewer, new SimpleInventory(2), $holder);
}
}

View File

@ -21,17 +21,19 @@
declare(strict_types=1);
namespace pocketmine\block\inventory;
namespace pocketmine\block\inventory\window;
use pocketmine\crafting\CraftingGrid;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\player\Player;
use pocketmine\world\Position;
final class CraftingTableInventory extends CraftingGrid implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
final class CraftingTableInventoryWindow extends BlockInventoryWindow{
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(CraftingGrid::SIZE_BIG);
public function __construct(
Player $viewer,
Position $holder
){
//TODO: generics would be good for this, since it has special methods
parent::__construct($viewer, new CraftingGrid(CraftingGrid::SIZE_BIG), $holder);
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory\window;
use pocketmine\inventory\Inventory;
use pocketmine\player\Player;
use pocketmine\world\Position;
final class DoubleChestInventoryWindow extends BlockInventoryWindow{
public function __construct(
Player $viewer,
Inventory $inventory,
private Position $left,
private Position $right
){
parent::__construct($viewer, $inventory, $this->left);
}
public function getLeft() : Position{ return $this->left; }
public function getRight() : Position{ return $this->right; }
}

View File

@ -0,0 +1,104 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory\window;
use pocketmine\event\player\PlayerEnchantingOptionsRequestEvent;
use pocketmine\inventory\CallbackInventoryListener;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\InventoryListener;
use pocketmine\inventory\SimpleInventory;
use pocketmine\item\enchantment\EnchantingHelper as Helper;
use pocketmine\item\enchantment\EnchantingOption;
use pocketmine\item\Item;
use pocketmine\player\Player;
use pocketmine\world\Position;
use function array_values;
use function count;
final class EnchantingTableInventoryWindow extends BlockInventoryWindow{
public const SLOT_INPUT = 0;
public const SLOT_LAPIS = 1;
/** @var EnchantingOption[] $options */
private array $options = [];
private InventoryListener $listener;
public function __construct(
Player $viewer,
Position $holder
){
parent::__construct($viewer, new SimpleInventory(2), $holder);
/** @phpstan-var \WeakReference<$this> $weakThis */
$weakThis = \WeakReference::create($this);
$this->listener = new CallbackInventoryListener(
onSlotChange: static function(Inventory $_, int $slot) use ($weakThis) : void{ //remaining params unneeded
if($slot === self::SLOT_INPUT && ($strongThis = $weakThis->get()) !== null){
$strongThis->regenerateOptions();
}
},
onContentChange: static function() use ($weakThis) : void{
if(($strongThis = $weakThis->get()) !== null){
$strongThis->regenerateOptions();
}
}
);
$this->inventory->getListeners()->add($this->listener);
}
public function __destruct(){
$this->inventory->getListeners()->remove($this->listener);
}
private function regenerateOptions() : void{
$this->options = [];
$item = $this->getInput();
$options = Helper::generateOptions($this->holder, $item, $this->viewer->getEnchantmentSeed());
$event = new PlayerEnchantingOptionsRequestEvent($this->viewer, $this, $options);
$event->call();
if(!$event->isCancelled() && count($event->getOptions()) > 0){
$this->options = array_values($event->getOptions());
$this->viewer->getNetworkSession()->getInvManager()?->syncEnchantingTableOptions($this->options);
}
}
public function getInput() : Item{
return $this->inventory->getItem(self::SLOT_INPUT);
}
public function getLapis() : Item{
return $this->inventory->getItem(self::SLOT_LAPIS);
}
public function getOutput(int $optionId) : ?Item{
$option = $this->getOption($optionId);
return $option === null ? null : Helper::enchantItem($this->getInput(), $option->getEnchantments());
}
public function getOption(int $optionId) : ?EnchantingOption{
return $this->options[$optionId] ?? null;
}
}

View File

@ -21,51 +21,51 @@
declare(strict_types=1);
namespace pocketmine\block\inventory;
namespace pocketmine\block\inventory\window;
use pocketmine\crafting\FurnaceType;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\Inventory;
use pocketmine\item\Item;
use pocketmine\player\Player;
use pocketmine\world\Position;
class FurnaceInventory extends SimpleInventory implements BlockInventory{
use BlockInventoryTrait;
final class FurnaceInventoryWindow extends BlockInventoryWindow{
public const SLOT_INPUT = 0;
public const SLOT_FUEL = 1;
public const SLOT_RESULT = 2;
public function __construct(
Player $viewer,
Inventory $inventory,
Position $holder,
private FurnaceType $furnaceType
){
$this->holder = $holder;
parent::__construct(3);
parent::__construct($viewer, $inventory, $holder);
}
public function getFurnaceType() : FurnaceType{ return $this->furnaceType; }
public function getResult() : Item{
return $this->getItem(self::SLOT_RESULT);
return $this->inventory->getItem(self::SLOT_RESULT);
}
public function getFuel() : Item{
return $this->getItem(self::SLOT_FUEL);
return $this->inventory->getItem(self::SLOT_FUEL);
}
public function getSmelting() : Item{
return $this->getItem(self::SLOT_INPUT);
return $this->inventory->getItem(self::SLOT_INPUT);
}
public function setResult(Item $item) : void{
$this->setItem(self::SLOT_RESULT, $item);
$this->inventory->setItem(self::SLOT_RESULT, $item);
}
public function setFuel(Item $item) : void{
$this->setItem(self::SLOT_FUEL, $item);
$this->inventory->setItem(self::SLOT_FUEL, $item);
}
public function setSmelting(Item $item) : void{
$this->setItem(self::SLOT_INPUT, $item);
$this->inventory->setItem(self::SLOT_INPUT, $item);
}
}

View File

@ -21,10 +21,8 @@
declare(strict_types=1);
namespace pocketmine\block\inventory;
namespace pocketmine\block\inventory\window;
use pocketmine\world\Position;
final class HopperInventoryWindow extends BlockInventoryWindow{
interface BlockInventory{
public function getHolder() : Position;
}

View File

@ -21,21 +21,23 @@
declare(strict_types=1);
namespace pocketmine\block\inventory;
namespace pocketmine\block\inventory\window;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\player\Player;
use pocketmine\player\TemporaryInventoryWindow;
use pocketmine\world\Position;
final class LoomInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
final class LoomInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{
public const SLOT_BANNER = 0;
public const SLOT_DYE = 1;
public const SLOT_PATTERN = 2;
public function __construct(Position $holder, int $size = 3){
$this->holder = $holder;
parent::__construct($size);
public function __construct(
Player $viewer,
Position $holder
){
parent::__construct($viewer, new SimpleInventory(3), $holder);
}
}

View File

@ -21,16 +21,15 @@
declare(strict_types=1);
namespace pocketmine\block\inventory;
namespace pocketmine\block\inventory\window;
use pocketmine\inventory\SimpleInventory;
use pocketmine\player\Player;
use pocketmine\player\TemporaryInventoryWindow;
use pocketmine\world\Position;
class HopperInventory extends SimpleInventory implements BlockInventory{
use BlockInventoryTrait;
public function __construct(Position $holder, int $size = 5){
$this->holder = $holder;
parent::__construct($size);
final class SmithingTableInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{
public function __construct(Player $viewer, Position $holder){
parent::__construct($viewer, new SimpleInventory(3), $holder);
}
}

View File

@ -21,19 +21,17 @@
declare(strict_types=1);
namespace pocketmine\block\inventory;
namespace pocketmine\block\inventory\window;
use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\player\Player;
use pocketmine\player\TemporaryInventoryWindow;
use pocketmine\world\Position;
class StonecutterInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
final class StonecutterInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{
public const SLOT_INPUT = 0;
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(1);
public function __construct(Player $viewer, Position $holder){
parent::__construct($viewer, new SimpleInventory(1), $holder);
}
}