From 45a4282e8b4e131fc2cf37575a915bc77e157db5 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 24 Nov 2024 21:40:47 +0000 Subject: [PATCH] First look: Split up Inventory & InventoryWindow this unblocks a variety of changes, such as positionless tiles, enhanced APIs on Blocks for inventories, and also eliminates a bunch of cyclic references within the core code. linked to #5033 --- src/block/Anvil.php | 4 +- src/block/Barrel.php | 3 +- src/block/BrewingStand.php | 3 +- src/block/Campfire.php | 9 +- src/block/CartographyTable.php | 4 +- src/block/Chest.php | 23 +- src/block/CraftingTable.php | 4 +- src/block/EnchantingTable.php | 4 +- src/block/EnderChest.php | 5 +- src/block/Furnace.php | 3 +- src/block/Hopper.php | 3 +- src/block/Loom.php | 4 +- src/block/ShulkerBox.php | 3 +- src/block/SmithingTable.php | 4 +- src/block/Stonecutter.php | 4 +- .../inventory/AnimatedBlockInventoryTrait.php | 67 ---- .../AnimatedBlockInventoryWindow.php | 65 ++++ ...Inventory.php => AnvilInventoryWindow.php} | 15 +- ...nventory.php => BarrelInventoryWindow.php} | 18 +- src/block/inventory/BlockInventoryWindow.php | 42 +++ ...it.php => BrewingStandInventoryWindow.php} | 14 +- src/block/inventory/CampfireInventory.php | 8 +- ...hp => CartographyTableInventoryWindow.php} | 14 +- ...Inventory.php => ChestInventoryWindow.php} | 16 +- ...y.php => CraftingTableInventoryWindow.php} | 14 +- src/block/inventory/DoubleChestInventory.php | 33 +- ...ory.php => DoubleChestInventoryWindow.php} | 32 +- src/block/inventory/EnchantInventory.php | 85 ----- .../EnchantingTableInventoryWindow.php | 110 +++++++ ...tory.php => EnderChestInventoryWindow.php} | 55 ++-- ...ventory.php => FurnaceInventoryWindow.php} | 24 +- ...nventory.php => HopperInventoryWindow.php} | 4 +- ...mInventory.php => LoomInventoryWindow.php} | 14 +- ...tory.php => ShulkerBoxInventoryWindow.php} | 27 +- .../inventory/SmithingTableInventory.php | 37 --- ...y.php => SmithingTableInventoryWindow.php} | 11 +- ...ory.php => StonecutterInventoryWindow.php} | 12 +- src/block/tile/Barrel.php | 11 +- src/block/tile/BrewingStand.php | 23 +- src/block/tile/Campfire.php | 2 +- src/block/tile/Chest.php | 11 +- src/block/tile/Furnace.php | 23 +- src/block/tile/Hopper.php | 11 +- src/block/tile/ShulkerBox.php | 24 +- src/block/utils/BrewingStandSlot.php | 8 +- src/crafting/CraftingGrid.php | 2 +- src/entity/Human.php | 26 +- src/entity/Living.php | 3 +- src/event/inventory/InventoryCloseEvent.php | 4 +- src/event/inventory/InventoryEvent.php | 8 +- src/event/inventory/InventoryOpenEvent.php | 4 +- .../PlayerEnchantingOptionsRequestEvent.php | 7 +- src/inventory/ArmorInventory.php | 9 +- src/inventory/BaseInventory.php | 11 +- src/inventory/DelegateInventory.php | 4 + src/inventory/Inventory.php | 12 + src/inventory/PlayerCraftingInventory.php | 36 --- src/inventory/PlayerCursorInventory.php | 38 --- src/inventory/PlayerEnderInventory.php | 37 --- src/inventory/PlayerInventory.php | 40 --- src/inventory/PlayerOffHandInventory.php | 37 --- src/inventory/SimpleInventory.php | 2 +- .../transaction/InventoryTransaction.php | 30 +- ...entory.php => SlotChangeActionBuilder.php} | 31 +- .../transaction/TransactionBuilder.php | 8 +- .../transaction/action/SlotChangeAction.php | 26 +- ...MapEntry.php => ComplexWindowMapEntry.php} | 8 +- src/network/mcpe/InventoryManager.php | 303 ++++++++++-------- src/network/mcpe/InventoryManagerEntry.php | 6 +- .../mcpe/handler/InGamePacketHandler.php | 6 +- .../mcpe/handler/ItemStackRequestExecutor.php | 37 ++- .../mcpe/handler/ItemStackResponseBuilder.php | 3 +- src/player/Player.php | 108 ++++--- .../TemporaryInventoryWindow.php} | 4 +- 74 files changed, 827 insertions(+), 933 deletions(-) delete mode 100644 src/block/inventory/AnimatedBlockInventoryTrait.php create mode 100644 src/block/inventory/AnimatedBlockInventoryWindow.php rename src/block/inventory/{AnvilInventory.php => AnvilInventoryWindow.php} (74%) rename src/block/inventory/{BarrelInventory.php => BarrelInventoryWindow.php} (71%) create mode 100644 src/block/inventory/BlockInventoryWindow.php rename src/block/inventory/{BlockInventoryTrait.php => BrewingStandInventoryWindow.php} (76%) rename src/block/inventory/{CartographyTableInventory.php => CartographyTableInventoryWindow.php} (72%) rename src/block/inventory/{ChestInventory.php => ChestInventoryWindow.php} (72%) rename src/block/inventory/{CraftingTableInventory.php => CraftingTableInventoryWindow.php} (72%) rename src/block/inventory/{BrewingStandInventory.php => DoubleChestInventoryWindow.php} (54%) delete mode 100644 src/block/inventory/EnchantInventory.php create mode 100644 src/block/inventory/EnchantingTableInventoryWindow.php rename src/block/inventory/{EnderChestInventory.php => EnderChestInventoryWindow.php} (53%) rename src/block/inventory/{FurnaceInventory.php => FurnaceInventoryWindow.php} (71%) rename src/block/inventory/{BlockInventory.php => HopperInventoryWindow.php} (88%) rename src/block/inventory/{LoomInventory.php => LoomInventoryWindow.php} (75%) rename src/block/inventory/{ShulkerBoxInventory.php => ShulkerBoxInventoryWindow.php} (59%) delete mode 100644 src/block/inventory/SmithingTableInventory.php rename src/block/inventory/{HopperInventory.php => SmithingTableInventoryWindow.php} (73%) rename src/block/inventory/{StonecutterInventory.php => StonecutterInventoryWindow.php} (73%) delete mode 100644 src/inventory/PlayerCraftingInventory.php delete mode 100644 src/inventory/PlayerCursorInventory.php delete mode 100644 src/inventory/PlayerEnderInventory.php delete mode 100644 src/inventory/PlayerInventory.php delete mode 100644 src/inventory/PlayerOffHandInventory.php rename src/inventory/transaction/{TransactionBuilderInventory.php => SlotChangeActionBuilder.php} (70%) rename src/network/mcpe/{ComplexInventoryMapEntry.php => ComplexWindowMapEntry.php} (88%) rename src/{inventory/TemporaryInventory.php => player/TemporaryInventoryWindow.php} (90%) diff --git a/src/block/Anvil.php b/src/block/Anvil.php index 4b4afef61..84013baad 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\AnvilInventory; +use pocketmine\block\inventory\AnvilInventoryWindow; use pocketmine\block\utils\Fallable; use pocketmine\block\utils\FallableTrait; use pocketmine\block\utils\HorizontalFacingTrait; @@ -83,7 +83,7 @@ class Anvil extends Transparent implements Fallable{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player instanceof Player){ - $player->setCurrentWindow(new AnvilInventory($this->position)); + $player->setCurrentWindow(new AnvilInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/Barrel.php b/src/block/Barrel.php index 0f0499ab9..b1dcd8124 100644 --- a/src/block/Barrel.php +++ b/src/block/Barrel.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\inventory\BarrelInventoryWindow; use pocketmine\block\tile\Barrel as TileBarrel; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -81,7 +82,7 @@ class Barrel extends Opaque{ return true; } - $player->setCurrentWindow($barrel->getInventory()); + $player->setCurrentWindow(new BarrelInventoryWindow($player, $barrel->getInventory(), $this->position)); } } diff --git a/src/block/BrewingStand.php b/src/block/BrewingStand.php index 039693164..2ea276e75 100644 --- a/src/block/BrewingStand.php +++ b/src/block/BrewingStand.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\inventory\BrewingStandInventoryWindow; use pocketmine\block\tile\BrewingStand as TileBrewingStand; use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\SupportType; @@ -99,7 +100,7 @@ class BrewingStand extends Transparent{ if($player instanceof Player){ $stand = $this->position->getWorld()->getTile($this->position); if($stand instanceof TileBrewingStand && $stand->canOpenWith($item->getCustomName())){ - $player->setCurrentWindow($stand->getInventory()); + $player->setCurrentWindow(new BrewingStandInventoryWindow($player, $stand->getInventory(), $this->position)); } } diff --git a/src/block/Campfire.php b/src/block/Campfire.php index ce759ee87..d25f3f2cd 100644 --- a/src/block/Campfire.php +++ b/src/block/Campfire.php @@ -71,6 +71,13 @@ class Campfire extends Transparent{ protected CampfireInventory $inventory; + public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ + parent::__construct($idInfo, $name, $typeInfo); + //TODO: this should never have been in the block - it creates problems for setting blocks in different positions + //as inventories aren't designed to be cloned + $this->inventory = new CampfireInventory(); + } + /** * @var int[] slot => ticks * @phpstan-var array @@ -89,7 +96,7 @@ class Campfire extends Transparent{ $this->inventory = $tile->getInventory(); $this->cookingTimes = $tile->getCookingTimes(); }else{ - $this->inventory = new CampfireInventory($this->position); + $this->inventory = new CampfireInventory(); } return $this; diff --git a/src/block/CartographyTable.php b/src/block/CartographyTable.php index 67d950c5a..3338f219b 100644 --- a/src/block/CartographyTable.php +++ b/src/block/CartographyTable.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\CartographyTableInventory; +use pocketmine\block\inventory\CartographyTableInventoryWindow; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; @@ -32,7 +32,7 @@ final class CartographyTable extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new CartographyTableInventory($this->position)); + $player->setCurrentWindow(new CartographyTableInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/Chest.php b/src/block/Chest.php index dca21576a..e823187c3 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\inventory\ChestInventoryWindow; +use pocketmine\block\inventory\DoubleChestInventoryWindow; use pocketmine\block\tile\Chest as TileChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; @@ -74,8 +76,8 @@ class Chest extends Transparent{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player instanceof Player){ - - $chest = $this->position->getWorld()->getTile($this->position); + $world = $this->position->getWorld(); + $chest = $world->getTile($this->position); if($chest instanceof TileChest){ if( !$this->getSide(Facing::UP)->isTransparent() || @@ -85,7 +87,22 @@ class Chest extends Transparent{ return true; } - $player->setCurrentWindow($chest->getInventory()); + foreach([false, true] as $clockwise){ + $sideFacing = Facing::rotateY($this->facing, $clockwise); + $side = $this->position->getSide($sideFacing); + $pair = $world->getTile($side); + if($pair instanceof TileChest && $pair->getPair() === $chest){ + [$left, $right] = $clockwise ? [$side, $this->position] : [$this->position, $side]; + + //TODO: we should probably construct DoubleChestInventory here directly too using the same logic + //right now it uses some weird logic in TileChest which produces incorrect results + //however I'm not sure if this is currently possible + $window = new DoubleChestInventoryWindow($player, $chest->getInventory(), $left, $right); + break; + } + } + + $player->setCurrentWindow($window ?? new ChestInventoryWindow($player, $chest->getInventory(), $this->position)); } } diff --git a/src/block/CraftingTable.php b/src/block/CraftingTable.php index dcd9edce2..7fd0e43fd 100644 --- a/src/block/CraftingTable.php +++ b/src/block/CraftingTable.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\CraftingTableInventory; +use pocketmine\block\inventory\CraftingTableInventoryWindow; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; @@ -32,7 +32,7 @@ class CraftingTable extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player instanceof Player){ - $player->setCurrentWindow(new CraftingTableInventory($this->position)); + $player->setCurrentWindow(new CraftingTableInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/EnchantingTable.php b/src/block/EnchantingTable.php index 6a6c936b2..36ec15c8a 100644 --- a/src/block/EnchantingTable.php +++ b/src/block/EnchantingTable.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\EnchantInventory; +use pocketmine\block\inventory\EnchantingTableInventoryWindow; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -48,7 +48,7 @@ class EnchantingTable extends Transparent{ if($player instanceof Player){ //TODO lock - $player->setCurrentWindow(new EnchantInventory($this->position)); + $player->setCurrentWindow(new EnchantingTableInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index 9004f7c79..f4634e200 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\EnderChestInventory; +use pocketmine\block\inventory\EnderChestInventoryWindow; use pocketmine\block\tile\EnderChest as TileEnderChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; @@ -56,8 +56,7 @@ class EnderChest extends Transparent{ if($player instanceof Player){ $enderChest = $this->position->getWorld()->getTile($this->position); if($enderChest instanceof TileEnderChest && $this->getSide(Facing::UP)->isTransparent()){ - $enderChest->setViewerCount($enderChest->getViewerCount() + 1); - $player->setCurrentWindow(new EnderChestInventory($this->position, $player->getEnderInventory())); + $player->setCurrentWindow(new EnderChestInventoryWindow($player, $player->getEnderInventory(), $this->position)); } } diff --git a/src/block/Furnace.php b/src/block/Furnace.php index 7a64e3cd3..d8394fcc9 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\inventory\FurnaceInventoryWindow; use pocketmine\block\tile\Furnace as TileFurnace; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\LightableTrait; @@ -61,7 +62,7 @@ class Furnace extends Opaque{ if($player instanceof Player){ $furnace = $this->position->getWorld()->getTile($this->position); if($furnace instanceof TileFurnace && $furnace->canOpenWith($item->getCustomName())){ - $player->setCurrentWindow($furnace->getInventory()); + $player->setCurrentWindow(new FurnaceInventoryWindow($player, $furnace->getInventory(), $this->position, $this->furnaceType)); } } diff --git a/src/block/Hopper.php b/src/block/Hopper.php index 0d823674b..45d8b6112 100644 --- a/src/block/Hopper.php +++ b/src/block/Hopper.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\inventory\HopperInventoryWindow; use pocketmine\block\tile\Hopper as TileHopper; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\block\utils\SupportType; @@ -84,7 +85,7 @@ class Hopper extends Transparent{ if($player !== null){ $tile = $this->position->getWorld()->getTile($this->position); if($tile instanceof TileHopper){ //TODO: find a way to have inventories open on click without this boilerplate in every block - $player->setCurrentWindow($tile->getInventory()); + $player->setCurrentWindow(new HopperInventoryWindow($player, $tile->getInventory(), $this->position)); } return true; } diff --git a/src/block/Loom.php b/src/block/Loom.php index d3dd4f3c7..e9e634ff7 100644 --- a/src/block/Loom.php +++ b/src/block/Loom.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\LoomInventory; +use pocketmine\block\inventory\LoomInventoryWindow; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\item\Item; use pocketmine\math\Vector3; @@ -34,7 +34,7 @@ final class Loom extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new LoomInventory($this->position)); + $player->setCurrentWindow(new LoomInventoryWindow($player, $this->position)); return true; } return false; diff --git a/src/block/ShulkerBox.php b/src/block/ShulkerBox.php index d557401ee..e6f208ba0 100644 --- a/src/block/ShulkerBox.php +++ b/src/block/ShulkerBox.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\inventory\ShulkerBoxInventoryWindow; use pocketmine\block\tile\ShulkerBox as TileShulkerBox; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\block\utils\SupportType; @@ -105,7 +106,7 @@ class ShulkerBox extends Opaque{ return true; } - $player->setCurrentWindow($shulker->getInventory()); + $player->setCurrentWindow(new ShulkerBoxInventoryWindow($player, $shulker->getInventory(), $this->position)); } } diff --git a/src/block/SmithingTable.php b/src/block/SmithingTable.php index 741e9c02f..5d0f76324 100644 --- a/src/block/SmithingTable.php +++ b/src/block/SmithingTable.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\SmithingTableInventory; +use pocketmine\block\inventory\SmithingTableInventoryWindow; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; @@ -32,7 +32,7 @@ final class SmithingTable extends Opaque{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new SmithingTableInventory($this->position)); + $player->setCurrentWindow(new SmithingTableInventoryWindow($player, $this->position)); } return true; diff --git a/src/block/Stonecutter.php b/src/block/Stonecutter.php index 30c19d25d..0c739e36a 100644 --- a/src/block/Stonecutter.php +++ b/src/block/Stonecutter.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\StonecutterInventory; +use pocketmine\block\inventory\StonecutterInventoryWindow; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; @@ -37,7 +37,7 @@ class Stonecutter extends Transparent{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ - $player->setCurrentWindow(new StonecutterInventory($this->position)); + $player->setCurrentWindow(new StonecutterInventoryWindow($player, $this->position)); } return true; } diff --git a/src/block/inventory/AnimatedBlockInventoryTrait.php b/src/block/inventory/AnimatedBlockInventoryTrait.php deleted file mode 100644 index 8720c985b..000000000 --- a/src/block/inventory/AnimatedBlockInventoryTrait.php +++ /dev/null @@ -1,67 +0,0 @@ -getViewers()); - } - - /** - * @return Player[] - * @phpstan-return array - */ - 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); - } -} diff --git a/src/block/inventory/AnimatedBlockInventoryWindow.php b/src/block/inventory/AnimatedBlockInventoryWindow.php new file mode 100644 index 000000000..3dcf9207f --- /dev/null +++ b/src/block/inventory/AnimatedBlockInventoryWindow.php @@ -0,0 +1,65 @@ +inventory->getViewers()); + } + + abstract protected function getOpenSound() : Sound; + + abstract protected function getCloseSound() : Sound; + + abstract protected function animateBlock(Position $position, bool $isOpen) : void; + + protected function playSound(Position $position, bool $isOpen) : void{ + $position->getWorld()->addSound($position->add(0.5, 0.5, 0.5), $isOpen ? $this->getOpenSound() : $this->getCloseSound()); + } + + protected function doBlockEffects(bool $isOpen) : void{ + $position = $this->holder; + $this->animateBlock($position, $isOpen); + $this->playSound($position, $isOpen); + } + + public function onOpen() : void{ + parent::onOpen(); + if($this->getViewerCount() === 1){ + $this->doBlockEffects(true); + } + } + + public function onClose() : void{ + if($this->getViewerCount() === 1){ + $this->doBlockEffects(false); + } + parent::onClose(); + } +} diff --git a/src/block/inventory/AnvilInventory.php b/src/block/inventory/AnvilInventoryWindow.php similarity index 74% rename from src/block/inventory/AnvilInventory.php rename to src/block/inventory/AnvilInventoryWindow.php index 7d906a632..50705d4f5 100644 --- a/src/block/inventory/AnvilInventory.php +++ b/src/block/inventory/AnvilInventoryWindow.php @@ -24,17 +24,18 @@ declare(strict_types=1); namespace pocketmine\block\inventory; 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); } } diff --git a/src/block/inventory/BarrelInventory.php b/src/block/inventory/BarrelInventoryWindow.php similarity index 71% rename from src/block/inventory/BarrelInventory.php rename to src/block/inventory/BarrelInventoryWindow.php index 0d17d2a3e..89c8b70c7 100644 --- a/src/block/inventory/BarrelInventory.php +++ b/src/block/inventory/BarrelInventoryWindow.php @@ -24,19 +24,12 @@ 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); - } +final class BarrelInventoryWindow extends AnimatedBlockInventoryWindow{ protected function getOpenSound() : Sound{ return new BarrelOpenSound(); @@ -46,12 +39,11 @@ class BarrelInventory extends SimpleInventory implements BlockInventory{ return new BarrelCloseSound(); } - protected function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - $world = $holder->getWorld(); - $block = $world->getBlock($holder); + protected function animateBlock(Position $position, bool $isOpen) : void{ + $world = $position->getWorld(); + $block = $world->getBlock($position); if($block instanceof Barrel){ - $world->setBlock($holder, $block->setOpen($isOpen)); + $world->setBlock($position, $block->setOpen($isOpen)); } } } diff --git a/src/block/inventory/BlockInventoryWindow.php b/src/block/inventory/BlockInventoryWindow.php new file mode 100644 index 000000000..23d27647e --- /dev/null +++ b/src/block/inventory/BlockInventoryWindow.php @@ -0,0 +1,42 @@ +holder; } +} diff --git a/src/block/inventory/BlockInventoryTrait.php b/src/block/inventory/BrewingStandInventoryWindow.php similarity index 76% rename from src/block/inventory/BlockInventoryTrait.php rename to src/block/inventory/BrewingStandInventoryWindow.php index 980e947f0..eae68f60c 100644 --- a/src/block/inventory/BlockInventoryTrait.php +++ b/src/block/inventory/BrewingStandInventoryWindow.php @@ -23,12 +23,10 @@ declare(strict_types=1); namespace pocketmine\block\inventory; -use pocketmine\world\Position; - -trait BlockInventoryTrait{ - protected Position $holder; - - public function getHolder() : Position{ - return $this->holder; - } +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; } diff --git a/src/block/inventory/CampfireInventory.php b/src/block/inventory/CampfireInventory.php index ae762473e..f3bd618c2 100644 --- a/src/block/inventory/CampfireInventory.php +++ b/src/block/inventory/CampfireInventory.php @@ -24,13 +24,9 @@ declare(strict_types=1); namespace pocketmine\block\inventory; use pocketmine\inventory\SimpleInventory; -use pocketmine\world\Position; -class CampfireInventory extends SimpleInventory implements BlockInventory{ - use BlockInventoryTrait; - - public function __construct(Position $holder){ - $this->holder = $holder; +final class CampfireInventory extends SimpleInventory{ + public function __construct(){ parent::__construct(4); } diff --git a/src/block/inventory/CartographyTableInventory.php b/src/block/inventory/CartographyTableInventoryWindow.php similarity index 72% rename from src/block/inventory/CartographyTableInventory.php rename to src/block/inventory/CartographyTableInventoryWindow.php index 7bd9146ac..90f06edb1 100644 --- a/src/block/inventory/CartographyTableInventory.php +++ b/src/block/inventory/CartographyTableInventoryWindow.php @@ -24,14 +24,16 @@ declare(strict_types=1); namespace pocketmine\block\inventory; use pocketmine\inventory\SimpleInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -final class CartographyTableInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; +final class CartographyTableInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ - 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); } } diff --git a/src/block/inventory/ChestInventory.php b/src/block/inventory/ChestInventoryWindow.php similarity index 72% rename from src/block/inventory/ChestInventory.php rename to src/block/inventory/ChestInventoryWindow.php index b61fab57c..be61fc22b 100644 --- a/src/block/inventory/ChestInventory.php +++ b/src/block/inventory/ChestInventoryWindow.php @@ -23,7 +23,6 @@ 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; @@ -31,13 +30,7 @@ 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); - } +class ChestInventoryWindow extends AnimatedBlockInventoryWindow{ protected function getOpenSound() : Sound{ return new ChestOpenSound(); @@ -47,10 +40,9 @@ class ChestInventory extends SimpleInventory implements BlockInventory{ return new ChestCloseSound(); } - public function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - + protected function animateBlock(Position $position, bool $isOpen) : void{ //event ID is always 1 for a chest - $holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0)); + //TODO: we probably shouldn't be sending a packet directly here, but it doesn't fit anywhere into existing systems + $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); } } diff --git a/src/block/inventory/CraftingTableInventory.php b/src/block/inventory/CraftingTableInventoryWindow.php similarity index 72% rename from src/block/inventory/CraftingTableInventory.php rename to src/block/inventory/CraftingTableInventoryWindow.php index 767e8a5f4..905cd1ccc 100644 --- a/src/block/inventory/CraftingTableInventory.php +++ b/src/block/inventory/CraftingTableInventoryWindow.php @@ -24,14 +24,16 @@ declare(strict_types=1); namespace pocketmine\block\inventory; 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); } } diff --git a/src/block/inventory/DoubleChestInventory.php b/src/block/inventory/DoubleChestInventory.php index a7eb4a439..5c11d1a7a 100644 --- a/src/block/inventory/DoubleChestInventory.php +++ b/src/block/inventory/DoubleChestInventory.php @@ -24,27 +24,17 @@ declare(strict_types=1); namespace pocketmine\block\inventory; use pocketmine\inventory\BaseInventory; -use pocketmine\inventory\InventoryHolder; +use pocketmine\inventory\Inventory; 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; +final class DoubleChestInventory extends BaseInventory{ public function __construct( - private ChestInventory $left, - private ChestInventory $right + private Inventory $left, + private Inventory $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(); } @@ -85,7 +75,7 @@ class DoubleChestInventory extends BaseInventory implements BlockInventory, Inve $this->right->setContents($rightContents); } - protected function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ $leftSize = $this->left->getSize(); return $slot < $leftSize ? $this->left->getMatchingItemCount($slot, $test, $checkTags) : @@ -99,20 +89,11 @@ class DoubleChestInventory extends BaseInventory implements BlockInventory, Inve $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{ + public function getLeftSide() : Inventory{ return $this->left; } - public function getRightSide() : ChestInventory{ + public function getRightSide() : Inventory{ return $this->right; } } diff --git a/src/block/inventory/BrewingStandInventory.php b/src/block/inventory/DoubleChestInventoryWindow.php similarity index 54% rename from src/block/inventory/BrewingStandInventory.php rename to src/block/inventory/DoubleChestInventoryWindow.php index 8bab4ba97..e65312b1c 100644 --- a/src/block/inventory/BrewingStandInventory.php +++ b/src/block/inventory/DoubleChestInventoryWindow.php @@ -23,20 +23,30 @@ declare(strict_types=1); namespace pocketmine\block\inventory; -use pocketmine\inventory\SimpleInventory; +use pocketmine\inventory\Inventory; +use pocketmine\player\Player; use pocketmine\world\Position; -class BrewingStandInventory extends SimpleInventory implements BlockInventory{ - use BlockInventoryTrait; +final class DoubleChestInventoryWindow extends ChestInventoryWindow{ - 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( + Player $viewer, + Inventory $inventory, + private Position $left, + private Position $right + ){ + parent::__construct($viewer, $inventory, $this->left); + } - public function __construct(Position $holder, int $size = 5){ - $this->holder = $holder; - parent::__construct($size); + public function getLeft() : Position{ return $this->left; } + + public function getRight() : Position{ return $this->right; } + + protected function doBlockEffects(bool $isOpen) : void{ + $this->animateBlock($this->left, $isOpen); + $this->animateBlock($this->right, $isOpen); + + $this->playSound($this->left, $isOpen); + $this->playSound($this->right, $isOpen); } } diff --git a/src/block/inventory/EnchantInventory.php b/src/block/inventory/EnchantInventory.php deleted file mode 100644 index b726dbedf..000000000 --- a/src/block/inventory/EnchantInventory.php +++ /dev/null @@ -1,85 +0,0 @@ -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; - } -} diff --git a/src/block/inventory/EnchantingTableInventoryWindow.php b/src/block/inventory/EnchantingTableInventoryWindow.php new file mode 100644 index 000000000..660d5c707 --- /dev/null +++ b/src/block/inventory/EnchantingTableInventoryWindow.php @@ -0,0 +1,110 @@ + */ + private \WeakReference $listener; + + public function __construct( + Player $viewer, + Position $holder + ){ + parent::__construct($viewer, new SimpleInventory(2), $holder); + + /** @phpstan-var \WeakReference $weakThis */ + $weakThis = \WeakReference::create($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($listener); + + $this->listener = \WeakReference::create($listener); + } + + public function __destruct(){ + $listener = $this->listener->get(); + if($listener !== null){ + $this->inventory->getListeners()->remove($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; + } +} diff --git a/src/block/inventory/EnderChestInventory.php b/src/block/inventory/EnderChestInventoryWindow.php similarity index 53% rename from src/block/inventory/EnderChestInventory.php rename to src/block/inventory/EnderChestInventoryWindow.php index c1d7c5401..ad290134d 100644 --- a/src/block/inventory/EnderChestInventory.php +++ b/src/block/inventory/EnderChestInventoryWindow.php @@ -24,45 +24,30 @@ 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; - } +final class EnderChestInventoryWindow extends AnimatedBlockInventoryWindow{ - 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()); + protected function getViewerCount() : int{ + $enderChest = $this->holder->getWorld()->getTile($this->getHolder()); if(!$enderChest instanceof EnderChest){ return 0; } return $enderChest->getViewerCount(); } + private function updateViewerCount(int $amount) : void{ + $enderChest = $this->holder->getWorld()->getTile($this->getHolder()); + if($enderChest instanceof EnderChest){ + $enderChest->setViewerCount($enderChest->getViewerCount() + $amount); + } + } + protected function getOpenSound() : Sound{ return new EnderChestOpenSound(); } @@ -71,18 +56,18 @@ class EnderChestInventory extends DelegateInventory implements BlockInventory{ return new EnderChestCloseSound(); } - protected function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - + protected function animateBlock(Position $position, bool $isOpen) : void{ //event ID is always 1 for a chest - $holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0)); + $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 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); - } + public function onOpen() : void{ + parent::onOpen(); + $this->updateViewerCount(1); + } + + public function onClose() : void{ + parent::onClose(); + $this->updateViewerCount(-1); } } diff --git a/src/block/inventory/FurnaceInventory.php b/src/block/inventory/FurnaceInventoryWindow.php similarity index 71% rename from src/block/inventory/FurnaceInventory.php rename to src/block/inventory/FurnaceInventoryWindow.php index ff44d6b70..98e2ac360 100644 --- a/src/block/inventory/FurnaceInventory.php +++ b/src/block/inventory/FurnaceInventoryWindow.php @@ -24,48 +24,48 @@ declare(strict_types=1); namespace pocketmine\block\inventory; 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); } } diff --git a/src/block/inventory/BlockInventory.php b/src/block/inventory/HopperInventoryWindow.php similarity index 88% rename from src/block/inventory/BlockInventory.php rename to src/block/inventory/HopperInventoryWindow.php index 063a451a9..4ddf981c9 100644 --- a/src/block/inventory/BlockInventory.php +++ b/src/block/inventory/HopperInventoryWindow.php @@ -23,8 +23,6 @@ declare(strict_types=1); namespace pocketmine\block\inventory; -use pocketmine\world\Position; +final class HopperInventoryWindow extends BlockInventoryWindow{ -interface BlockInventory{ - public function getHolder() : Position; } diff --git a/src/block/inventory/LoomInventory.php b/src/block/inventory/LoomInventoryWindow.php similarity index 75% rename from src/block/inventory/LoomInventory.php rename to src/block/inventory/LoomInventoryWindow.php index fd34620a0..1140cdf1a 100644 --- a/src/block/inventory/LoomInventory.php +++ b/src/block/inventory/LoomInventoryWindow.php @@ -24,18 +24,20 @@ declare(strict_types=1); namespace pocketmine\block\inventory; 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); } } diff --git a/src/block/inventory/ShulkerBoxInventory.php b/src/block/inventory/ShulkerBoxInventoryWindow.php similarity index 59% rename from src/block/inventory/ShulkerBoxInventory.php rename to src/block/inventory/ShulkerBoxInventoryWindow.php index d915a9951..a6e7b0c70 100644 --- a/src/block/inventory/ShulkerBoxInventory.php +++ b/src/block/inventory/ShulkerBoxInventoryWindow.php @@ -23,10 +23,6 @@ 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; @@ -34,14 +30,7 @@ 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); - } - +final class ShulkerBoxInventoryWindow extends AnimatedBlockInventoryWindow{ protected function getOpenSound() : Sound{ return new ShulkerBoxOpenSound(); } @@ -50,18 +39,8 @@ class ShulkerBoxInventory extends SimpleInventory implements BlockInventory{ 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(); - + protected function animateBlock(Position $position, bool $isOpen) : void{ //event ID is always 1 for a chest - $holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0)); + $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); } } diff --git a/src/block/inventory/SmithingTableInventory.php b/src/block/inventory/SmithingTableInventory.php deleted file mode 100644 index 2f67ac9d2..000000000 --- a/src/block/inventory/SmithingTableInventory.php +++ /dev/null @@ -1,37 +0,0 @@ -holder = $holder; - parent::__construct(3); - } -} diff --git a/src/block/inventory/HopperInventory.php b/src/block/inventory/SmithingTableInventoryWindow.php similarity index 73% rename from src/block/inventory/HopperInventory.php rename to src/block/inventory/SmithingTableInventoryWindow.php index a20e9ae1a..1d5bcf7dd 100644 --- a/src/block/inventory/HopperInventory.php +++ b/src/block/inventory/SmithingTableInventoryWindow.php @@ -24,13 +24,12 @@ declare(strict_types=1); namespace pocketmine\block\inventory; 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); } } diff --git a/src/block/inventory/StonecutterInventory.php b/src/block/inventory/StonecutterInventoryWindow.php similarity index 73% rename from src/block/inventory/StonecutterInventory.php rename to src/block/inventory/StonecutterInventoryWindow.php index 4ed644ff2..c37b3bb94 100644 --- a/src/block/inventory/StonecutterInventory.php +++ b/src/block/inventory/StonecutterInventoryWindow.php @@ -24,16 +24,14 @@ declare(strict_types=1); namespace pocketmine\block\inventory; 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); } } diff --git a/src/block/tile/Barrel.php b/src/block/tile/Barrel.php index a7f353214..f642d6cac 100644 --- a/src/block/tile/Barrel.php +++ b/src/block/tile/Barrel.php @@ -23,7 +23,8 @@ declare(strict_types=1); namespace pocketmine\block\tile; -use pocketmine\block\inventory\BarrelInventory; +use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\World; @@ -32,11 +33,11 @@ class Barrel extends Spawnable implements Container, Nameable{ use NameableTrait; use ContainerTrait; - protected BarrelInventory $inventory; + protected Inventory $inventory; public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new BarrelInventory($this->position); + $this->inventory = new SimpleInventory(27); } public function readSaveData(CompoundTag $nbt) : void{ @@ -56,11 +57,11 @@ class Barrel extends Spawnable implements Container, Nameable{ } } - public function getInventory() : BarrelInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : BarrelInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/tile/BrewingStand.php b/src/block/tile/BrewingStand.php index c3a331e6c..0e4289a84 100644 --- a/src/block/tile/BrewingStand.php +++ b/src/block/tile/BrewingStand.php @@ -23,12 +23,13 @@ declare(strict_types=1); namespace pocketmine\block\tile; -use pocketmine\block\inventory\BrewingStandInventory; +use pocketmine\block\inventory\BrewingStandInventoryWindow; use pocketmine\crafting\BrewingRecipe; use pocketmine\event\block\BrewingFuelUseEvent; use pocketmine\event\block\BrewItemEvent; use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\item\Item; use pocketmine\item\VanillaItems; use pocketmine\math\Vector3; @@ -54,7 +55,7 @@ class BrewingStand extends Spawnable implements Container, Nameable{ private const TAG_REMAINING_FUEL_TIME = "Fuel"; //TAG_Byte private const TAG_REMAINING_FUEL_TIME_PE = "FuelAmount"; //TAG_Short - private BrewingStandInventory $inventory; + private Inventory $inventory; private int $brewTime = 0; private int $maxFuelTime = 0; @@ -62,7 +63,7 @@ class BrewingStand extends Spawnable implements Container, Nameable{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new BrewingStandInventory($this->position); + $this->inventory = new SimpleInventory(5); $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange(static function(Inventory $unused) use ($world, $pos) : void{ $world->scheduleDelayedBlockUpdate($pos, 1); })); @@ -112,11 +113,11 @@ class BrewingStand extends Spawnable implements Container, Nameable{ } } - public function getInventory() : BrewingStandInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : BrewingStandInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } @@ -132,7 +133,7 @@ class BrewingStand extends Spawnable implements Container, Nameable{ } $item->pop(); - $this->inventory->setItem(BrewingStandInventory::SLOT_FUEL, $item); + $this->inventory->setItem(BrewingStandInventoryWindow::SLOT_FUEL, $item); $this->maxFuelTime = $this->remainingFuelTime = $ev->getFuelTime(); } @@ -142,14 +143,14 @@ class BrewingStand extends Spawnable implements Container, Nameable{ * @phpstan-return array */ private function getBrewableRecipes() : array{ - $ingredient = $this->inventory->getItem(BrewingStandInventory::SLOT_INGREDIENT); + $ingredient = $this->inventory->getItem(BrewingStandInventoryWindow::SLOT_INGREDIENT); if($ingredient->isNull()){ return []; } $recipes = []; $craftingManager = $this->position->getWorld()->getServer()->getCraftingManager(); - foreach([BrewingStandInventory::SLOT_BOTTLE_LEFT, BrewingStandInventory::SLOT_BOTTLE_MIDDLE, BrewingStandInventory::SLOT_BOTTLE_RIGHT] as $slot){ + foreach([BrewingStandInventoryWindow::SLOT_BOTTLE_LEFT, BrewingStandInventoryWindow::SLOT_BOTTLE_MIDDLE, BrewingStandInventoryWindow::SLOT_BOTTLE_RIGHT] as $slot){ $input = $this->inventory->getItem($slot); if($input->isNull()){ continue; @@ -176,8 +177,8 @@ class BrewingStand extends Spawnable implements Container, Nameable{ $ret = false; - $fuel = $this->inventory->getItem(BrewingStandInventory::SLOT_FUEL); - $ingredient = $this->inventory->getItem(BrewingStandInventory::SLOT_INGREDIENT); + $fuel = $this->inventory->getItem(BrewingStandInventoryWindow::SLOT_FUEL); + $ingredient = $this->inventory->getItem(BrewingStandInventoryWindow::SLOT_INGREDIENT); $recipes = $this->getBrewableRecipes(); $canBrew = count($recipes) !== 0; @@ -219,7 +220,7 @@ class BrewingStand extends Spawnable implements Container, Nameable{ } $ingredient->pop(); - $this->inventory->setItem(BrewingStandInventory::SLOT_INGREDIENT, $ingredient); + $this->inventory->setItem(BrewingStandInventoryWindow::SLOT_INGREDIENT, $ingredient); $this->brewTime = 0; }else{ diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php index ad4a193d7..89e42fa06 100644 --- a/src/block/tile/Campfire.php +++ b/src/block/tile/Campfire.php @@ -53,7 +53,7 @@ class Campfire extends Spawnable implements Container{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new CampfireInventory($this->position); + $this->inventory = new CampfireInventory(); $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange( static function(Inventory $unused) use ($world, $pos) : void{ $block = $world->getBlock($pos); diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index 4f97eed23..35e9425e1 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block\tile; -use pocketmine\block\inventory\ChestInventory; use pocketmine\block\inventory\DoubleChestInventory; +use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; @@ -44,7 +45,7 @@ class Chest extends Spawnable implements Container, Nameable{ public const TAG_PAIRZ = "pairz"; public const TAG_PAIR_LEAD = "pairlead"; - protected ChestInventory $inventory; + protected Inventory $inventory; protected ?DoubleChestInventory $doubleInventory = null; private ?int $pairX = null; @@ -52,7 +53,7 @@ class Chest extends Spawnable implements Container, Nameable{ public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new ChestInventory($this->position); + $this->inventory = new SimpleInventory(27); } public function readSaveData(CompoundTag $nbt) : void{ @@ -114,14 +115,14 @@ class Chest extends Spawnable implements Container, Nameable{ $this->containerTraitBlockDestroyedHook(); } - public function getInventory() : ChestInventory|DoubleChestInventory{ + public function getInventory() : Inventory|DoubleChestInventory{ if($this->isPaired() && $this->doubleInventory === null){ $this->checkPairing(); } return $this->doubleInventory instanceof DoubleChestInventory ? $this->doubleInventory : $this->inventory; } - public function getRealInventory() : ChestInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/tile/Furnace.php b/src/block/tile/Furnace.php index a706a827e..f744e6b41 100644 --- a/src/block/tile/Furnace.php +++ b/src/block/tile/Furnace.php @@ -24,13 +24,14 @@ declare(strict_types=1); namespace pocketmine\block\tile; use pocketmine\block\Furnace as BlockFurnace; -use pocketmine\block\inventory\FurnaceInventory; +use pocketmine\block\inventory\FurnaceInventoryWindow; use pocketmine\crafting\FurnaceRecipe; use pocketmine\crafting\FurnaceType; use pocketmine\event\inventory\FurnaceBurnEvent; use pocketmine\event\inventory\FurnaceSmeltEvent; use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; @@ -48,14 +49,14 @@ abstract class Furnace extends Spawnable implements Container, Nameable{ public const TAG_COOK_TIME = "CookTime"; public const TAG_MAX_TIME = "MaxTime"; - protected FurnaceInventory $inventory; + protected Inventory $inventory; private int $remainingFuelTime = 0; private int $cookTime = 0; private int $maxFuelTime = 0; public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new FurnaceInventory($this->position, $this->getFurnaceType()); + $this->inventory = new SimpleInventory(3); $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange( static function(Inventory $unused) use ($world, $pos) : void{ $world->scheduleDelayedBlockUpdate($pos, 1); @@ -104,11 +105,11 @@ abstract class Furnace extends Spawnable implements Container, Nameable{ } } - public function getInventory() : FurnaceInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : FurnaceInventory{ + public function getRealInventory() : Inventory{ return $this->getInventory(); } @@ -123,7 +124,7 @@ abstract class Furnace extends Spawnable implements Container, Nameable{ $this->onStartSmelting(); if($this->remainingFuelTime > 0 && $ev->isBurning()){ - $this->inventory->setFuel($fuel->getFuelResidue()); + $this->inventory->setItem(FurnaceInventoryWindow::SLOT_FUEL, $fuel->getFuelResidue()); } } @@ -159,9 +160,9 @@ abstract class Furnace extends Spawnable implements Container, Nameable{ $ret = false; - $fuel = $this->inventory->getFuel(); - $raw = $this->inventory->getSmelting(); - $product = $this->inventory->getResult(); + $fuel = $this->inventory->getItem(FurnaceInventoryWindow::SLOT_FUEL); + $raw = $this->inventory->getItem(FurnaceInventoryWindow::SLOT_INPUT); + $product = $this->inventory->getItem(FurnaceInventoryWindow::SLOT_RESULT); $furnaceType = $this->getFurnaceType(); $smelt = $this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($furnaceType)->match($raw); @@ -184,9 +185,9 @@ abstract class Furnace extends Spawnable implements Container, Nameable{ $ev->call(); if(!$ev->isCancelled()){ - $this->inventory->setResult($ev->getResult()); + $this->inventory->setItem(FurnaceInventoryWindow::SLOT_RESULT, $ev->getResult()); $raw->pop(); - $this->inventory->setSmelting($raw); + $this->inventory->setItem(FurnaceInventoryWindow::SLOT_INPUT, $raw); } $this->cookTime -= $furnaceType->getCookDurationTicks(); diff --git a/src/block/tile/Hopper.php b/src/block/tile/Hopper.php index 5c39bc2bd..8e43fa15b 100644 --- a/src/block/tile/Hopper.php +++ b/src/block/tile/Hopper.php @@ -23,7 +23,8 @@ declare(strict_types=1); namespace pocketmine\block\tile; -use pocketmine\block\inventory\HopperInventory; +use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\World; @@ -35,12 +36,12 @@ class Hopper extends Spawnable implements Container, Nameable{ private const TAG_TRANSFER_COOLDOWN = "TransferCooldown"; - private HopperInventory $inventory; + private Inventory $inventory; private int $transferCooldown = 0; public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new HopperInventory($this->position); + $this->inventory = new SimpleInventory(5); } public function readSaveData(CompoundTag $nbt) : void{ @@ -69,11 +70,11 @@ class Hopper extends Spawnable implements Container, Nameable{ return "Hopper"; } - public function getInventory() : HopperInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : HopperInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } } diff --git a/src/block/tile/ShulkerBox.php b/src/block/tile/ShulkerBox.php index a30b75c4e..1c5b74a08 100644 --- a/src/block/tile/ShulkerBox.php +++ b/src/block/tile/ShulkerBox.php @@ -23,8 +23,13 @@ declare(strict_types=1); namespace pocketmine\block\tile; -use pocketmine\block\inventory\ShulkerBoxInventory; +use pocketmine\block\BlockTypeIds; +use pocketmine\inventory\Inventory; +use pocketmine\inventory\SimpleInventory; +use pocketmine\inventory\transaction\action\validator\CallbackSlotValidator; +use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; +use pocketmine\item\ItemTypeIds; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; @@ -40,11 +45,20 @@ class ShulkerBox extends Spawnable implements Container, Nameable{ protected int $facing = Facing::NORTH; - protected ShulkerBoxInventory $inventory; + protected Inventory $inventory; public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); - $this->inventory = new ShulkerBoxInventory($this->position); + $this->inventory = new SimpleInventory(27); + + $this->inventory->getSlotValidators()->add(new CallbackSlotValidator(static function(Inventory $_, Item $item) : ?TransactionValidationException{ //remaining params not needed + $blockTypeId = ItemTypeIds::toBlockTypeId($item->getTypeId()); + if($blockTypeId === BlockTypeIds::SHULKER_BOX || $blockTypeId === BlockTypeIds::DYED_SHULKER_BOX){ + return new TransactionValidationException("Shulker box inventory cannot contain shulker boxes"); + } + + return null; + })); } public function readSaveData(CompoundTag $nbt) : void{ @@ -93,11 +107,11 @@ class ShulkerBox extends Spawnable implements Container, Nameable{ $this->facing = $facing; } - public function getInventory() : ShulkerBoxInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : ShulkerBoxInventory{ + public function getRealInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/utils/BrewingStandSlot.php b/src/block/utils/BrewingStandSlot.php index c7d74d8da..edf8374fa 100644 --- a/src/block/utils/BrewingStandSlot.php +++ b/src/block/utils/BrewingStandSlot.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block\utils; -use pocketmine\block\inventory\BrewingStandInventory; +use pocketmine\block\inventory\BrewingStandInventoryWindow; enum BrewingStandSlot{ case EAST; @@ -35,9 +35,9 @@ enum BrewingStandSlot{ */ public function getSlotNumber() : int{ return match($this){ - self::EAST => BrewingStandInventory::SLOT_BOTTLE_LEFT, - self::NORTHWEST => BrewingStandInventory::SLOT_BOTTLE_MIDDLE, - self::SOUTHWEST => BrewingStandInventory::SLOT_BOTTLE_RIGHT + self::EAST => BrewingStandInventoryWindow::SLOT_BOTTLE_LEFT, + self::NORTHWEST => BrewingStandInventoryWindow::SLOT_BOTTLE_MIDDLE, + self::SOUTHWEST => BrewingStandInventoryWindow::SLOT_BOTTLE_RIGHT }; } } diff --git a/src/crafting/CraftingGrid.php b/src/crafting/CraftingGrid.php index a41b5e3a7..47cca94ba 100644 --- a/src/crafting/CraftingGrid.php +++ b/src/crafting/CraftingGrid.php @@ -29,7 +29,7 @@ use function max; use function min; use const PHP_INT_MAX; -abstract class CraftingGrid extends SimpleInventory{ +class CraftingGrid extends SimpleInventory{ public const SIZE_SMALL = 2; public const SIZE_BIG = 3; diff --git a/src/entity/Human.php b/src/entity/Human.php index 8b446660a..5f7f57702 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -35,9 +35,7 @@ use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Hotbar; use pocketmine\inventory\Inventory; use pocketmine\inventory\InventoryHolder; -use pocketmine\inventory\PlayerEnderInventory; -use pocketmine\inventory\PlayerInventory; -use pocketmine\inventory\PlayerOffHandInventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\item\enchantment\EnchantingHelper; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; @@ -102,9 +100,9 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ public function getNetworkTypeId() : string{ return EntityIds::PLAYER; } protected Hotbar $hotbar; - protected PlayerInventory $inventory; - protected PlayerOffHandInventory $offHandInventory; - protected PlayerEnderInventory $enderInventory; + protected Inventory $inventory; + protected Inventory $offHandInventory; + protected Inventory $enderInventory; protected UuidInterface $uuid; @@ -234,13 +232,13 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ return $this->hotbar; } - public function getInventory() : PlayerInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getOffHandInventory() : PlayerOffHandInventory{ return $this->offHandInventory; } + public function getOffHandInventory() : Inventory{ return $this->offHandInventory; } - public function getEnderInventory() : PlayerEnderInventory{ + public function getEnderInventory() : Inventory{ return $this->enderInventory; } @@ -271,7 +269,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ $this->hungerManager = new HungerManager($this); $this->xpManager = new ExperienceManager($this); - $this->inventory = new PlayerInventory($this); + $this->inventory = new SimpleInventory(36); $this->hotbar = new Hotbar($this->inventory); $syncHeldItem = fn() => NetworkBroadcastUtils::broadcastEntityEvent( @@ -290,8 +288,8 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ } } )); - $this->offHandInventory = new PlayerOffHandInventory($this); - $this->enderInventory = new PlayerEnderInventory($this); + $this->offHandInventory = new SimpleInventory(1); + $this->enderInventory = new SimpleInventory(27); $this->initHumanData($nbt); $inventoryTag = $nbt->getListTag(self::TAG_INVENTORY); @@ -335,6 +333,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ } $this->hotbar->setSelectedIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0)); + //TODO: cyclic reference $this->hotbar->getSelectedIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent( $this->getViewers(), fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this) @@ -546,9 +545,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ protected function destroyCycles() : void{ unset( - $this->inventory, - $this->offHandInventory, - $this->enderInventory, $this->hungerManager, $this->xpManager ); diff --git a/src/entity/Living.php b/src/entity/Living.php index 81f46424f..faed09ef1 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -145,7 +145,7 @@ abstract class Living extends Entity{ $this->effectManager->getEffectAddHooks()->add(function() : void{ $this->networkPropertiesDirty = true; }); $this->effectManager->getEffectRemoveHooks()->add(function() : void{ $this->networkPropertiesDirty = true; }); - $this->armorInventory = new ArmorInventory($this); + $this->armorInventory = new ArmorInventory(); //TODO: load/save armor inventory contents $this->armorInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent( $this->getViewers(), @@ -928,7 +928,6 @@ abstract class Living extends Entity{ protected function destroyCycles() : void{ unset( - $this->armorInventory, $this->effectManager ); parent::destroyCycles(); diff --git a/src/event/inventory/InventoryCloseEvent.php b/src/event/inventory/InventoryCloseEvent.php index 4401a7126..be5415e27 100644 --- a/src/event/inventory/InventoryCloseEvent.php +++ b/src/event/inventory/InventoryCloseEvent.php @@ -23,12 +23,12 @@ declare(strict_types=1); namespace pocketmine\event\inventory; -use pocketmine\inventory\Inventory; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; class InventoryCloseEvent extends InventoryEvent{ public function __construct( - Inventory $inventory, + InventoryWindow $inventory, private Player $who ){ parent::__construct($inventory); diff --git a/src/event/inventory/InventoryEvent.php b/src/event/inventory/InventoryEvent.php index 420294679..30250505b 100644 --- a/src/event/inventory/InventoryEvent.php +++ b/src/event/inventory/InventoryEvent.php @@ -27,15 +27,15 @@ declare(strict_types=1); namespace pocketmine\event\inventory; use pocketmine\event\Event; -use pocketmine\inventory\Inventory; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; abstract class InventoryEvent extends Event{ public function __construct( - protected Inventory $inventory + protected InventoryWindow $inventory ){} - public function getInventory() : Inventory{ + public function getInventory() : InventoryWindow{ return $this->inventory; } @@ -43,6 +43,6 @@ abstract class InventoryEvent extends Event{ * @return Player[] */ public function getViewers() : array{ - return $this->inventory->getViewers(); + return $this->inventory->getInventory()->getViewers(); } } diff --git a/src/event/inventory/InventoryOpenEvent.php b/src/event/inventory/InventoryOpenEvent.php index f45056bf4..44275b671 100644 --- a/src/event/inventory/InventoryOpenEvent.php +++ b/src/event/inventory/InventoryOpenEvent.php @@ -25,14 +25,14 @@ namespace pocketmine\event\inventory; use pocketmine\event\Cancellable; use pocketmine\event\CancellableTrait; -use pocketmine\inventory\Inventory; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; class InventoryOpenEvent extends InventoryEvent implements Cancellable{ use CancellableTrait; public function __construct( - Inventory $inventory, + InventoryWindow $inventory, private Player $who ){ parent::__construct($inventory); diff --git a/src/event/player/PlayerEnchantingOptionsRequestEvent.php b/src/event/player/PlayerEnchantingOptionsRequestEvent.php index 833185f76..e29183520 100644 --- a/src/event/player/PlayerEnchantingOptionsRequestEvent.php +++ b/src/event/player/PlayerEnchantingOptionsRequestEvent.php @@ -23,10 +23,9 @@ declare(strict_types=1); namespace pocketmine\event\player; -use pocketmine\block\inventory\EnchantInventory; +use pocketmine\block\inventory\EnchantingTableInventoryWindow; use pocketmine\event\Cancellable; use pocketmine\event\CancellableTrait; -use pocketmine\event\Event; use pocketmine\item\enchantment\EnchantingOption; use pocketmine\player\Player; use pocketmine\utils\Utils; @@ -44,13 +43,13 @@ class PlayerEnchantingOptionsRequestEvent extends PlayerEvent implements Cancell */ public function __construct( Player $player, - private readonly EnchantInventory $inventory, + private readonly EnchantingTableInventoryWindow $inventory, private array $options ){ $this->player = $player; } - public function getInventory() : EnchantInventory{ + public function getInventory() : EnchantingTableInventoryWindow{ return $this->inventory; } diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index 8591cc65b..6985f460a 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace pocketmine\inventory; use pocketmine\block\BlockTypeIds; -use pocketmine\entity\Living; use pocketmine\inventory\transaction\action\validator\CallbackSlotValidator; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Armor; @@ -37,18 +36,12 @@ class ArmorInventory extends SimpleInventory{ public const SLOT_LEGS = 2; public const SLOT_FEET = 3; - public function __construct( - protected Living $holder - ){ + public function __construct(){ parent::__construct(4); $this->validators->add(new CallbackSlotValidator(self::validate(...))); } - public function getHolder() : Living{ - return $this->holder; - } - public function getHelmet() : Item{ return $this->getItem(self::SLOT_HEAD); } diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 14ed270c5..e307b5b85 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -107,15 +107,6 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{ $this->onContentChange($oldContents); } - /** - * Helper for utility functions which search the inventory. - * TODO: make this abstract instead of providing a slow default implementation (BC break) - */ - protected function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ - $item = $this->getItem($slot); - return $item->equals($test, true, $checkTags) ? $item->getCount() : 0; - } - public function contains(Item $item) : bool{ $count = max(1, $item->getCount()); $checkTags = $item->hasNamedTag(); @@ -345,7 +336,7 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{ */ public function removeAllViewers() : void{ foreach($this->viewers as $hash => $viewer){ - if($viewer->getCurrentWindow() === $this){ //this might not be the case for the player's own inventory + if($viewer->getCurrentWindow()?->getInventory() === $this){ //this might not be the case for the player's own inventory $viewer->removeCurrentWindow(); } unset($this->viewers[$hash]); diff --git a/src/inventory/DelegateInventory.php b/src/inventory/DelegateInventory.php index a211732cf..5bf9b908a 100644 --- a/src/inventory/DelegateInventory.php +++ b/src/inventory/DelegateInventory.php @@ -85,6 +85,10 @@ class DelegateInventory extends BaseInventory{ $this->backingInventory->setContents($items); } + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ + return $this->backingInventory->getMatchingItemCount($slot, $test, $checkTags); + } + public function isSlotEmpty(int $index) : bool{ return $this->backingInventory->isSlotEmpty($index); } diff --git a/src/inventory/Inventory.php b/src/inventory/Inventory.php index 5c81d7d9c..de460ca29 100644 --- a/src/inventory/Inventory.php +++ b/src/inventory/Inventory.php @@ -98,6 +98,13 @@ interface Inventory{ */ public function getAddableItemQuantity(Item $item) : int; + /** + * Returns the number of items in the inventory that match the given item. + * + * @param bool $checkTags If true, the NBT of the items will also be checked and must be the same to be counted. + */ + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int; + /** * Returns whether the total amount of matching items is at least the stack size of the given item. Multiple stacks * of the same item are added together. @@ -179,6 +186,11 @@ interface Inventory{ */ public function getViewers() : array; + /** + * Tells all Players viewing this inventory to stop viewing it and discard associated windows. + */ + public function removeAllViewers() : void; + /** * Called when a player opens this inventory. */ diff --git a/src/inventory/PlayerCraftingInventory.php b/src/inventory/PlayerCraftingInventory.php deleted file mode 100644 index 75752c9e7..000000000 --- a/src/inventory/PlayerCraftingInventory.php +++ /dev/null @@ -1,36 +0,0 @@ -holder; } -} diff --git a/src/inventory/PlayerCursorInventory.php b/src/inventory/PlayerCursorInventory.php deleted file mode 100644 index 30f9e3aec..000000000 --- a/src/inventory/PlayerCursorInventory.php +++ /dev/null @@ -1,38 +0,0 @@ -holder; - } -} diff --git a/src/inventory/PlayerEnderInventory.php b/src/inventory/PlayerEnderInventory.php deleted file mode 100644 index c10c42b12..000000000 --- a/src/inventory/PlayerEnderInventory.php +++ /dev/null @@ -1,37 +0,0 @@ -holder; } -} diff --git a/src/inventory/PlayerInventory.php b/src/inventory/PlayerInventory.php deleted file mode 100644 index a2e9e9252..000000000 --- a/src/inventory/PlayerInventory.php +++ /dev/null @@ -1,40 +0,0 @@ -holder = $player; - parent::__construct(36); - } - - public function getHolder() : Human{ - return $this->holder; - } -} diff --git a/src/inventory/PlayerOffHandInventory.php b/src/inventory/PlayerOffHandInventory.php deleted file mode 100644 index 127b09f98..000000000 --- a/src/inventory/PlayerOffHandInventory.php +++ /dev/null @@ -1,37 +0,0 @@ -holder = $player; - parent::__construct(1); - } - - public function getHolder() : Human{ return $this->holder; } -} diff --git a/src/inventory/SimpleInventory.php b/src/inventory/SimpleInventory.php index 4b44326fa..a6a339322 100644 --- a/src/inventory/SimpleInventory.php +++ b/src/inventory/SimpleInventory.php @@ -84,7 +84,7 @@ class SimpleInventory extends BaseInventory{ } } - protected function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ $slotItem = $this->slots[$slot]; return $slotItem !== null && $slotItem->equals($test, true, $checkTags) ? $slotItem->getCount() : 0; } diff --git a/src/inventory/transaction/InventoryTransaction.php b/src/inventory/transaction/InventoryTransaction.php index 2ca00f910..a994a156f 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -28,6 +28,7 @@ use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\InventoryAction; use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\item\Item; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; use function array_keys; use function array_values; @@ -57,8 +58,8 @@ use function spl_object_id; class InventoryTransaction{ protected bool $hasExecuted = false; - /** @var Inventory[] */ - protected array $inventories = []; + /** @var InventoryWindow[] */ + protected array $inventoryWindows = []; /** @var InventoryAction[] */ protected array $actions = []; @@ -80,10 +81,10 @@ class InventoryTransaction{ } /** - * @return Inventory[] + * @return InventoryWindow[] */ - public function getInventories() : array{ - return $this->inventories; + public function getInventoryWindows() : array{ + return $this->inventoryWindows; } /** @@ -124,9 +125,9 @@ class InventoryTransaction{ * @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions * involving inventories. */ - public function addInventory(Inventory $inventory) : void{ - if(!isset($this->inventories[$hash = spl_object_id($inventory)])){ - $this->inventories[$hash] = $inventory; + public function addInventoryWindow(InventoryWindow $inventoryWindow) : void{ + if(!isset($this->inventoryWindows[$hash = spl_object_id($inventoryWindow)])){ + $this->inventoryWindows[$hash] = $inventoryWindow; } } @@ -190,15 +191,15 @@ class InventoryTransaction{ protected function squashDuplicateSlotChanges() : void{ /** @var SlotChangeAction[][] $slotChanges */ $slotChanges = []; - /** @var Inventory[] $inventories */ + /** @var InventoryWindow[] $inventories */ $inventories = []; /** @var int[] $slots */ $slots = []; foreach($this->actions as $key => $action){ if($action instanceof SlotChangeAction){ - $slotChanges[$h = (spl_object_hash($action->getInventory()) . "@" . $action->getSlot())][] = $action; - $inventories[$h] = $action->getInventory(); + $slotChanges[$h = (spl_object_hash($action->getInventoryWindow()) . "@" . $action->getSlot())][] = $action; + $inventories[$h] = $action->getInventoryWindow(); $slots[$h] = $action->getSlot(); } } @@ -208,10 +209,11 @@ class InventoryTransaction{ continue; } - $inventory = $inventories[$hash]; + $window = $inventories[$hash]; + $inventory = $window->getInventory(); $slot = $slots[$hash]; if(!$inventory->slotExists($slot)){ //this can get hit for crafting tables because the validation happens after this compaction - throw new TransactionValidationException("Slot $slot does not exist in inventory " . get_class($inventory)); + throw new TransactionValidationException("Slot $slot does not exist in inventory window " . get_class($window)); } $sourceItem = $inventory->getItem($slot); @@ -226,7 +228,7 @@ class InventoryTransaction{ if(!$targetItem->equalsExact($sourceItem)){ //sometimes we get actions on the crafting grid whose source and target items are the same, so dump them - $this->addAction(new SlotChangeAction($inventory, $slot, $sourceItem, $targetItem)); + $this->addAction(new SlotChangeAction($window, $slot, $sourceItem, $targetItem)); } } } diff --git a/src/inventory/transaction/TransactionBuilderInventory.php b/src/inventory/transaction/SlotChangeActionBuilder.php similarity index 70% rename from src/inventory/transaction/TransactionBuilderInventory.php rename to src/inventory/transaction/SlotChangeActionBuilder.php index 95b6c4a14..90fe2111a 100644 --- a/src/inventory/transaction/TransactionBuilderInventory.php +++ b/src/inventory/transaction/SlotChangeActionBuilder.php @@ -28,6 +28,7 @@ use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\item\Item; use pocketmine\item\VanillaItems; +use pocketmine\player\InventoryWindow; /** * This class facilitates generating SlotChangeActions to build an inventory transaction. @@ -35,7 +36,7 @@ use pocketmine\item\VanillaItems; * This allows you to use the normal Inventory API methods like addItem() and so on to build a transaction, without * modifying the original inventory. */ -final class TransactionBuilderInventory extends BaseInventory{ +final class SlotChangeActionBuilder extends BaseInventory{ /** * @var \SplFixedArray|(Item|null)[] @@ -44,14 +45,14 @@ final class TransactionBuilderInventory extends BaseInventory{ private \SplFixedArray $changedSlots; public function __construct( - private Inventory $actualInventory + private InventoryWindow $inventoryWindow ){ parent::__construct(); - $this->changedSlots = new \SplFixedArray($this->actualInventory->getSize()); + $this->changedSlots = new \SplFixedArray($this->inventoryWindow->getInventory()->getSize()); } - public function getActualInventory() : Inventory{ - return $this->actualInventory; + public function getInventoryWindow() : InventoryWindow{ + return $this->inventoryWindow; } protected function internalSetContents(array $items) : void{ @@ -65,21 +66,21 @@ final class TransactionBuilderInventory extends BaseInventory{ } protected function internalSetItem(int $index, Item $item) : void{ - if(!$item->equalsExact($this->actualInventory->getItem($index))){ + if(!$item->equalsExact($this->inventoryWindow->getInventory()->getItem($index))){ $this->changedSlots[$index] = $item->isNull() ? VanillaItems::AIR() : clone $item; } } public function getSize() : int{ - return $this->actualInventory->getSize(); + return $this->inventoryWindow->getInventory()->getSize(); } public function getItem(int $index) : Item{ - return $this->changedSlots[$index] !== null ? clone $this->changedSlots[$index] : $this->actualInventory->getItem($index); + return $this->changedSlots[$index] !== null ? clone $this->changedSlots[$index] : $this->inventoryWindow->getInventory()->getItem($index); } public function getContents(bool $includeEmpty = false) : array{ - $contents = $this->actualInventory->getContents($includeEmpty); + $contents = $this->inventoryWindow->getInventory()->getContents($includeEmpty); foreach($this->changedSlots as $index => $item){ if($item !== null){ if($includeEmpty || !$item->isNull()){ @@ -92,6 +93,14 @@ final class TransactionBuilderInventory extends BaseInventory{ return $contents; } + public function getMatchingItemCount(int $slot, Item $test, bool $checkTags) : int{ + $slotItem = $this->changedSlots[$slot] ?? null; + if($slotItem !== null){ + return $slotItem->equals($test, true, $checkTags) ? $slotItem->getCount() : 0; + } + return $this->inventoryWindow->getInventory()->getMatchingItemCount($slot, $test, $checkTags); + } + /** * @return SlotChangeAction[] */ @@ -99,9 +108,9 @@ final class TransactionBuilderInventory extends BaseInventory{ $result = []; foreach($this->changedSlots as $index => $newItem){ if($newItem !== null){ - $oldItem = $this->actualInventory->getItem($index); + $oldItem = $this->inventoryWindow->getInventory()->getItem($index); if(!$newItem->equalsExact($oldItem)){ - $result[] = new SlotChangeAction($this->actualInventory, $index, $oldItem, $newItem); + $result[] = new SlotChangeAction($this->inventoryWindow, $index, $oldItem, $newItem); } } } diff --git a/src/inventory/transaction/TransactionBuilder.php b/src/inventory/transaction/TransactionBuilder.php index f56b2aaa1..6232a44a4 100644 --- a/src/inventory/transaction/TransactionBuilder.php +++ b/src/inventory/transaction/TransactionBuilder.php @@ -23,13 +23,13 @@ declare(strict_types=1); namespace pocketmine\inventory\transaction; -use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\InventoryAction; +use pocketmine\player\InventoryWindow; use function spl_object_id; final class TransactionBuilder{ - /** @var TransactionBuilderInventory[] */ + /** @var SlotChangeActionBuilder[] */ private array $inventories = []; /** @var InventoryAction[] */ @@ -39,9 +39,9 @@ final class TransactionBuilder{ $this->extraActions[spl_object_id($action)] = $action; } - public function getInventory(Inventory $inventory) : TransactionBuilderInventory{ + public function getActionBuilder(InventoryWindow $inventory) : SlotChangeActionBuilder{ $id = spl_object_id($inventory); - return $this->inventories[$id] ??= new TransactionBuilderInventory($inventory); + return $this->inventories[$id] ??= new SlotChangeActionBuilder($inventory); } /** diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 68c3dba1b..2be051006 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -28,6 +28,7 @@ use pocketmine\inventory\SlotValidatedInventory; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; /** @@ -35,7 +36,7 @@ use pocketmine\player\Player; */ class SlotChangeAction extends InventoryAction{ public function __construct( - protected Inventory $inventory, + protected InventoryWindow $inventoryWindow, private int $inventorySlot, Item $sourceItem, Item $targetItem @@ -44,10 +45,10 @@ class SlotChangeAction extends InventoryAction{ } /** - * Returns the inventory involved in this action. + * Returns the inventory window involved in this action. */ - public function getInventory() : Inventory{ - return $this->inventory; + public function getInventoryWindow() : InventoryWindow{ + return $this->inventoryWindow; } /** @@ -63,21 +64,22 @@ class SlotChangeAction extends InventoryAction{ * @throws TransactionValidationException */ public function validate(Player $source) : void{ - if(!$this->inventory->slotExists($this->inventorySlot)){ + $inventory = $this->inventoryWindow->getInventory(); + if(!$inventory->slotExists($this->inventorySlot)){ throw new TransactionValidationException("Slot does not exist"); } - if(!$this->inventory->getItem($this->inventorySlot)->equalsExact($this->sourceItem)){ + if(!$inventory->getItem($this->inventorySlot)->equalsExact($this->sourceItem)){ throw new TransactionValidationException("Slot does not contain expected original item"); } if($this->targetItem->getCount() > $this->targetItem->getMaxStackSize()){ throw new TransactionValidationException("Target item exceeds item type max stack size"); } - if($this->targetItem->getCount() > $this->inventory->getMaxStackSize()){ + if($this->targetItem->getCount() > $inventory->getMaxStackSize()){ throw new TransactionValidationException("Target item exceeds inventory max stack size"); } - if($this->inventory instanceof SlotValidatedInventory && !$this->targetItem->isNull()){ - foreach($this->inventory->getSlotValidators() as $validator){ - $ret = $validator->validate($this->inventory, $this->targetItem, $this->inventorySlot); + if($inventory instanceof SlotValidatedInventory && !$this->targetItem->isNull()){ + foreach($inventory->getSlotValidators() as $validator){ + $ret = $validator->validate($inventory, $this->targetItem, $this->inventorySlot); if($ret !== null){ throw new TransactionValidationException("Target item is not accepted by the inventory at slot #" . $this->inventorySlot . ": " . $ret->getMessage(), 0, $ret); } @@ -89,13 +91,13 @@ class SlotChangeAction extends InventoryAction{ * Adds this action's target inventory to the transaction's inventory list. */ public function onAddToTransaction(InventoryTransaction $transaction) : void{ - $transaction->addInventory($this->inventory); + $transaction->addInventoryWindow($this->inventoryWindow); } /** * Sets the item into the target inventory. */ public function execute(Player $source) : void{ - $this->inventory->setItem($this->inventorySlot, $this->targetItem); + $this->inventoryWindow->getInventory()->setItem($this->inventorySlot, $this->targetItem); } } diff --git a/src/network/mcpe/ComplexInventoryMapEntry.php b/src/network/mcpe/ComplexWindowMapEntry.php similarity index 88% rename from src/network/mcpe/ComplexInventoryMapEntry.php rename to src/network/mcpe/ComplexWindowMapEntry.php index dfd3e999a..fa457e14f 100644 --- a/src/network/mcpe/ComplexInventoryMapEntry.php +++ b/src/network/mcpe/ComplexWindowMapEntry.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; -use pocketmine\inventory\Inventory; +use pocketmine\player\InventoryWindow; -final class ComplexInventoryMapEntry{ +final class ComplexWindowMapEntry{ /** * @var int[] @@ -38,7 +38,7 @@ final class ComplexInventoryMapEntry{ * @phpstan-param array $slotMap */ public function __construct( - private Inventory $inventory, + private InventoryWindow $inventory, private array $slotMap ){ foreach($slotMap as $slot => $index){ @@ -46,7 +46,7 @@ final class ComplexInventoryMapEntry{ } } - public function getInventory() : Inventory{ return $this->inventory; } + public function getWindow() : InventoryWindow{ return $this->inventory; } /** * @return int[] diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index a57bf20c4..91be53362 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -23,17 +23,17 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; -use pocketmine\block\inventory\AnvilInventory; -use pocketmine\block\inventory\BlockInventory; -use pocketmine\block\inventory\BrewingStandInventory; -use pocketmine\block\inventory\CartographyTableInventory; -use pocketmine\block\inventory\CraftingTableInventory; -use pocketmine\block\inventory\EnchantInventory; -use pocketmine\block\inventory\FurnaceInventory; -use pocketmine\block\inventory\HopperInventory; -use pocketmine\block\inventory\LoomInventory; -use pocketmine\block\inventory\SmithingTableInventory; -use pocketmine\block\inventory\StonecutterInventory; +use pocketmine\block\inventory\AnvilInventoryWindow; +use pocketmine\block\inventory\BlockInventoryWindow; +use pocketmine\block\inventory\BrewingStandInventoryWindow; +use pocketmine\block\inventory\CartographyTableInventoryWindow; +use pocketmine\block\inventory\CraftingTableInventoryWindow; +use pocketmine\block\inventory\EnchantingTableInventoryWindow; +use pocketmine\block\inventory\FurnaceInventoryWindow; +use pocketmine\block\inventory\HopperInventoryWindow; +use pocketmine\block\inventory\LoomInventoryWindow; +use pocketmine\block\inventory\SmithingTableInventoryWindow; +use pocketmine\block\inventory\StonecutterInventoryWindow; use pocketmine\crafting\FurnaceType; use pocketmine\data\bedrock\EnchantmentIdMap; use pocketmine\inventory\Inventory; @@ -63,7 +63,9 @@ use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction; use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes; use pocketmine\network\PacketHandlingException; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; +use pocketmine\player\PlayerInventoryWindow; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\ObjectSet; use function array_fill_keys; @@ -78,27 +80,27 @@ use function max; use function spl_object_id; /** - * @phpstan-type ContainerOpenClosure \Closure(int $id, Inventory $inventory) : (list|null) + * @phpstan-type ContainerOpenClosure \Closure(int $id, InventoryWindow $window) : (list|null) */ class InventoryManager implements InventoryListener{ /** * @var InventoryManagerEntry[] spl_object_id(Inventory) => InventoryManagerEntry * @phpstan-var array */ - private array $inventories = []; + private array $entries = []; /** - * @var Inventory[] network window ID => Inventory - * @phpstan-var array + * @var InventoryWindow[] network window ID => InventoryWindow + * @phpstan-var array */ - private array $networkIdToInventoryMap = []; + private array $networkIdToWindowMap = []; /** - * @var ComplexInventoryMapEntry[] net slot ID => ComplexWindowMapEntry - * @phpstan-var array + * @var ComplexWindowMapEntry[] net slot ID => ComplexWindowMapEntry + * @phpstan-var array */ - private array $complexSlotToInventoryMap = []; + private array $complexSlotToWindowMap = []; - private int $lastInventoryNetworkId = ContainerIds::FIRST; + private int $lastWindowNetworkId = ContainerIds::FIRST; private int $currentWindowType = WindowTypes::CONTAINER; private int $clientSelectedHotbarSlot = -1; @@ -128,34 +130,48 @@ class InventoryManager implements InventoryListener{ $this->containerOpenCallbacks = new ObjectSet(); $this->containerOpenCallbacks->add(self::createContainerOpen(...)); - $this->add(ContainerIds::INVENTORY, $this->player->getInventory()); - $this->add(ContainerIds::OFFHAND, $this->player->getOffHandInventory()); - $this->add(ContainerIds::ARMOR, $this->player->getArmorInventory()); - $this->addComplex(UIInventorySlotOffset::CURSOR, $this->player->getCursorInventory()); - $this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $this->player->getCraftingGrid()); + foreach($this->player->getPermanentWindows() as $window){ + match($window->getType()){ + PlayerInventoryWindow::TYPE_INVENTORY => $this->add(ContainerIds::INVENTORY, $window), + PlayerInventoryWindow::TYPE_OFFHAND => $this->add(ContainerIds::OFFHAND, $window), + PlayerInventoryWindow::TYPE_ARMOR => $this->add(ContainerIds::ARMOR, $window), + PlayerInventoryWindow::TYPE_CURSOR => $this->addComplex(UIInventorySlotOffset::CURSOR, $window), + PlayerInventoryWindow::TYPE_CRAFTING => $this->addComplex(UIInventorySlotOffset::CRAFTING2X2_INPUT, $window), + default => throw new AssumptionFailedError("Unknown permanent window type " . $window->getType()) + }; + } $this->player->getHotbar()->getSelectedIndexChangeListeners()->add($this->syncSelectedHotbarSlot(...)); } - private function associateIdWithInventory(int $id, Inventory $inventory) : void{ - $this->networkIdToInventoryMap[$id] = $inventory; + private function associateIdWithInventory(int $id, InventoryWindow $window) : void{ + $this->networkIdToWindowMap[$id] = $window; } private function getNewWindowId() : int{ - $this->lastInventoryNetworkId = max(ContainerIds::FIRST, ($this->lastInventoryNetworkId + 1) % ContainerIds::LAST); - return $this->lastInventoryNetworkId; + $this->lastWindowNetworkId = max(ContainerIds::FIRST, ($this->lastWindowNetworkId + 1) % ContainerIds::LAST); + return $this->lastWindowNetworkId; } - private function add(int $id, Inventory $inventory) : void{ - if(isset($this->inventories[spl_object_id($inventory)])){ - throw new \InvalidArgumentException("Inventory " . get_class($inventory) . " is already tracked"); + private function getEntry(Inventory $inventory) : ?InventoryManagerEntry{ + return $this->entries[spl_object_id($inventory)] ?? null; + } + + public function getInventoryWindow(Inventory $inventory) : ?InventoryWindow{ + return $this->getEntry($inventory)?->window; + } + + private function add(int $id, InventoryWindow $window) : void{ + $k = spl_object_id($window->getInventory()); + if(isset($this->entries[$k])){ + throw new \InvalidArgumentException("Inventory " . get_class($window) . " is already tracked"); } - $this->inventories[spl_object_id($inventory)] = new InventoryManagerEntry($inventory); - $inventory->getListeners()->add($this); - $this->associateIdWithInventory($id, $inventory); + $this->entries[$k] = new InventoryManagerEntry($window); + $window->getInventory()->getListeners()->add($this); + $this->associateIdWithInventory($id, $window); } - private function addDynamic(Inventory $inventory) : int{ + private function addDynamic(InventoryWindow $inventory) : int{ $id = $this->getNewWindowId(); $this->add($id, $inventory); return $id; @@ -165,18 +181,19 @@ class InventoryManager implements InventoryListener{ * @param int[]|int $slotMap * @phpstan-param array|int $slotMap */ - private function addComplex(array|int $slotMap, Inventory $inventory) : void{ - if(isset($this->inventories[spl_object_id($inventory)])){ - throw new \InvalidArgumentException("Inventory " . get_class($inventory) . " is already tracked"); + private function addComplex(array|int $slotMap, InventoryWindow $window) : void{ + $k = spl_object_id($window->getInventory()); + if(isset($this->entries[$k])){ + throw new \InvalidArgumentException("Inventory " . get_class($window) . " is already tracked"); } - $complexSlotMap = new ComplexInventoryMapEntry($inventory, is_int($slotMap) ? [$slotMap => 0] : $slotMap); - $this->inventories[spl_object_id($inventory)] = new InventoryManagerEntry( - $inventory, + $complexSlotMap = new ComplexWindowMapEntry($window, is_int($slotMap) ? [$slotMap => 0] : $slotMap); + $this->entries[$k] = new InventoryManagerEntry( + $window, $complexSlotMap ); - $inventory->getListeners()->add($this); + $window->getInventory()->getListeners()->add($this); foreach($complexSlotMap->getSlotMap() as $netSlot => $coreSlot){ - $this->complexSlotToInventoryMap[$netSlot] = $complexSlotMap; + $this->complexSlotToWindowMap[$netSlot] = $complexSlotMap; } } @@ -184,7 +201,7 @@ class InventoryManager implements InventoryListener{ * @param int[]|int $slotMap * @phpstan-param array|int $slotMap */ - private function addComplexDynamic(array|int $slotMap, Inventory $inventory) : int{ + private function addComplexDynamic(array|int $slotMap, InventoryWindow $inventory) : int{ $this->addComplex($slotMap, $inventory); $id = $this->getNewWindowId(); $this->associateIdWithInventory($id, $inventory); @@ -192,49 +209,52 @@ class InventoryManager implements InventoryListener{ } private function remove(int $id) : void{ - $inventory = $this->networkIdToInventoryMap[$id]; - unset($this->networkIdToInventoryMap[$id]); - if($this->getWindowId($inventory) === null){ + $window = $this->networkIdToWindowMap[$id]; + $inventory = $window->getInventory(); + unset($this->networkIdToWindowMap[$id]); + if($this->getWindowId($window) === null){ $inventory->getListeners()->remove($this); - unset($this->inventories[spl_object_id($inventory)]); - foreach($this->complexSlotToInventoryMap as $netSlot => $entry){ - if($entry->getInventory() === $inventory){ - unset($this->complexSlotToInventoryMap[$netSlot]); + unset($this->entries[spl_object_id($inventory)]); + foreach($this->complexSlotToWindowMap as $netSlot => $entry){ + if($entry->getWindow() === $window){ + unset($this->complexSlotToWindowMap[$netSlot]); } } } } - public function getWindowId(Inventory $inventory) : ?int{ - return ($id = array_search($inventory, $this->networkIdToInventoryMap, true)) !== false ? $id : null; + public function getWindowId(InventoryWindow $window) : ?int{ + return ($id = array_search($window, $this->networkIdToWindowMap, true)) !== false ? $id : null; } public function getCurrentWindowId() : int{ - return $this->lastInventoryNetworkId; + return $this->lastWindowNetworkId; } /** - * @phpstan-return array{Inventory, int}|null + * @phpstan-return array{InventoryWindow, int}|null */ public function locateWindowAndSlot(int $windowId, int $netSlotId) : ?array{ if($windowId === ContainerIds::UI){ - $entry = $this->complexSlotToInventoryMap[$netSlotId] ?? null; + $entry = $this->complexSlotToWindowMap[$netSlotId] ?? null; if($entry === null){ return null; } - $inventory = $entry->getInventory(); + $window = $entry->getWindow(); $coreSlotId = $entry->mapNetToCore($netSlotId); - return $coreSlotId !== null && $inventory->slotExists($coreSlotId) ? [$inventory, $coreSlotId] : null; + return $coreSlotId !== null && $window->getInventory()->slotExists($coreSlotId) ? [$window, $coreSlotId] : null; } - $inventory = $this->networkIdToInventoryMap[$windowId] ?? null; - if($inventory !== null && $inventory->slotExists($netSlotId)){ - return [$inventory, $netSlotId]; + $window = $this->networkIdToWindowMap[$windowId] ?? null; + if($window !== null && $window->getInventory()->slotExists($netSlotId)){ + return [$window, $netSlotId]; } return null; } - private function addPredictedSlotChange(Inventory $inventory, int $slot, ItemStack $item) : void{ - $this->inventories[spl_object_id($inventory)]->predictions[$slot] = $item; + private function addPredictedSlotChange(InventoryWindow $window, int $slot, ItemStack $item) : void{ + //TODO: does this need a null check? + $entry = $this->getEntry($window->getInventory()) ?? throw new AssumptionFailedError("Assume this should never be null"); + $entry->predictions[$slot] = $item; } public function addTransactionPredictedSlotChanges(InventoryTransaction $tx) : void{ @@ -243,7 +263,7 @@ class InventoryManager implements InventoryListener{ if($action instanceof SlotChangeAction){ //TODO: ItemStackRequestExecutor can probably build these predictions with much lower overhead $itemStack = $typeConverter->coreItemStackToNet($action->getTargetItem()); - $this->addPredictedSlotChange($action->getInventory(), $action->getSlot(), $itemStack); + $this->addPredictedSlotChange($action->getInventoryWindow(), $action->getSlot(), $itemStack); } } } @@ -271,8 +291,8 @@ class InventoryManager implements InventoryListener{ continue; } - [$inventory, $slot] = $info; - $this->addPredictedSlotChange($inventory, $slot, $action->newItem->getItemStack()); + [$window, $slot] = $info; + $this->addPredictedSlotChange($window, $slot, $action->newItem->getItemStack()); } } @@ -307,32 +327,32 @@ class InventoryManager implements InventoryListener{ * @return int[]|null * @phpstan-return array|null */ - private function createComplexSlotMapping(Inventory $inventory) : ?array{ + private function createComplexSlotMapping(InventoryWindow $inventory) : ?array{ //TODO: make this dynamic so plugins can add mappings for stuff not implemented by PM return match(true){ - $inventory instanceof AnvilInventory => UIInventorySlotOffset::ANVIL, - $inventory instanceof EnchantInventory => UIInventorySlotOffset::ENCHANTING_TABLE, - $inventory instanceof LoomInventory => UIInventorySlotOffset::LOOM, - $inventory instanceof StonecutterInventory => [UIInventorySlotOffset::STONE_CUTTER_INPUT => StonecutterInventory::SLOT_INPUT], - $inventory instanceof CraftingTableInventory => UIInventorySlotOffset::CRAFTING3X3_INPUT, - $inventory instanceof CartographyTableInventory => UIInventorySlotOffset::CARTOGRAPHY_TABLE, - $inventory instanceof SmithingTableInventory => UIInventorySlotOffset::SMITHING_TABLE, + $inventory instanceof AnvilInventoryWindow => UIInventorySlotOffset::ANVIL, + $inventory instanceof EnchantingTableInventoryWindow => UIInventorySlotOffset::ENCHANTING_TABLE, + $inventory instanceof LoomInventoryWindow => UIInventorySlotOffset::LOOM, + $inventory instanceof StonecutterInventoryWindow => [UIInventorySlotOffset::STONE_CUTTER_INPUT => StonecutterInventoryWindow::SLOT_INPUT], + $inventory instanceof CraftingTableInventoryWindow => UIInventorySlotOffset::CRAFTING3X3_INPUT, + $inventory instanceof CartographyTableInventoryWindow => UIInventorySlotOffset::CARTOGRAPHY_TABLE, + $inventory instanceof SmithingTableInventoryWindow => UIInventorySlotOffset::SMITHING_TABLE, default => null, }; } - public function onCurrentWindowChange(Inventory $inventory) : void{ + public function onCurrentWindowChange(InventoryWindow $window) : void{ $this->onCurrentWindowRemove(); - $this->openWindowDeferred(function() use ($inventory) : void{ - if(($slotMap = $this->createComplexSlotMapping($inventory)) !== null){ - $windowId = $this->addComplexDynamic($slotMap, $inventory); + $this->openWindowDeferred(function() use ($window) : void{ + if(($slotMap = $this->createComplexSlotMapping($window)) !== null){ + $windowId = $this->addComplexDynamic($slotMap, $window); }else{ - $windowId = $this->addDynamic($inventory); + $windowId = $this->addDynamic($window); } foreach($this->containerOpenCallbacks as $callback){ - $pks = $callback($windowId, $inventory); + $pks = $callback($windowId, $window); if($pks !== null){ $windowType = null; foreach($pks as $pk){ @@ -343,7 +363,7 @@ class InventoryManager implements InventoryListener{ $this->session->sendDataPacket($pk); } $this->currentWindowType = $windowType ?? WindowTypes::CONTAINER; - $this->syncContents($inventory); + $this->syncContents($window); return; } } @@ -358,27 +378,27 @@ class InventoryManager implements InventoryListener{ * @return ClientboundPacket[]|null * @phpstan-return list|null */ - protected static function createContainerOpen(int $id, Inventory $inv) : ?array{ + protected static function createContainerOpen(int $id, InventoryWindow $window) : ?array{ //TODO: we should be using some kind of tagging system to identify the types. Instanceof is flaky especially //if the class isn't final, not to mention being inflexible. - if($inv instanceof BlockInventory){ - $blockPosition = BlockPosition::fromVector3($inv->getHolder()); + if($window instanceof BlockInventoryWindow){ + $blockPosition = BlockPosition::fromVector3($window->getHolder()); $windowType = match(true){ - $inv instanceof LoomInventory => WindowTypes::LOOM, - $inv instanceof FurnaceInventory => match($inv->getFurnaceType()){ + $window instanceof LoomInventoryWindow => WindowTypes::LOOM, + $window instanceof FurnaceInventoryWindow => match($window->getFurnaceType()){ FurnaceType::FURNACE => WindowTypes::FURNACE, FurnaceType::BLAST_FURNACE => WindowTypes::BLAST_FURNACE, FurnaceType::SMOKER => WindowTypes::SMOKER, FurnaceType::CAMPFIRE, FurnaceType::SOUL_CAMPFIRE => throw new \LogicException("Campfire inventory cannot be displayed to a player") }, - $inv instanceof EnchantInventory => WindowTypes::ENCHANTMENT, - $inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND, - $inv instanceof AnvilInventory => WindowTypes::ANVIL, - $inv instanceof HopperInventory => WindowTypes::HOPPER, - $inv instanceof CraftingTableInventory => WindowTypes::WORKBENCH, - $inv instanceof StonecutterInventory => WindowTypes::STONECUTTER, - $inv instanceof CartographyTableInventory => WindowTypes::CARTOGRAPHY, - $inv instanceof SmithingTableInventory => WindowTypes::SMITHING_TABLE, + $window instanceof EnchantingTableInventoryWindow => WindowTypes::ENCHANTMENT, + $window instanceof BrewingStandInventoryWindow => WindowTypes::BREWING_STAND, + $window instanceof AnvilInventoryWindow => WindowTypes::ANVIL, + $window instanceof HopperInventoryWindow => WindowTypes::HOPPER, + $window instanceof CraftingTableInventoryWindow => WindowTypes::WORKBENCH, + $window instanceof StonecutterInventoryWindow => WindowTypes::STONECUTTER, + $window instanceof CartographyTableInventoryWindow => WindowTypes::CARTOGRAPHY, + $window instanceof SmithingTableInventoryWindow => WindowTypes::SMITHING_TABLE, default => WindowTypes::CONTAINER }; return [ContainerOpenPacket::blockInv($id, $windowType, $blockPosition)]; @@ -391,7 +411,8 @@ class InventoryManager implements InventoryListener{ $this->openWindowDeferred(function() : void{ $windowId = $this->getNewWindowId(); - $this->associateIdWithInventory($windowId, $this->player->getInventory()); + $window = $this->getInventoryWindow($this->player->getInventory()) ?? throw new AssumptionFailedError("This should never be null"); + $this->associateIdWithInventory($windowId, $window); $this->currentWindowType = WindowTypes::INVENTORY; $this->session->sendDataPacket(ContainerOpenPacket::entityInv( @@ -403,25 +424,25 @@ class InventoryManager implements InventoryListener{ } public function onCurrentWindowRemove() : void{ - if(isset($this->networkIdToInventoryMap[$this->lastInventoryNetworkId])){ - $this->remove($this->lastInventoryNetworkId); - $this->session->sendDataPacket(ContainerClosePacket::create($this->lastInventoryNetworkId, $this->currentWindowType, true)); + if(isset($this->networkIdToWindowMap[$this->lastWindowNetworkId])){ + $this->remove($this->lastWindowNetworkId); + $this->session->sendDataPacket(ContainerClosePacket::create($this->lastWindowNetworkId, $this->currentWindowType, true)); if($this->pendingCloseWindowId !== null){ throw new AssumptionFailedError("We should not have opened a new window while a window was waiting to be closed"); } - $this->pendingCloseWindowId = $this->lastInventoryNetworkId; + $this->pendingCloseWindowId = $this->lastWindowNetworkId; $this->enchantingTableOptions = []; } } public function onClientRemoveWindow(int $id) : void{ - if($id === $this->lastInventoryNetworkId){ - if(isset($this->networkIdToInventoryMap[$id]) && $id !== $this->pendingCloseWindowId){ + if($id === $this->lastWindowNetworkId){ + if(isset($this->networkIdToWindowMap[$id]) && $id !== $this->pendingCloseWindowId){ $this->remove($id); $this->player->removeCurrentWindow(); } }else{ - $this->session->getLogger()->debug("Attempted to close inventory with network ID $id, but current is $this->lastInventoryNetworkId"); + $this->session->getLogger()->debug("Attempted to close inventory with network ID $id, but current is $this->lastWindowNetworkId"); } //Always send this, even if no window matches. If we told the client to close a window, it will behave as if it @@ -474,12 +495,24 @@ class InventoryManager implements InventoryListener{ } public function onSlotChange(Inventory $inventory, int $slot, Item $oldItem) : void{ - $inventoryEntry = $this->inventories[spl_object_id($inventory)] ?? null; + $window = $this->getInventoryWindow($inventory); + if($window === null){ + //this can happen when an inventory changed during InventoryCloseEvent, or when a temporary inventory + //is cleared before removal. + return; + } + $this->requestSyncSlot($window, $slot); + } + + public function requestSyncSlot(InventoryWindow $window, int $slot) : void{ + $inventory = $window->getInventory(); + $inventoryEntry = $this->getEntry($inventory); if($inventoryEntry === null){ //this can happen when an inventory changed during InventoryCloseEvent, or when a temporary inventory //is cleared before removal. return; } + $currentItem = $this->session->getTypeConverter()->coreItemStackToNet($inventory->getItem($slot)); $clientSideItem = $inventoryEntry->predictions[$slot] ?? null; if($clientSideItem === null || !$this->itemStacksEqual($currentItem, $clientSideItem)){ @@ -507,7 +540,7 @@ class InventoryManager implements InventoryListener{ $this->session->sendDataPacket(InventorySlotPacket::create( $windowId, $netSlot, - new FullContainerName($this->lastInventoryNetworkId), + new FullContainerName($this->lastWindowNetworkId), new ItemStackWrapper(0, ItemStack::null()), new ItemStackWrapper(0, ItemStack::null()) )); @@ -516,7 +549,7 @@ class InventoryManager implements InventoryListener{ $this->session->sendDataPacket(InventorySlotPacket::create( $windowId, $netSlot, - new FullContainerName($this->lastInventoryNetworkId), + new FullContainerName($this->lastWindowNetworkId), new ItemStackWrapper(0, ItemStack::null()), $itemStackWrapper )); @@ -537,18 +570,15 @@ class InventoryManager implements InventoryListener{ $this->session->sendDataPacket(InventoryContentPacket::create( $windowId, array_fill_keys(array_keys($itemStackWrappers), new ItemStackWrapper(0, ItemStack::null())), - new FullContainerName($this->lastInventoryNetworkId), + new FullContainerName($this->lastWindowNetworkId), new ItemStackWrapper(0, ItemStack::null()) )); //now send the real contents - $this->session->sendDataPacket(InventoryContentPacket::create($windowId, $itemStackWrappers, new FullContainerName($this->lastInventoryNetworkId), new ItemStackWrapper(0, ItemStack::null()))); + $this->session->sendDataPacket(InventoryContentPacket::create($windowId, $itemStackWrappers, new FullContainerName($this->lastWindowNetworkId), new ItemStackWrapper(0, ItemStack::null()))); } - public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack) : void{ - $entry = $this->inventories[spl_object_id($inventory)] ?? null; - if($entry === null){ - throw new \LogicException("Cannot sync an untracked inventory"); - } + private function syncSlot(InventoryWindow $window, int $slot, ItemStack $itemStack) : void{ + $entry = $this->getEntry($window->getInventory()) ?? throw new \LogicException("Cannot sync an untracked inventory"); $itemStackInfo = $entry->itemStackInfos[$slot]; if($itemStackInfo === null){ throw new \LogicException("Cannot sync an untracked inventory slot"); @@ -557,7 +587,7 @@ class InventoryManager implements InventoryListener{ $windowId = ContainerIds::UI; $netSlot = $entry->complexSlotMap->mapCoreToNet($slot) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null"); }else{ - $windowId = $this->getWindowId($inventory) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null"); + $windowId = $this->getWindowId($window) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null"); $netSlot = $slot; } @@ -576,11 +606,16 @@ class InventoryManager implements InventoryListener{ } public function onContentChange(Inventory $inventory, array $oldContents) : void{ - $this->syncContents($inventory); + //this can be null when an inventory changed during InventoryCloseEvent, or when a temporary inventory + //is cleared before removal. + $window = $this->getInventoryWindow($inventory); + if($window !== null){ + $this->syncContents($window); + } } - public function syncContents(Inventory $inventory) : void{ - $entry = $this->inventories[spl_object_id($inventory)] ?? null; + private function syncContents(InventoryWindow $window) : void{ + $entry = $this->getEntry($window->getInventory()); if($entry === null){ //this can happen when an inventory changed during InventoryCloseEvent, or when a temporary inventory //is cleared before removal. @@ -589,14 +624,14 @@ class InventoryManager implements InventoryListener{ if($entry->complexSlotMap !== null){ $windowId = ContainerIds::UI; }else{ - $windowId = $this->getWindowId($inventory); + $windowId = $this->getWindowId($window); } if($windowId !== null){ $entry->predictions = []; $entry->pendingSyncs = []; $contents = []; $typeConverter = $this->session->getTypeConverter(); - foreach($inventory->getContents(true) as $slot => $item){ + foreach($window->getInventory()->getContents(true) as $slot => $item){ $itemStack = $typeConverter->coreItemStackToNet($item); $info = $this->trackItemStack($entry, $slot, $itemStack, null); $contents[] = new ItemStackWrapper($info->getStackId(), $itemStack); @@ -617,8 +652,8 @@ class InventoryManager implements InventoryListener{ } public function syncAll() : void{ - foreach($this->inventories as $entry){ - $this->syncContents($entry->inventory); + foreach($this->entries as $entry){ + $this->syncContents($entry->window); } } @@ -628,8 +663,8 @@ class InventoryManager implements InventoryListener{ public function syncMismatchedPredictedSlotChanges() : void{ $typeConverter = $this->session->getTypeConverter(); - foreach($this->inventories as $entry){ - $inventory = $entry->inventory; + foreach($this->entries as $entry){ + $inventory = $entry->window->getInventory(); foreach($entry->predictions as $slot => $expectedItem){ if(!$inventory->slotExists($slot) || $entry->itemStackInfos[$slot] === null){ continue; //TODO: size desync ??? @@ -647,14 +682,14 @@ class InventoryManager implements InventoryListener{ public function flushPendingUpdates() : void{ if($this->fullSyncRequested){ $this->fullSyncRequested = false; - $this->session->getLogger()->debug("Full inventory sync requested, sending contents of " . count($this->inventories) . " inventories"); + $this->session->getLogger()->debug("Full inventory sync requested, sending contents of " . count($this->entries) . " inventories"); $this->syncAll(); }else{ - foreach($this->inventories as $entry){ + foreach($this->entries as $entry){ if(count($entry->pendingSyncs) === 0){ continue; } - $inventory = $entry->inventory; + $inventory = $entry->window; $this->session->getLogger()->debug("Syncing slots " . implode(", ", array_keys($entry->pendingSyncs)) . " in inventory " . get_class($inventory) . "#" . spl_object_id($inventory)); foreach($entry->pendingSyncs as $slot => $itemStack){ $this->syncSlot($inventory, $slot, $itemStack); @@ -665,7 +700,13 @@ class InventoryManager implements InventoryListener{ } public function syncData(Inventory $inventory, int $propertyId, int $value) : void{ - $windowId = $this->getWindowId($inventory); + //TODO: the handling of this data has always kinda sucked. Probably ought to route it through InventoryWindow + //somehow, but I'm not sure exactly how that should look. + $window = $this->getInventoryWindow($inventory); + if($window === null){ + return; + } + $windowId = $this->getWindowId($window); if($windowId !== null){ $this->session->sendDataPacket(ContainerSetDataPacket::create($windowId, $propertyId, $value)); } @@ -679,10 +720,7 @@ class InventoryManager implements InventoryListener{ $playerInventory = $this->player->getInventory(); $selected = $this->player->getHotbar()->getSelectedIndex(); if($selected !== $this->clientSelectedHotbarSlot){ - $inventoryEntry = $this->inventories[spl_object_id($playerInventory)] ?? null; - if($inventoryEntry === null){ - throw new AssumptionFailedError("Player inventory should always be tracked"); - } + $inventoryEntry = $this->getEntry($playerInventory) ?? throw new AssumptionFailedError("Player inventory should always be tracked"); $itemStackInfo = $inventoryEntry->itemStackInfos[$selected] ?? null; if($itemStackInfo === null){ throw new AssumptionFailedError("Untracked player inventory slot $selected"); @@ -741,8 +779,7 @@ class InventoryManager implements InventoryListener{ } public function getItemStackInfo(Inventory $inventory, int $slot) : ?ItemStackInfo{ - $entry = $this->inventories[spl_object_id($inventory)] ?? null; - return $entry?->itemStackInfos[$slot] ?? null; + return $this->getEntry($inventory)?->itemStackInfos[$slot] ?? null; } private function trackItemStack(InventoryManagerEntry $entry, int $slotId, ItemStack $itemStack, ?int $itemStackRequestId) : ItemStackInfo{ diff --git a/src/network/mcpe/InventoryManagerEntry.php b/src/network/mcpe/InventoryManagerEntry.php index deb2e8e4d..8f7c07de2 100644 --- a/src/network/mcpe/InventoryManagerEntry.php +++ b/src/network/mcpe/InventoryManagerEntry.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; -use pocketmine\inventory\Inventory; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; +use pocketmine\player\InventoryWindow; final class InventoryManagerEntry{ /** @@ -46,7 +46,7 @@ final class InventoryManagerEntry{ public array $pendingSyncs = []; public function __construct( - public Inventory $inventory, - public ?ComplexInventoryMapEntry $complexSlotMap = null + public InventoryWindow $window, + public ?ComplexWindowMapEntry $complexSlotMap = null ){} } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 8c101853f..6a61a5c0f 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -101,6 +101,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerBlockActionStopBreak; use pocketmine\network\mcpe\protocol\types\PlayerBlockActionWithBlockInfo; use pocketmine\network\PacketHandlingException; use pocketmine\player\Player; +use pocketmine\player\PlayerInventoryWindow; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; use pocketmine\utils\TextFormat; @@ -355,7 +356,7 @@ class InGamePacketHandler extends PacketHandler{ [$windowId, $slot] = ItemStackContainerIdTranslator::translate($containerInfo->getContainerId(), $this->inventoryManager->getCurrentWindowId(), $netSlot); $inventoryAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $slot); if($inventoryAndSlot !== null){ //trigger the normal slot sync logic - $this->inventoryManager->onSlotChange($inventoryAndSlot[0], $inventoryAndSlot[1]); + $this->inventoryManager->requestSyncSlot($inventoryAndSlot[0], $inventoryAndSlot[1]); } } } @@ -461,7 +462,8 @@ class InGamePacketHandler extends PacketHandler{ $droppedItem = $sourceSlotItem->pop($droppedCount); $builder = new TransactionBuilder(); - $builder->getInventory($inventory)->setItem($sourceSlot, $sourceSlotItem); + //TODO: this probably shouldn't be creating an ephemeral window here - it works, but no idea what side effects it might have on the permanent window + $builder->getActionBuilder(new PlayerInventoryWindow($this->player, $inventory, PlayerInventoryWindow::TYPE_INVENTORY))->setItem($sourceSlot, $sourceSlotItem); $builder->addAction(new DropItemAction($droppedItem)); $transaction = new InventoryTransaction($this->player, $builder->generateActions()); diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php index 54a192590..d58ec6223 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\handler; -use pocketmine\block\inventory\EnchantInventory; +use pocketmine\block\inventory\EnchantingTableInventoryWindow; use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\CreateItemAction; use pocketmine\inventory\transaction\action\DestroyItemAction; @@ -31,8 +31,8 @@ use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\CraftingTransaction; use pocketmine\inventory\transaction\EnchantingTransaction; use pocketmine\inventory\transaction\InventoryTransaction; +use pocketmine\inventory\transaction\SlotChangeActionBuilder; use pocketmine\inventory\transaction\TransactionBuilder; -use pocketmine\inventory\transaction\TransactionBuilderInventory; use pocketmine\item\Item; use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds; @@ -52,6 +52,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\SwapStackReque use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\TakeStackRequestAction; use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponse; use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; use function array_key_first; @@ -81,25 +82,22 @@ class ItemStackRequestExecutor{ $this->builder = new TransactionBuilder(); } - protected function prettyInventoryAndSlot(Inventory $inventory, int $slot) : string{ - if($inventory instanceof TransactionBuilderInventory){ - $inventory = $inventory->getActualInventory(); - } + protected function prettyWindowAndSlot(InventoryWindow $inventory, int $slot) : string{ return (new \ReflectionClass($inventory))->getShortName() . "#" . spl_object_id($inventory) . ", slot: $slot"; } /** * @throws ItemStackRequestProcessException */ - private function matchItemStack(Inventory $inventory, int $slotId, int $clientItemStackId) : void{ - $info = $this->inventoryManager->getItemStackInfo($inventory, $slotId); + private function matchItemStack(InventoryWindow $window, int $slotId, int $clientItemStackId) : void{ + $info = $this->inventoryManager->getItemStackInfo($window->getInventory(), $slotId); if($info === null){ throw new AssumptionFailedError("The inventory is tracked and the slot is valid, so this should not be null"); } if(!($clientItemStackId < 0 ? $info->getRequestId() === $clientItemStackId : $info->getStackId() === $clientItemStackId)){ throw new ItemStackRequestProcessException( - $this->prettyInventoryAndSlot($inventory, $slotId) . ": " . + $this->prettyWindowAndSlot($window, $slotId) . ": " . "Mismatched expected itemstack, " . "client expected: $clientItemStackId, server actual: " . $info->getStackId() . ", last modified by request: " . ($info->getRequestId() ?? "none") ); @@ -107,7 +105,7 @@ class ItemStackRequestExecutor{ } /** - * @phpstan-return array{TransactionBuilderInventory, int} + * @phpstan-return array{SlotChangeActionBuilder, int} * * @throws ItemStackRequestProcessException */ @@ -117,16 +115,17 @@ class ItemStackRequestExecutor{ if($windowAndSlot === null){ throw new ItemStackRequestProcessException("No open inventory matches container UI ID: " . $info->getContainerName()->getContainerId() . ", slot ID: " . $info->getSlotId()); } - [$inventory, $slot] = $windowAndSlot; + [$window, $slot] = $windowAndSlot; + $inventory = $window->getInventory(); if(!$inventory->slotExists($slot)){ - throw new ItemStackRequestProcessException("No such inventory slot :" . $this->prettyInventoryAndSlot($inventory, $slot)); + throw new ItemStackRequestProcessException("No such inventory slot :" . $this->prettyWindowAndSlot($window, $slot)); } if($info->getStackId() !== $this->request->getRequestId()){ //the itemstack may have been modified by the current request - $this->matchItemStack($inventory, $slot, $info->getStackId()); + $this->matchItemStack($window, $slot, $info->getStackId()); } - return [$this->builder->getInventory($inventory), $slot]; + return [$this->builder->getActionBuilder($window), $slot]; } /** @@ -151,12 +150,12 @@ class ItemStackRequestExecutor{ [$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo); if($count < 1){ //this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits - throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack"); + throw new ItemStackRequestProcessException($this->prettyWindowAndSlot($inventory->getInventoryWindow(), $slot) . ": Cannot take less than 1 items from a stack"); } $existingItem = $inventory->getItem($slot); if($existingItem->getCount() < $count){ - throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take $count items from a stack of " . $existingItem->getCount()); + throw new ItemStackRequestProcessException($this->prettyWindowAndSlot($inventory->getInventoryWindow(), $slot) . ": Cannot take $count items from a stack of " . $existingItem->getCount()); } $removed = $existingItem->pop($count); @@ -174,12 +173,12 @@ class ItemStackRequestExecutor{ [$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo); if($count < 1){ //this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits - throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack"); + throw new ItemStackRequestProcessException($this->prettyWindowAndSlot($inventory->getInventoryWindow(), $slot) . ": Cannot take less than 1 items from a stack"); } $existingItem = $inventory->getItem($slot); if(!$existingItem->isNull() && !$existingItem->canStackWith($item)){ - throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Can only add items to an empty slot, or a slot containing the same item"); + throw new ItemStackRequestProcessException($this->prettyWindowAndSlot($inventory->getInventoryWindow(), $slot) . ": Can only add items to an empty slot, or a slot containing the same item"); } //we can't use the existing item here; it may be an empty stack @@ -336,7 +335,7 @@ class ItemStackRequestExecutor{ $this->setNextCreatedItem($item, true); }elseif($action instanceof CraftRecipeStackRequestAction){ $window = $this->player->getCurrentWindow(); - if($window instanceof EnchantInventory){ + if($window instanceof EnchantingTableInventoryWindow){ $optionId = $this->inventoryManager->getEnchantingTableOptionIndex($action->getRecipeId()); if($optionId !== null && ($option = $window->getOption($optionId)) !== null){ $this->specialTransaction = new EnchantingTransaction($this->player, $option, $optionId + 1); diff --git a/src/network/mcpe/handler/ItemStackResponseBuilder.php b/src/network/mcpe/handler/ItemStackResponseBuilder.php index 1369e3ba7..75c740a5f 100644 --- a/src/network/mcpe/handler/ItemStackResponseBuilder.php +++ b/src/network/mcpe/handler/ItemStackResponseBuilder.php @@ -59,7 +59,8 @@ final class ItemStackResponseBuilder{ if($windowAndSlot === null){ return null; } - [$inventory, $slot] = $windowAndSlot; + [$window, $slot] = $windowAndSlot; + $inventory = $window->getInventory(); if(!$inventory->slotExists($slot)){ return null; } diff --git a/src/player/Player.php b/src/player/Player.php index 3dba79931..3b54a2b86 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -85,9 +85,7 @@ use pocketmine\form\FormValidationException; use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\CreativeInventory; use pocketmine\inventory\Inventory; -use pocketmine\inventory\PlayerCraftingInventory; -use pocketmine\inventory\PlayerCursorInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\inventory\SimpleInventory; use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionBuilder; @@ -218,11 +216,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ protected bool $authenticated; protected PlayerInfo $playerInfo; - protected ?Inventory $currentWindow = null; - /** @var Inventory[] */ + protected ?InventoryWindow $currentWindow = null; + /** @var PlayerInventoryWindow[] */ protected array $permanentWindows = []; - protected PlayerCursorInventory $cursorInventory; - protected PlayerCraftingInventory $craftingGrid; + protected Inventory $cursorInventory; + protected CraftingGrid $craftingGrid; protected CreativeInventory $creativeInventory; protected int $messageCounter = 2; @@ -2318,7 +2316,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->loadQueue = []; $this->removeCurrentWindow(); - $this->removePermanentInventories(); + $this->removePermanentWindows(); $this->perm->getPermissionRecalculationCallbacks()->clear(); @@ -2334,8 +2332,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ protected function destroyCycles() : void{ $this->networkSession = null; - unset($this->cursorInventory); - unset($this->craftingGrid); $this->spawnPosition = null; $this->blockBreakHandler = null; parent::destroyCycles(); @@ -2589,15 +2585,19 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } protected function addDefaultWindows() : void{ - $this->cursorInventory = new PlayerCursorInventory($this); - $this->craftingGrid = new PlayerCraftingInventory($this); + $this->cursorInventory = new SimpleInventory(1); + $this->craftingGrid = new CraftingGrid(CraftingGrid::SIZE_SMALL); - $this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory, $this->craftingGrid); - - //TODO: more windows + $this->addPermanentWindows([ + new PlayerInventoryWindow($this, $this->inventory, PlayerInventoryWindow::TYPE_INVENTORY), + new PlayerInventoryWindow($this, $this->armorInventory, PlayerInventoryWindow::TYPE_ARMOR), + new PlayerInventoryWindow($this, $this->cursorInventory, PlayerInventoryWindow::TYPE_CURSOR), + new PlayerInventoryWindow($this, $this->offHandInventory, PlayerInventoryWindow::TYPE_OFFHAND), + new PlayerInventoryWindow($this, $this->craftingGrid, PlayerInventoryWindow::TYPE_CRAFTING), + ]); } - public function getCursorInventory() : PlayerCursorInventory{ + public function getCursorInventory() : Inventory{ return $this->cursorInventory; } @@ -2628,22 +2628,37 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ * inventory. */ private function doCloseInventory() : void{ - $inventories = [$this->craftingGrid, $this->cursorInventory]; - if($this->currentWindow instanceof TemporaryInventory){ - $inventories[] = $this->currentWindow; + $windowsToClear = []; + $mainInventoryWindow = null; + foreach($this->permanentWindows as $window){ + if($window->getType() === PlayerInventoryWindow::TYPE_CRAFTING || $window->getType() === PlayerInventoryWindow::TYPE_CURSOR){ + $windowsToClear[] = $window; + }elseif($window->getType() === PlayerInventoryWindow::TYPE_INVENTORY){ + $mainInventoryWindow = $window; + } + } + if($mainInventoryWindow === null){ + //TODO: in the future this might not be the case, if we implement support for the player closing their + //inventory window outside the protocol layer + //in that case we'd have to create a new ephemeral window here + throw new AssumptionFailedError("This should never be null"); + } + + if($this->currentWindow instanceof TemporaryInventoryWindow){ + $windowsToClear[] = $this->currentWindow; } $builder = new TransactionBuilder(); - foreach($inventories as $inventory){ - $contents = $inventory->getContents(); + foreach($windowsToClear as $window){ + $contents = $window->getInventory()->getContents(); if(count($contents) > 0){ - $drops = $builder->getInventory($this->inventory)->addItem(...$contents); + $drops = $builder->getActionBuilder($mainInventoryWindow)->addItem(...$contents); foreach($drops as $drop){ $builder->addAction(new DropItemAction($drop)); } - $builder->getInventory($inventory)->clearAll(); + $builder->getActionBuilder($window)->clearAll(); } } @@ -2655,8 +2670,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->logger->debug("Successfully evacuated items from temporary inventories"); }catch(TransactionCancelledException){ $this->logger->debug("Plugin cancelled transaction evacuating items from temporary inventories; items will be destroyed"); - foreach($inventories as $inventory){ - $inventory->clearAll(); + foreach($windowsToClear as $window){ + $window->getInventory()->clearAll(); } }catch(TransactionValidationException $e){ throw new AssumptionFailedError("This server-generated transaction should never be invalid", 0, $e); @@ -2667,18 +2682,18 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ /** * Returns the inventory the player is currently viewing. This might be a chest, furnace, or any other container. */ - public function getCurrentWindow() : ?Inventory{ + public function getCurrentWindow() : ?InventoryWindow{ return $this->currentWindow; } /** * Opens an inventory window to the player. Returns if it was successful. */ - public function setCurrentWindow(Inventory $inventory) : bool{ - if($inventory === $this->currentWindow){ + public function setCurrentWindow(InventoryWindow $window) : bool{ + if($window === $this->currentWindow){ return true; } - $ev = new InventoryOpenEvent($inventory, $this); + $ev = new InventoryOpenEvent($window, $this); $ev->call(); if($ev->isCancelled()){ return false; @@ -2689,10 +2704,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ if(($inventoryManager = $this->getNetworkSession()->getInvManager()) === null){ throw new \InvalidArgumentException("Player cannot open inventories in this state"); } - $this->logger->debug("Opening inventory " . get_class($inventory) . "#" . spl_object_id($inventory)); - $inventoryManager->onCurrentWindowChange($inventory); - $inventory->onOpen($this); - $this->currentWindow = $inventory; + $this->logger->debug("Opening inventory " . get_class($window) . "#" . spl_object_id($window)); + $inventoryManager->onCurrentWindowChange($window); + $window->onOpen(); + $this->currentWindow = $window; return true; } @@ -2701,7 +2716,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ if($this->currentWindow !== null){ $currentWindow = $this->currentWindow; $this->logger->debug("Closing inventory " . get_class($this->currentWindow) . "#" . spl_object_id($this->currentWindow)); - $this->currentWindow->onClose($this); + $this->currentWindow->onClose(); if(($inventoryManager = $this->getNetworkSession()->getInvManager()) !== null){ $inventoryManager->onCurrentWindowRemove(); } @@ -2710,20 +2725,31 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } } - protected function addPermanentInventories(Inventory ...$inventories) : void{ - foreach($inventories as $inventory){ - $inventory->onOpen($this); - $this->permanentWindows[spl_object_id($inventory)] = $inventory; + /** + * @param PlayerInventoryWindow[] $windows + */ + protected function addPermanentWindows(array $windows) : void{ + foreach($windows as $window){ + $window->onOpen(); + $this->permanentWindows[spl_object_id($window)] = $window; } } - protected function removePermanentInventories() : void{ - foreach($this->permanentWindows as $inventory){ - $inventory->onClose($this); + protected function removePermanentWindows() : void{ + foreach($this->permanentWindows as $window){ + $window->onClose(); } $this->permanentWindows = []; } + /** + * @return PlayerInventoryWindow[] + * @internal + */ + public function getPermanentWindows() : array{ + return $this->permanentWindows; + } + /** * Opens the player's sign editor GUI for the sign at the given position. * TODO: add support for editing the rear side of the sign (not currently supported due to technical limitations) diff --git a/src/inventory/TemporaryInventory.php b/src/player/TemporaryInventoryWindow.php similarity index 90% rename from src/inventory/TemporaryInventory.php rename to src/player/TemporaryInventoryWindow.php index 26a53b171..c42386564 100644 --- a/src/inventory/TemporaryInventory.php +++ b/src/player/TemporaryInventoryWindow.php @@ -21,8 +21,8 @@ declare(strict_types=1); -namespace pocketmine\inventory; +namespace pocketmine\player; -interface TemporaryInventory extends Inventory{ +interface TemporaryInventoryWindow{ }